jsonschema를 이용한 JSON Validation Example_1

olxtar·2022년 4월 28일
0

JSON Study

목록 보기
2/3
post-thumbnail

00. Settings


0. Import


import jsonschema
import json
from jsonschema import validate   # JSON Schema vs JSON Data 검증해주는 라이브러리

!pip install genson

from genson import SchemaBuilder  # JSON Schema를 보다 쉽게 만들 수 있게 해주는 라이브러리



1. How to Read JSON Data

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 읽고 쓰는법을 정리하자 \rightarrow JSON Read & Write
[!] 여기서 키포인트는 JSON data file을 위와 같은 코드로 읽으면 (json.load(json_file)) Dictionary 타입으로 불러와주는 것임

JSON은 그냥 단순 텍스트임, 어느 프레임워크에서도 쓰일 수 있는...
json.load와 같은 함수의 도움없이 썡 JSON파일은 Key값으로 접근이 안됨
따라서 딕셔너리 형태로 변환이 필요함(?)





01. How to make JSON Schema


0. JSON Data for JSON Schema


  1. 파일이름 : 문자열 #타입 체크
  2. 파일인덱스 : 숫자(정수형) #타입 체크
  3. 파일작성날짜 : 숫자(정수형) #타입 체크 및 최대최소값 체크
    3-1. 0~20221231
  4. 파일타입 : 문자열 #타입 체크 및 열거형으로 체크
    4-1. PNG or JPG
  5. 어노테이션 : 리스트 #타입 체크 및 최대최소값 체크
    5-1. 객체
    5-1-1. X : 숫자(정수형) 0~외부변수1
    5-1-2. Y : 숫자(정수형) 0~외부변수2
  6. 고유번호 #타입 체크 및 Schema 결합 체크
    6-1. 3 또는 4의 배수
  7. 전체조건 : 위 6개의 프로퍼티가 꼭 있어야함

눈에 확 들어오지는 않겠지만 위와 같은 구조와 조건으로 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"    # 불필요한 프로퍼티
            }



1. Without genson.SchemaBuilder

자 이제 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는 아래와 같은 꼴을 한다는 것을 기억하자.

  • Object : {"type" : "object", "properties" : {...} }
  • Array : {"type" : "array", "items" : {...} }
  • Else : {"type" : "string" , 조건1, 조건2, ... 조건n}




2. With genson.SchemaBuilder

두번째로는 genson.SchemaBuilder를 통해서 만들어볼것임

genson.SchemaBuilder의 핵심기능은 아래와 같다.

  1. 하나의 Sample JSON Data로 JSON Schema를 생성할 수 있다.
  2. 스키마의 접근 및 수정이 용이

따라서 위에서 만든 j_data_1 JSON Data를 통해서 Schema를 생성하고
수정을 통해서 Schema를 완성시켜보자.


2-1. Building JSON Schema from Sample JSON data

# !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의 구조와 약간의 조건들, 즉 뼈대를 간단하게 만들어줌. 당연히 상세 조건들을 없을 수 밖에 없겠지?

\therefore 여러개의 JSON file을 검증하려할때, 하나의 JSON file(멀쩡한놈!)SchemaBuilder에 넣어서 스키마를 만들고 수정 및 조건 삽입으로 완성시키면 됨



2-2. Modifying JSON Schema


Sample JSON Data=j_data_1SchemaBuilder로 만들었던 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']}

\uparrow 스키마 Annotation 속성의 itemrequired 스키마에 접근해서 없애고싶음...




j_schema_2["properties"]["Annotation"]{"items"]["required"]

>>>
['x', 'y']

\uparrow [""]를 통해서 매우 직관적(?)으로 접근할 수 있는 것을 볼 수 있다.




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"]

>>>
[]

\uparrow 위와 같이 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} ]

\uparrow 뭐 이런식으로 조건 삽입, 수정을 하면 된다.

이전에 작성한 JSON-2 글이나 인터넷을 찾아보면 더 다양한 schema 조건이 있으므로 잘 사용해보자. ex) 정규표현식, min/maxProperties, additionalItems, uniqueItems 등





02. How to Validate

JSON Schema만 있다면 검증(Validation)은 매우 쉽다. 당연히 JSON Data도 있어야함



1. Validate

# 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_1j_schema_2는 뭐 거의 같다고 보면 되고,

j_data_2는 스키마에 부합하지 않는 데이터이므로 위와 같이 기분나쁜 Error가 발생한다.

그런데 이와 같이 에러가 발생하는 구조면 여러개의 JSON Data file을 검증할때에는 걸리적거림.

\therefore jsonschema.Draft7Validator를 사용해보자.



2. Validator

  1. 아래와 같이 Validator에 스키마를 넣어준다.
validator = jsonschema.Draft7Validator(j_schema_1)

  1. 선언한 validatoriter_errors()를 통해 JSON Data를 넣어준다.
validator.iter_errors(j_data_2)

>>>
<generator object create.<locals>.Validator.iter_errors at 0x0000018FB9E1CB30>

[+] 아까 만든 j_data_2의 경우 스키마에 부합하지 않는 부분이 한두군데가 아니였다. 따라서 에러가 여러개 뜰 수 밖에 없는데 이를 generator 형식으로 나타내준다.


  1. generator를 통해서 Error를 모두 뽑아본다.
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
profile
예술과 기술

0개의 댓글