Python / jsonschema / 討論 === ###### tags: `Python` ###### tags: `Python`, `JSON`, `jsonschema`,`json-schema`, `schema_path`, `validator`, `dict`, `recursively`,`diff`, `string`, `regular expressions`, `numeric types`, `object`, `array`, `boolean`, `null` <br> [TOC] <br> ## 討論1 ### 實作 openai function-call / tool-call 比對程式 - ### Question 我想要實作 openai function-call / tool-call 比對程式 如果預期是: ```python= "functions": [ { "name": "sculpture_price_calculate", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "type": "object", "properties": { "material": { "type": "string", "description": "The material used for the sculpture." }, "size": { "type": "integer", "description": "The size of the sculpture in feet." }, "complexity": { "type": "string", "enum": [ "low", "medium", "high" ], "description": "The complexity level of the sculpture.", "default": "medium" } }, "required": [ "material", "size" ] } }, ``` 如何用 python 寫一個比對程式,將 output 對比上面的 JSON schema? 像 complexity 就不是必要,但如果有出現,值必須是 "low", "medium", "high" (三個其中之一) - ### ChatGPT-4o 理解了,您想要用 Python 寫一個比對程式,來檢查輸出的 JSON 是否符合給定的 JSON schema。這裡我會用 jsonschema 庫來進行比對。 首先,您需要安裝 jsonschema 庫: ```bash pip install jsonschema ``` 接著,可以使用以下程式碼來進行比對: ```python= import jsonschema from jsonschema import validate # 定義 JSON schema schema = { "functions": [ { "name": "sculpture_price_calculate", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "type": "object", "properties": { "material": { "type": "string", "description": "The material used for the sculpture." }, "size": { "type": "integer", "description": "The size of the sculpture in feet." }, "complexity": { "type": "string", "enum": ["low", "medium", "high"], "description": "The complexity level of the sculpture.", "default": "medium" } }, "required": ["material", "size"] } } ] } # 定義要比對的 JSON 輸出 output = { "functions": [ { "name": "sculpture_price_calculate", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "material": "bronze", "size": 5, "complexity": "medium" } } ] } # 定義比對程式 def compare_json_with_schema(output, schema): try: # 檢查每個 function 是否符合 schema for func in output['functions']: schema_func = next((item for item in schema['functions'] if item['name'] == func['name']), None) if schema_func is not None: validate(instance=func, schema=schema_func) else: raise ValueError(f"Function {func['name']} is not defined in schema.") print("JSON output is valid against the schema.") except jsonschema.exceptions.ValidationError as err: print(f"Validation error: {err.message}") # 執行比對 compare_json_with_schema(output, schema) ``` 這段程式碼的主要步驟如下: 1. 定義 JSON schema 和要比對的 JSON 輸出。 2. 實作 `compare_json_with_schema` 函數,檢查每個 function 是否符合 schema。 3. 使用 `jsonschema.validate` 函數進行比對,如果符合 schema,則輸出有效;否則輸出錯誤訊息。 這樣可以幫助您確認 JSON 輸出是否符合給定的 JSON schema。 <br> ### 比對時,enum 大小寫不分 - ### Question 如果比對時是屬於字串大小寫不分,jsonschema 要如何設定? - ### ChatGPT-4o 使用 `jsonschema` 庫進行字串大小寫不分的比對需要進行一些額外的處理。`jsonschema` 庫本身並不直接支援這種功能,但我們可以透過自定義驗證器來實現。 以下是如何編寫自定義驗證器來忽略字串大小寫的比對: 1. 定義 JSON schema。 2. 實作自定義驗證器來處理大小寫不敏感的比對。 3. 執行比對並檢查輸出。 以下是範例程式碼: ```python= import jsonschema from jsonschema import Draft7Validator, exceptions # 定義 JSON schema schema = { "type": "object", "properties": { "functions": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "description": {"type": "string"}, "parameters": { "type": "object", "properties": { "material": {"type": "string"}, "size": {"type": "integer"}, "complexity": { "type": "string", "enum": ["low", "medium", "high"], "default": "medium" } }, "required": ["material", "size"] } }, "required": ["name", "description", "parameters"] } } }, "required": ["functions"] } # 自定義驗證器 def lowercase_enum_validator(validator, enums, instance, schema): if not validator.is_type(instance, "string"): return if instance.lower() not in (e.lower() for e in enums): yield exceptions.ValidationError(f"{instance} is not one of {enums}") # 選擇草稿版 all_validators = dict(Draft7Validator.VALIDATORS) all_validators["enum"] = lowercase_enum_validator # 自定義驗證器類 CustomValidator = jsonschema.validators.create( meta_schema=Draft7Validator.META_SCHEMA, validators=all_validators, ) # 定義要比對的 JSON 輸出 output = { "functions": [ { "name": "sculpture_price_calculate", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "material": "bronze", "size": 5, "complexity": "MEDIUM" } } ] } # 定義比對程式 def compare_json_with_schema(output, schema): try: # 執行自定義驗證 CustomValidator(schema).validate(output) print("JSON output is valid against the schema.") except jsonschema.exceptions.ValidationError as err: print(f"Validation error: {err.message}") # 執行比對 compare_json_with_schema(output, schema) ``` 這段程式碼的主要步驟如下: 1. 定義 JSON schema。 2. 創建一個自定義驗證器 `CustomValidator`,其中包括自定義的 `lowercase_enum_validator`,用來忽略枚舉字串的大小寫。 3. 使用自定義驗證器來比對輸出的 JSON,檢查是否符合 schema。 這樣,您就可以實現字串大小寫不敏感的 JSON schema 驗證。 <br> ### 比對時,const string 大小寫不分 - ### Question 如果是固定值字串 ("const": "xxx"),沒有用到 enum,比對時是屬於字串大小寫不分,jsonschema 要如何設定? - ### ChatGPT-4o 在 `jsonschema` 中,如果您需要比對固定值字串(即使用 `"const"` 屬性)且不區分大小寫,您可以通過自定義驗證器來實現。以下是如何實現這一目標的步驟: 1. 定義 JSON schema。 2. 實作自定義驗證器來處理大小寫不敏感的固定值字串比對。 3. 執行比對並檢查輸出。 以下是範例程式碼: ```python= import jsonschema from jsonschema import Draft7Validator, exceptions # 定義 JSON schema schema = { "type": "object", "properties": { "functions": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string", "const": "sculpture_price_calculate"}, "description": {"type": "string"}, "parameters": { "type": "object", "properties": { "material": {"type": "string"}, "size": {"type": "integer"}, "complexity": { "type": "string", "const": "medium" } }, "required": ["material", "size"] } }, "required": ["name", "description", "parameters"] } } }, "required": ["functions"] } # 自定義驗證器 def lowercase_const_validator(validator, const, instance, schema): if not validator.is_type(instance, "string"): return if instance.lower() != const.lower(): yield exceptions.ValidationError(f"{instance} does not match constant value {const}") # 選擇草稿版 all_validators = dict(Draft7Validator.VALIDATORS) all_validators["const"] = lowercase_const_validator # 自定義驗證器類 CustomValidator = jsonschema.validators.create( meta_schema=Draft7Validator.META_SCHEMA, validators=all_validators, ) # 定義要比對的 JSON 輸出 output = { "functions": [ { "name": "SCULPTURE_PRICE_CALCULATE", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "material": "bronze", "size": 5, "complexity": "MEDIUM" } } ] } # 定義比對程式 def compare_json_with_schema(output, schema): try: # 執行自定義驗證 CustomValidator(schema).validate(output) print("JSON output is valid against the schema.") except jsonschema.exceptions.ValidationError as err: print(f"Validation error: {err.message}") # 執行比對 compare_json_with_schema(output, schema) ``` 這段程式碼的主要步驟如下: 1. 定義 JSON schema,使用 `"const"` 屬性來指定固定值字串。 2. 創建一個自定義驗證器 `CustomValidator`,其中包括自定義的 `lowercase_const_validator`,用來忽略固定值字串的大小寫。 3. 使用自定義驗證器來比對輸出的 JSON,檢查是否符合 schema。 這樣,您就可以實現固定值字串大小寫不敏感的 JSON schema 驗證。 <br> ### 比對時,const string 有些有區分大小寫,有些沒有 - ### Question 如果有些字串是大小寫敏感,有些不是,要如何設定? - ### ChatGPT-4o 如果需要在同一個 JSON schema 中對一些字串進行大小寫敏感的比較,而對其他字串進行大小寫不敏感的比較,您可以對不同的字段使用不同的自定義驗證器。以下是如何實現這一目標的步驟: 1. 定義 JSON schema。 2. 實作自定義驗證器來處理大小寫敏感和大小寫不敏感的比對。 3. 執行比對並檢查輸出。 以下是範例程式碼: ```python= import jsonschema from jsonschema import Draft7Validator, exceptions # 定義 JSON schema schema = { "type": "object", "properties": { "functions": { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string", "const": "sculpture_price_calculate"}, "description": {"type": "string"}, "parameters": { "type": "object", "properties": { "material": {"type": "string"}, "size": {"type": "integer"}, "complexity": { "type": "string", "const": "medium" # 這裡定義為大小寫不敏感 }, "style": { "type": "string", "const": "modern" # 這裡定義為大小寫敏感 } }, "required": ["material", "size", "style"] } }, "required": ["name", "description", "parameters"] } } }, "required": ["functions"] } # 自定義驗證器 def lowercase_const_validator(validator, const, instance, schema): if not validator.is_type(instance, "string"): return if instance.lower() != const.lower(): yield exceptions.ValidationError(f"{instance} does not match constant value {const} (case insensitive)") def case_sensitive_const_validator(validator, const, instance, schema): if not validator.is_type(instance, "string"): return if instance != const: yield exceptions.ValidationError(f"{instance} does not match constant value {const} (case sensitive)") # 選擇草稿版 all_validators = dict(Draft7Validator.VALIDATORS) # 替換 const 驗證器為自定義的大小寫不敏感驗證器 all_validators["const"] = lowercase_const_validator # 創建自定義驗證器類 LowercaseConstValidator = jsonschema.validators.create( meta_schema=Draft7Validator.META_SCHEMA, validators=all_validators, ) # 創建新的驗證器字典,只替換需要大小寫敏感的字段驗證器 sensitive_validators = dict(Draft7Validator.VALIDATORS) sensitive_validators["const"] = case_sensitive_const_validator # 創建自定義驗證器類 SensitiveConstValidator = jsonschema.validators.create( meta_schema=Draft7Validator.META_SCHEMA, validators=sensitive_validators, ) # 定義要比對的 JSON 輸出 output = { "functions": [ { "name": "SCULPTURE_PRICE_CALCULATE", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "material": "bronze", "size": 5, "complexity": "MEDIUM", "style": "modern" } } ] } # 定義比對程式 def compare_json_with_schema(output, schema): try: # 檢查 functions 中的每個 item for function in output["functions"]: # 使用敏感驗證器檢查特定字段 SensitiveConstValidator(schema["properties"]["functions"]["items"]["properties"]["parameters"]["properties"]["style"]).validate(function["parameters"]["style"]) # 使用大小寫不敏感驗證器檢查其他字段 LowercaseConstValidator(schema).validate(output) print("JSON output is valid against the schema.") except jsonschema.exceptions.ValidationError as err: print(f"Validation error: {err.message}") # 執行比對 compare_json_with_schema(output, schema) ``` 這段程式碼的主要步驟如下: 1. 定義 JSON schema,並標註需要大小寫敏感和不敏感的字段。 2. 創建兩個自定義驗證器,一個用於大小寫敏感的比對,另一個用於大小寫不敏感的比對。 3. 在比對過程中,根據字段使用相應的驗證器來檢查 JSON 輸出是否符合 schema。 這樣,您就可以實現對不同字段進行大小寫敏感和不敏感的 JSON schema 驗證。 <br> ### 比對時,透過字定義屬性 `case_insensitive` ,控制是否大小寫敏感 - ### Question 沒辦法直接定義在 schema 中的屬性嗎? - ### ChatGPT-4o 可以在 schema 中直接定義哪些字段需要進行大小寫敏感或不敏感的比對。我們可以透過在 schema 中使用特定的屬性來標註這些字段,然後在自定義驗證器中根據這些標註來進行相應的驗證。 以下是如何實現這一目標的步驟: 1. 在 JSON schema 中為每個字段添加一個標註(例如 `case_insensitive`)來指示該字段是否需要大小寫不敏感的驗證。 2. 實作自定義驗證器來根據這些標註進行相應的驗證。 3. 執行比對並檢查輸出。 以下是範例程式碼: ```python= import jsonschema from jsonschema import Draft7Validator, exceptions, validators # 定義 JSON schema schema = { "type": "object", "properties": { "functions": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "const": "sculpture_price_calculate", "case_insensitive": True }, "description": {"type": "string"}, "parameters": { "type": "object", "properties": { "material": {"type": "string"}, "size": {"type": "integer"}, "complexity": { "type": "string", "const": "medium", "case_insensitive": True # 標註為大小寫不敏感 }, "style": { "type": "string", "const": "modern" # 預設為大小寫敏感 } }, "required": ["material", "size", "style"] } }, "required": ["name", "description", "parameters"] } } }, "required": ["functions"] } # 自定義驗證器 def lowercase_const_validator(validator, const, instance, schema): if not validator.is_type(instance, "string"): return if schema.get("case_insensitive", False): if instance.lower() != const.lower(): yield exceptions.ValidationError(f"{instance} does not match constant value {const} (case insensitive)") else: if instance != const: yield exceptions.ValidationError(f"{instance} does not match constant value {const} (case sensitive)") # 創建自定義驗證器字典,替換 const 驗證器為自定義的驗證器 custom_validators = dict(Draft7Validator.VALIDATORS) custom_validators["const"] = lowercase_const_validator # 創建自定義驗證器類 CustomValidator = jsonschema.validators.create( meta_schema=Draft7Validator.META_SCHEMA, validators=custom_validators, ) # 定義要比對的 JSON 輸出 output = { "functions": [ { "name": "SCULPTURE_PRICE_CALCULATE", "description": "Calculate the estimated price to commission a sculpture based on the material and size.", "parameters": { "material": "bronze", "size": 5, "complexity": "MEDIUM", "style": "modern" } } ] } # 定義比對程式 def compare_json_with_schema(output, schema): try: # 執行自定義驗證 CustomValidator(schema).validate(output) print("JSON output is valid against the schema.") except jsonschema.exceptions.ValidationError as err: print(f"Validation error: {err.message}") # 執行比對 compare_json_with_schema(output, schema) ``` 這段程式碼的主要步驟如下: 1. 在 JSON schema 中,通過添加 `case_insensitive` 屬性來標註哪些字段需要進行大小寫不敏感的驗證。 2. 創建自定義驗證器 `lowercase_const_validator`,根據 `case_insensitive` 屬性來決定是否進行大小寫不敏感的驗證。 3. 使用自定義驗證器來比對輸出的 JSON,檢查是否符合 schema。 這樣,您就可以在 schema 中直接定義哪些字段需要進行大小寫敏感或不敏感的驗證。 <br> <hr> <br> ## 討論2 ### 實作「忽略大小寫」簡易範例 - ### Question 我想比對兩邊的 json data 一個是 output dict, 另一個是 json schema 有些 const or enum 字串,在比對時想忽略大小寫,也些則是大小寫敏感 請使用 python jsonschema 套件,幫我生出簡易範例 - ### ChatGPT-4o 當然可以。下面是一個簡易的 Python 範例,展示如何使用 `jsonschema` 套件來比對兩個 JSON 並實現大小寫敏感及不敏感的比較。 首先,您需要安裝 `jsonschema` 套件。如果尚未安裝,請使用以下命令安裝: ```bash pip install jsonschema ``` 接著,我們來看範例代碼: ```python= import json from jsonschema import validate, ValidationError from jsonschema.validators import validator_for import re # 定義 JSON schema schema = { "type": "object", "properties": { "name": {"type": "string", "enum": ["Alice", "Bob"]}, # 大小寫敏感 "city": {"type": "string", "const": "Taipei"} # 大小寫不敏感 }, "required": ["name", "city"] } # 定義 JSON data data = { "name": "alice", "city": "taipei" } # 自定義檢驗器,處理大小寫不敏感的檢查 class CustomValidator(validator_for(schema)): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.VALIDATORS.update({ "const": self.validate_const_case_insensitive, "enum": self.validate_enum_case_sensitive }) def validate_const_case_insensitive(self, const, instance, schema): if isinstance(instance, str) and isinstance(const, str): if instance.lower() != const.lower(): yield ValidationError(f"{instance} does not match const value {const} (case insensitive)") else: if instance != const: yield ValidationError(f"{instance} does not match const value {const}") def validate_enum_case_sensitive(self, enums, instance, schema): if instance not in enums: yield ValidationError(f"{instance} is not one of {enums}") # 驗證 JSON data validator = CustomValidator(schema) try: validator.validate(data) print("JSON data is valid according to the schema.") except ValidationError as e: print(f"JSON data is invalid: {e.message}") # 顯示驗證結果 print(json.dumps(data, indent=2)) print(json.dumps(schema, indent=2)) ``` 這個範例展示了如何使用自定義的檢驗器來處理大小寫敏感及不敏感的比較: 1. `validate_const_case_insensitive` 方法用來檢查 `const` 字串,忽略大小寫。 2. `validate_enum_case_sensitive` 方法用來檢查 `enum` 字串,大小寫敏感。 希望這個範例對您有幫助!如果有其他需求或問題,請隨時告訴我。 :::warning :warning: **DeprecationWarning**: Subclassing validator classes is not intended to be part of their public API. A future version will make doing so an error, as the behavior of subclasses isn't guaranteed to stay the same between releases of jsonschema. Instead, prefer composition of validators, wrapping them in an object owned entirely by the downstream library. class CustomValidator(validator_for(schema)): ::: <br> ### 避免 DeprecationWarning 的新作法 - ### Question **DeprecationWarning**: Subclassing validator classes is not intended to be part of their public API. A future version will make doing so an error, as the behavior of subclasses isn't guaranteed to stay the same between releases of jsonschema. Instead, prefer composition of validators, wrapping them in an object owned entirely by the downstream library. class CustomValidator(validator_for(schema)): - ### ChatGPT-4o 為了避免將來可能發生的錯誤,使用組合而不是繼承來實作自訂驗證器是一種更好的方法。以下是如何使用組合方式來實作自訂驗證器,以便處理大小寫敏感和不敏感的比較: ```Python= import json from jsonschema import Draft7Validator, ValidationError # 定义 JSON schema schema = { "type": "object", "properties": { "name": {"type": "string", "enum": ["Alice", "Bob"]}, # 大小寫敏感 "city": {"type": "string", "const": "Taipei"} # 大小寫不敏感 }, "required": ["name", "city"] } # 定义 JSON data data = { "name": "alice", "city": "taipei" } # 自定义验证函数 def validate_const_case_insensitive(validator, const, instance, schema): if isinstance(instance, str) and isinstance(const, str): if instance.lower() != const.lower(): yield ValidationError(f"{instance} does not match const value {const} (case insensitive)") else: if instance != const: yield ValidationError(f"{instance} does not match const value {const}") def validate_enum_case_sensitive(validator, enums, instance, schema): if instance not in enums: yield ValidationError(f"{instance} is not one of {enums}") # 创建组合验证器 class CustomValidator: def __init__(self, schema): self.validator = Draft7Validator(schema) self.VALIDATORS = { "const": validate_const_case_insensitive, "enum": validate_enum_case_sensitive } def validate(self, data): errors = list(self.validator.iter_errors(data)) for error in errors: yield error for error in self.custom_validate(data, schema): yield error def custom_validate(self, data, schema): for prop, subschema in schema.get("properties", {}).items(): value = data.get(prop) for keyword, validator in self.VALIDATORS.items(): if keyword in subschema: yield from validator(self, subschema[keyword], value, schema) # 验证 JSON data custom_validator = CustomValidator(schema) errors = sorted(custom_validator.validate(data), key=lambda e: e.path) if not errors: print("JSON data is valid according to the schema.") else: for error in errors: print(f"JSON data is invalid: {error.message}") # 显示验证结果 print(json.dumps(data, indent=2)) print(json.dumps(schema, indent=2)) ``` 在這個範例中,我們使用組合方法建立了一個 `CustomValidator` 類,它包含了一個標準的 `Draft7Validator` 實例。我們定義了兩個自訂驗證函數 `validate_const_case_insensitive` 和`validate_enum_case_sensitive`,並在`custom_validate`方法中呼叫這些函數。這樣可以確保自訂驗證邏輯在標準驗證邏輯之後執行。 透過這種方式,我們避免了繼承驗證器類別的潛在問題,同時仍能實作自訂的驗證邏輯。