import jsonschema
import json
from jsonschema import validate # JSON Schema vs JSON Data 검증해주는 라이브러리
!pip install genson
from genson import SchemaBuilder # JSON Schema를 보다 쉽게 만들 수 있게 해주는 라이브러리
Directory에
example_1.json
파일이 있다고하면...
with open("example_1.json", "r") as json_file:
JSON = json.load(json_file):
print(JSON)
type(JSON)
>>>
{'fruit': 'Apple', 'size': 'Large', 'color': 'Red'}
dict
[!] 다른 글에 JSON file 읽고 쓰는법을 정리하자 JSON Read & Write
[!] 여기서 키포인트는 JSON data file을 위와 같은 코드로 읽으면 (json.load(json_file)
) Dictionary 타입으로 불러와주는 것임
JSON은 그냥 단순 텍스트임, 어느 프레임워크에서도 쓰일 수 있는...
json.load와 같은 함수의 도움없이 썡 JSON파일은 Key값으로 접근이 안됨
따라서 딕셔너리 형태로 변환이 필요함(?)
눈에 확 들어오지는 않겠지만 위와 같은 구조와 조건으로 JSON파일이 존재해야된다고 생각하여
구조와 조건에 부합하는 JSON Data x1개
구조와 조건에 부합하지 않는 JSON Data x1개를 만들어보자!
Sample JSON Data 1
j_data_1 = {
"filename" : "PANO_KR_009_fei6298fjwy2bz.png",
"file_index" : 741,
"file_date" : 20220101,
"file_type" : 'PNG',
"Annotation" : [
{ 'x' : 521, 'y' : 215 },
{ 'x' : 115, 'y' : 988 }
],
"ID" : 24
}
Sample JSON Data 2
j_data_2 = {
"filename" : "PANO_KR_001_fei6298fjwy2bz.png",
"file_index" : 741,
"file_date" : 99999999, # 범위초과
"file_type" : 'TXT', # 열거된 리스트에 없음
"Annotation" : [
{ 'x' : 521, 'y' : 9999 }, # y 범위초과
{ 'x' : 9999, 'y' : 988 } # x 범위초과
],
"ID" : 2, # 3 or 4의 배수가 아님
"Shit" : "zzzzzzzz" # 불필요한 프로퍼티
}
자 이제 JSON Schema를 만들어보자!
첫번째로는genson.SchemaBuilder
없이 만들어볼것임
그냥 Dictionary처럼 만들어주면 된다.
하지만 검증할 JSON data의 구조가 깊고 복잡할수록ㄹㅇ어려워짐
j_schema_1 = {
"title" : "SCHEMA_1",
"version" : 0,
"type" : "object",
"properties" : {
"filename" : {"type" : "string" },
"file_index" : {"type" : "integer"},
"file_date" : {"type" : "integer",
"minimum" : 0,
"maximum" : 20221231},
"file_type" : {"type" : "string",
"enum" : ['PNG', 'JPG']},
"Annotation" : {"type" : "array",
"items" : {"type" : "object",
"properties" : {
"x" : {"type" : "integer",
"minimum" : 0,
"maximum" : image_width},
"y" : {"type" : "integer",
"minimum" : 0,
"maximum" : image_height},
}
}
},
"ID" : {
"anyOf" : [
{"type" : "number", "multipleOf" : 3},
{"type" : "number", "multipleOf" : 4}
]
}
},
"required" : ["filename",
"file_index",
"file_date",
"file_type",
"Annotation",
"ID"]
}
# 외부 변수
image_width = 1024
image_height = 768
[!] "title"
, "version"
은 해당 schema에 대한 설명 정도이므로 생략도 가능하다(?)
[!] "enum" : []
, "anyOf : []"
등은 중요하므로 기억!
[+] Tips : 각각의 schema는 아래와 같은 꼴을 한다는 것을 기억하자.
{"type" : "object", "properties" : {...} }
{"type" : "array", "items" : {...} }
{"type" : "string" , 조건1, 조건2, ... 조건n}
두번째로는
genson.SchemaBuilder
를 통해서 만들어볼것임
genson.SchemaBuilder
의 핵심기능은 아래와 같다.
- 하나의 Sample JSON Data로 JSON Schema를 생성할 수 있다.
- 스키마의 접근 및 수정이 용이
따라서 위에서 만든
j_data_1
JSON Data를 통해서 Schema를 생성하고
수정을 통해서 Schema를 완성시켜보자.
# !pip install genson
# from genson import SchemaBuilder
SB = SchemaBuilder()
SB.add_object(j_data_1)
j_schema_2 = SB.to_schema()
j_schema_2
>>>
{'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {'filename': {'type': 'string'},
'file_index': {'type': 'integer'},
'file_date': {'type': 'integer'},
'file_type': {'type': 'string'},
'Annotation': {'type': 'array',
'items': {'type': 'object',
'properties': {'x': {'type': 'integer'}, 'y': {'type': 'integer'}},
'required': ['x', 'y']}},
'ID': {'type': 'integer'}},
'required': ['Annotation',
'ID',
'file_date',
'file_index',
'file_type',
'filename']}
------------------------------
type(j_schema_2)
>>>
dict
호오... SchemaBuilder
에게 넘겨준 j_data_1
을 토대로 JSON Schema의 구조와 약간의 조건들, 즉 뼈대를 간단하게 만들어줌. 당연히 상세 조건들을 없을 수 밖에 없겠지?
여러개의 JSON file을 검증하려할때, 하나의 JSON file(멀쩡한놈!)을 SchemaBuilder
에 넣어서 스키마를 만들고 수정 및 조건 삽입으로 완성시키면 됨
Sample JSON Data=j_data_1
와 SchemaBuilder
로 만들었던 JSON Schema=j_schema_2
를 수정하기 전에 어떻게 접근,수정,삽입하는지 간단하게 배워보자!
[!] How to access : 아래의 JSON Schema에 접근하여 수정하는 방법은 아래와 같다.
j_schema_2
>>>
{'$schema': 'http://json-schema.org/schema#',
'type': 'object',
'properties': {'filename': {'type': 'string'},
'file_index': {'type': 'integer'},
'file_date': {'type': 'integer'},
'file_type': {'type': 'string'},
'Annotation': {'type': 'array',
'items': {'type': 'object',
'properties': {'x': {'type': 'integer'}, 'y': {'type': 'integer'}},
'required': ['x', 'y']}},
'ID': {'type': 'integer'}},
'required': ['Annotation',
'ID',
'file_date',
'file_index',
'file_type',
'filename']}
스키마 Annotation
속성의 item
중 required
스키마에 접근해서 없애고싶음...
j_schema_2["properties"]["Annotation"]{"items"]["required"]
>>>
['x', 'y']
[""]
를 통해서 매우 직관적(?)으로 접근할 수 있는 것을 볼 수 있다.
j_schema_2["properties"]["Annotation"]["items"]["required"].remove('x')
j_schema_2["properties"]["Annotation"]["items"]["required"].remove('y')
j_schema_2["properties"]["Annotation"]["items"]["required"]
>>>
[]
위와 같이 remove
를 통해 없앨 수 있다.
[?] ["required"]
를 통으로 없앨 수 있는 방법은 없을까?
자 이제 본격적으로 수정을 해보자!
j_schema_2["properties"]["file_date"]["minimum"] = 0
j_schema_2["properties"]["file_date"]["maximum"] = 20221231
j_schema_2["properties"]["file_type"]["enum"] = ['PNG', 'JPG']
j_schema_2["properties"]["Annotation"]["items"]["properties"]["x"]["minimum"] = 0
j_schema_2["properties"]["Annotation"]["items"]["properties"]["x"]["maximum"] = image_width
j_schema_2["properties"]["Annotation"]["items"]["properties"]["y"]["minimum"] = 0
j_schema_2["properties"]["Annotation"]["items"]["properties"]["y"]["maximum"] = image_height
j_schema_2["properties"]["ID"]["anyOf"] = [ {"type":"integer", "multipleOf" : 3},
{"type":"integer", "multipleOf" : 4} ]
뭐 이런식으로 조건 삽입, 수정을 하면 된다.
이전에 작성한 JSON-2 글이나 인터넷을 찾아보면 더 다양한 schema 조건이 있으므로 잘 사용해보자. ex) 정규표현식, min/maxProperties, additionalItems, uniqueItems 등
JSON Schema만 있다면 검증(Validation)은 매우 쉽다. 당연히 JSON Data도 있어야함
# import jsonschema
# from jsonschema import validate
validate(schema = j_shcema_1, instance = j_data_1)
>>>
j_data_1
은 문제없는 JSON Data이므로 검증 시 아무일도 없다 (return값이 없음)
validate(schema = j_schema_2, instance = j_data_2)
>>>
ValidationError: 99999999 is greater than the maximum of 20221231
Failed validating 'maximum' in schema['properties']['file_date']:
{'maximum': 20221231, 'minimum': 0, 'type': 'integer'}
On instance['file_date']:
99999999
j_schema_1
과 j_schema_2
는 뭐 거의 같다고 보면 되고,
j_data_2
는 스키마에 부합하지 않는 데이터이므로 위와 같이 기분나쁜 Error가 발생한다.
그런데 이와 같이 에러가 발생하는 구조면 여러개의 JSON Data file을 검증할때에는 걸리적거림.
jsonschema.Draft7Validator
를 사용해보자.
validator = jsonschema.Draft7Validator(j_schema_1)
validator
에 iter_errors()
를 통해 JSON Data를 넣어준다.validator.iter_errors(j_data_2)
>>>
<generator object create.<locals>.Validator.iter_errors at 0x0000018FB9E1CB30>
[+] 아까 만든 j_data_2
의 경우 스키마에 부합하지 않는 부분이 한두군데가 아니였다. 따라서 에러가 여러개 뜰 수 밖에 없는데 이를 generator 형식으로 나타내준다.
errors = validator.iter_errors(j_data_2)
for e in errors:
print(e)
>>>
99999999 is greater than the maximum of 20221231
Failed validating 'maximum' in schema['properties']['file_date']:
{'maximum': 20221231, 'minimum': 0, 'type': 'integer'}
On instance['file_date']:
99999999
'TXT' is not one of ['PNG', 'JPG']
Failed validating 'enum' in schema['properties']['file_type']:
{'enum': ['PNG', 'JPG'], 'type': 'string'}
On instance['file_type']:
'TXT'
9999 is greater than the maximum of 768
Failed validating 'maximum' in schema['properties']['Annotation']['items']['properties']['y']:
{'maximum': 768, 'minimum': 0, 'type': 'integer'}
On instance['Annotation'][0]['y']:
9999
9999 is greater than the maximum of 1024
Failed validating 'maximum' in schema['properties']['Annotation']['items']['properties']['x']:
{'maximum': 1024, 'minimum': 0, 'type': 'integer'}
On instance['Annotation'][1]['x']:
9999
988 is greater than the maximum of 768
Failed validating 'maximum' in schema['properties']['Annotation']['items']['properties']['y']:
{'maximum': 768, 'minimum': 0, 'type': 'integer'}
On instance['Annotation'][1]['y']:
988
2 is not valid under any of the given schemas
Failed validating 'anyOf' in schema['properties']['ID']:
{'anyOf': [{'multipleOf': 3, 'type': 'number'},
{'multipleOf': 4, 'type': 'number'}]}
On instance['ID']:
2
하나하나 살펴보자...아니 하나만 살펴보면 아래와 같은 구조의 Error를 return해줌을 알 수 있다.
# 왜 검증 에러떴는지 이유 보여줌
99999999 is greater than the maximum of 20221231
# 스키마의 명시된 조건 보여줌
Failed validating 'maximum' in schema['properties']['file_date']:
{'maximum': 20221231, 'minimum': 0, 'type': 'integer'}
# 인스턴스, 즉 데이터의 값(스키마 조건에 부합하지 않는...) 보여줌
On instance['file_date']:
99999999