├── requirements.txt ├── user.json ├── README.md ├── user-schema.json ├── gen_jsondata.py ├── user_validate.py ├── JSON-API.md └── JSON-Schema.md /requirements.txt: -------------------------------------------------------------------------------- 1 | genson==0.2.0 2 | jsonschema==2.5.1 3 | apitools==0.1.4 4 | rstr==2.2.5 -------------------------------------------------------------------------------- /user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "홍길동", 3 | "cellphone": "010-1345-7764", 4 | "address": "​이상국 행복리 234", 5 | "age": 33 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [JSON 스키마](JSON-Schema.md) 2 | JSON 스키마 스펙을 번역했습니다. 3 | 4 | # [JSON과 PYTHON API의 만남](JSON-API.md) 5 | JSON 스키마를 이용하여 파이썬 API 에 이용한 것에 대한 고찰입니다. 6 | 7 | 8 | 항상 [지훈현서블로그](http://mcchae.egloos.com/)의 마지막 내용에 넣는 내용이지만 제발 개발자 분들이 시행착오를 줄이고 더 생산적이고 더 좋은 일에 시간을 쓰셨으면 하는 바램입니다. 9 | 10 | 어느 분께는 도움이 되셨기를 바라며... 11 | 12 | [채문창](https://www.facebook.com/mcchae) 13 | -------------------------------------------------------------------------------- /user-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "cellphone": { 5 | "type": "string", 6 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 7 | }, 8 | "age": { 9 | "type": "integer", 10 | "minimum": 0, 11 | "maximum": 150 12 | }, 13 | "name": { 14 | "type": "string" 15 | }, 16 | "address": { 17 | "type": "string", 18 | "maxLength": 50 19 | } 20 | }, 21 | "required": [ 22 | "age", 23 | "name" 24 | ] 25 | } -------------------------------------------------------------------------------- /gen_jsondata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import sys 5 | from apitools.datagenerator import DataGenerator 6 | from jsonschema import validate 7 | from jsonschema.exceptions import ValidationError 8 | 9 | generator = DataGenerator() 10 | 11 | user_schema = { 12 | "type": "object", 13 | "properties": { 14 | "cellphone": { 15 | "type": "string", 16 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 17 | }, 18 | "age": { 19 | "type": "integer", 20 | "required": True, 21 | "minimum": 0, 22 | "maximum": 150 23 | }, 24 | "name": { 25 | "type": "string", 26 | "required": True 27 | }, 28 | "address": { 29 | "type": "string", 30 | "maxLength": 50 31 | } 32 | } 33 | } 34 | 35 | while True: 36 | r = generator.random_value(user_schema) 37 | try: 38 | validate(r, user_schema) 39 | break 40 | except ValidationError as ve: 41 | sys.stderr.write(str(ve) + "\n") 42 | print(r) -------------------------------------------------------------------------------- /user_validate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import sys 5 | from jsonschema import validate 6 | from jsonschema.exceptions import ValidationError 7 | 8 | schema = { 9 | "type": "object", 10 | "properties": { 11 | "cellphone": { 12 | "type": "string", 13 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 14 | }, 15 | "age": { 16 | "type": "integer", 17 | "minimum": 0, 18 | "maximum": 150 19 | }, 20 | "name": { 21 | "type": "string" 22 | }, 23 | "address": { 24 | "type": "string", 25 | "maxLength": 50 26 | } 27 | }, 28 | "required": [ 29 | "age", 30 | "name" 31 | ] 32 | } 33 | 34 | data = [ 35 | { 36 | "name": "홍길동", 37 | "cellphone": "010-1345-7764", 38 | "address": "​이상국 행복리 234", 39 | "age": 33 40 | }, 41 | { 42 | "name": "홍길동", 43 | "age": 33 44 | }, 45 | { 46 | "name": "홍길동", 47 | "address": "​이상국 행복리 234", 48 | }, 49 | { 50 | "name": "홍길동", 51 | "cellphone": "012-1345-7764", 52 | "age": 33 53 | } 54 | ] 55 | 56 | print("Validating the input data using jsonschema:") 57 | for idx, item in enumerate(data): 58 | try: 59 | validate(item, schema) 60 | sys.stdout.write("Record #{}: OK\n".format(idx)) 61 | except ValidationError as ve: 62 | sys.stderr.write("Record #{}: ERROR\n".format(idx)) 63 | sys.stderr.write(str(ve) + "\n") 64 | -------------------------------------------------------------------------------- /JSON-API.md: -------------------------------------------------------------------------------- 1 | # JSON과 PYTHON API의 만남 2 | 3 | 이 세상 어떤 발명물이던지 새롭게 하루아침에 없었던 것이 갑자기 만들어 지는 것은 없습니다. 모두 이전의 생각과 사상이 다음 아이디어에 영향을 미치고 전달되고 하고 더 위대한 무언가가 만들어지기 마련입니다. 마찬가지로 어떤 기술이던지 새롭게 하루아침에 태어난 것은 없습니다. 4 | 5 | [JSON (JavaScript Object Notation)](https://ko.wikipedia.org/wiki/JSON)은 현재 어떤 자료 전송에 있어 거의 표준이 되어 버렸습니다. 한때 XML이 그랬었고 그 이전에도 자료의 표준화 등의 시도는 있어왔죠. 6 | 7 | 그럼 왜 JavaScript 일까요? 저는 굳이 JavaScript로 Full-Stack 프로그램을 하려하지 않습니다만 Front-End에서는 JavaScript 말고는 더 다른 것을 표현할 길이 없을 만큼이나 그 스스로 표준 개발 언어가 되어 버렸습니다. 8 | 9 | Back-End 에서는 nodejs와 같은 JavaScript를 이용한 개발도 있을 수 있고, Java를 이용할 수도, 또는 PHP 등을 이용할 수도 있지만 저는 파이썬 이라는 언어를 제일 좋아하고 사랑합니다. (지금껏 열가지 이상의 프로그래밍 언어를 했었던 것 같은데 이렇게 사랑한다 라고 고백한 것은 파이썬이 처음 인듯 합니다) 10 | 11 | 이런 점에서 파이썬 역시 JSON 과 궁합이 잘 맞습니다. 1:1 이라고는 할 수 없지만 파이썬의 dict 라는 자료형과 JSON은 거의 동등하게 생각해도 좋을 만큼 서로 궁합이 맞습니다. 그 외에 NoSQL 중에 하나인 mongoDB 역시 [BSON (Binary JSON)](https://en.wikipedia.org/wiki/BSON) 이라고 하여 JSON과 거의 동일한 자료가 저장소까지 그대로 갔다가 다시 Back-End 파이썬을 거쳐 Front-End 까지 그대로 자료 왔다리 갔다리 할 수 있는 장점이 있습니다. 12 | 13 | 이를 RDB에 있었던 Schema 라는 개념이 NoSQL로 오면서 Schema-less 라는 것이 부곽되었죠. 즉 자유롭게 중첩가능한 '키-값' 및 어레이와 같은 어떤 데이터가 있고 필요시 어떤 값도 새롭게 넣고 뺄 수 있는 등의 자유도는 기존 RDB에 연결하여 작업하던 개발자에게는 엄청난 잇점 처럼 보이고 이를 행복하게 생각해 왔습니다. 14 | 15 | *그런데 말입니다*, 다시한번 API 라는 다른 관점에서 바라보기 바랍니다. 16 | 다시 처음으로 돌아가서 API 라는 것은 호출 가능한 함수에서 출발하고 함수는 패러미터와 그 결과값을 리턴합니다. 이런 함수가 원격 함수로 발전하면서 RPC 라는 것이 나왔고 (ONC RPC, DEC RPC 등이 있었고 각자 NFS, DCOM 등이 파생되었죠) XML이 자료형으로 유명할 때부터 XML-RPC라는 원격 API 호출이 있었습니다. 그러던 것이 HTTP를 통해 API를 전달하고 결과를 받는 곳에서 WebService를 거쳐 SOAP이 대세가 되었다가 이제는 RestfulAPI가 대세처럼 보입니다. 17 | 18 | 어찌되었던 간에, 19 | 20 | ``` python 21 | ret = func(param1, param2, param3) 22 | ``` 23 | 과 같은 API 가 있다고 하면 그것을 JSON 으로 표현해 보면 다음과 유사할 수 있습니다. 24 | 25 | 우선 패러미터는 26 | 27 | ``` json 28 | { 29 | "param1": 1, 30 | "param2": "abc", 31 | "param3": [1, 3, { "a":333 }] 32 | } 33 | ``` 34 | 과 같이 표현될 수 있고, 35 | 36 | 결과는 37 | 38 | ```json 39 | { 40 | "ok": true, 41 | "result": 0 42 | } 43 | ``` 44 | 45 | 과 같이 나타낼 수 있습니다. 46 | 47 | 다시한번 *그런데 말입니다*, API 라는 관점에서 위와 같이 패러미터나 결과의 return 값 (Scalar 값이 아닌 JSON 객체와 같은 구조체가 리턴될 수도 있습니다) 을 바라봅시다. 48 | 49 | `사용자` 라는 데이터는 50 | 51 | ``` json 52 | { 53 | "name" : "홍길동", 54 | "cellphone": "010-1345-7764", 55 | "address": "이상국 행복리 234", 56 | "age": 33 57 | } 58 | ``` 59 | 와 같이 표현된다고 하였을 때, 이 사용자를 시스템에 저장하는 add_user 라는 함수를 아래와 같이 표현할 수 있습니다. 60 | 61 | ```python 62 | param_json = { 63 | "name" : "홍길동", 64 | "cellphone": "010-1345-7764", 65 | "address": "이상국 행복리 234", 66 | "age": 33 67 | } 68 | 69 | ret_json = add_user(param_Json) 70 | ``` 71 | 72 | 세번째 *그런데 말입니다*. 위와 같이 패러미터가 API로 전달된다고 할 때 그 자료형이 무조건 자유롭다... 라고 생각하면 힘들어지는 부분이 발생합니다. JSON이 어떤 구조의 자료도 모두 표현된다고 하고 하더라도 사용자는 이렇게 표현된다... 라는 것이 없으면 이 API를 처리하는 부분에서는 그 값의 정당성 등을 제대로 파악할 수 없게 됩니다. 말하고 싶은 것은 저장소에 저장할 때 RDB에 스키마를 저장하듯이 결국 이런 API를 작성할 때는 어떤 자료의 형식을 갖추어 어떤 값이 오고 그 값의 유효성은 어떠하며 어떻게 해야 정당하다는 데이터 스키마를 지정해야만 합니다. (경우에 따라서는 그 결과를 받는 부분에서는 또 그 유효성을 검증해야 합니다) 73 | 74 | 약간 다른 논점에서 과거에 모놀리틱 구조의 프로그램에서는 제일 먼저 어떤 형식의 데이터를 정의하는 것이 제일 중요했습니다. 그러나 현재와 같이 [마이크로서비스 아키텍쳐](http://guruble.com/?p=951)와 같은 개념의 소프트웨어에서는 어디에 어떻게 저장되는 관점 보다는 어디에서 어떤 API 들이 있고 이를 어떻게 사용하여 원하는 서비스를 하는가가 주안점이 되고 있습니다. 75 | 76 | 따라서 오늘의 결론은 77 | 78 | "[JSON 스키마](JSON-Schema.md)"를 이용하여 API의 패러미터에 들어갈 데이터 형식과 결과의 형식을 지정하고 API 내부에서는 해당 자료가 올바른지 JSON 스키마를 통해 검증을 하며 해당 JSON 자료를 가지고 처리하다가 결과의 JSON에 값을 넣으면 그 결과를 받아 처리하는 부분 역시 (Client-Side JavaScript 일 수도 있고, 아니면 RestfulAPI를 호출하는 외부 API 일 수도 있습니다) 그 결과가 올바른 형식인지 알 필요가 있습니다. 79 | 80 | UI에서 조금 더 생각을 확장하면 JSON 스키마를 이용하여 해당 자료가 입력 폼은 어떻게 구성된다든지 또한 데이터 그리드는 어떤 형식인지, 또 검색할 항목은 어떤 것들이 있는 지 등등의 것들을 충분히 자동화할 수 있습니다. (데이터 그리드에는 표시하지 말라는 등의 부가 옵션 등이 있을 수 있겠네요.) 실지로 과거 2009년 정도에는 C#으로 유사하게 자동화를 부분 적용한 적도 있습니다. 81 | 82 | 그러면 오늘의 본론인 어떻게 하면 파이썬 API에서 JSON 스키마와 JSON 데이터를 처리할 것인가를 살펴보겠습니다. 83 | 84 | ## 1 테스트에 필요한 패키지 설치 85 | 86 | 우선 아래에 하나씩 설명을 하겠지만, 87 | 다음과 같은 패키지를 우선 설치합시다. 88 | 89 | > requirements.txt 라는 파일을 다음과 같이 만듭니다. 90 | 91 | ``` txt 92 | genson==0.2.0 93 | jsonschema==2.5.1 94 | apitools==0.1.4 95 | rstr==2.2.5 96 | ``` 97 | 98 | > pip install -r requirements.txt 99 | 100 | ## 2 JSON 스키마 생성 101 | 102 | [JSON 스키마](JSON-Schema.md)에 정의된 것처럼 만들기란 쉽지 않습니다. 그런데 거꾸로 생각해 봅시다. 위에 기술했던, 103 | 104 | ```python 105 | param_json = { 106 | "name" : "홍길동", 107 | "cellphone": "010-1345-7764", 108 | "address": "​이상국 행복리 234", 109 | "age": 33 110 | } 111 | ``` 112 | 과 같이 저런 데이터가 패러미터로 들어가겠구나... 하는 것은 어렵지 않게 생성할 수 있습니다. 113 | 114 | 그러면 머리 좋은 누군가는 115 | 116 | > 아하! 그럼 JSON 데이터를 넣고 JSON 스키마를 자동으로 맹글어 (만들어의 사투리 입니다 ^^) 주는 무언가가 있지 않을까? 117 | > 118 | > 구글 신께 여쭈어 봐야지~~~ 119 | 120 | 그래서 찾은 [GenSON](https://github.com/wolverdude/genson/) 되시겠습니다. 뭐 다들 최신 Draft 4을 지원한다고들 합니다. 121 | 122 | 설치는 123 | 124 | > pip install genson # VirtualEnv 인 경우 125 | > 126 | > sudo pip install genson 127 | 128 | ``` bash 129 | $ genson --help 130 | usage: genson [-h] [-a] [-d DELIM] [-i SPACES] [-s SCHEMA] ... 131 | 132 | Generate one, unified JSON Schema from one or more JSON objects and/or JSON 133 | Schemas. (uses Draft 4 - http://json-schema.org/draft-04/schema) 134 | 135 | positional arguments: 136 | object files containing JSON objects (defaults to stdin if no 137 | arguments are passed and the -s option is not present) 138 | 139 | optional arguments: 140 | -h, --help show this help message and exit 141 | -a, --no-merge-arrays 142 | generate a different subschema for each element in an 143 | array rather than merging them all into one 144 | -d DELIM, --delimiter DELIM 145 | set a delimiter - Use this option if the input files 146 | contain multiple JSON objects/schemas. You can pass 147 | any string. A few cases ('newline', 'tab', 'space') 148 | will get converted to a whitespace character, and if 149 | empty string ('') is passed, the parser will try to 150 | auto-detect where the boundary is. 151 | -i SPACES, --indent SPACES 152 | pretty-print the output, indenting SPACES spaces 153 | -s SCHEMA, --schema SCHEMA 154 | file containing a JSON Schema (can be specified 155 | mutliple times to merge schemas) 156 | ``` 157 | 158 | 라고 도움말이 나오는데, 159 | 160 | ```bash 161 | $ cat user.json 162 | { 163 | "name" : "홍길동", 164 | "cellphone": "010-1345-7764", 165 | "address": "이상국 행복리 234", 166 | "age": 33 167 | } 168 | ``` 169 | 라고 파일이 있으면 170 | 171 | 이를 genson으로 172 | 173 | ```bash 174 | $ genson user.json 175 | {"required": ["address", "age", "cellphone", "name"], "type": "object", "properties": {"cellphone": {"type": "string"}, "age": {"type": "integer"}, "name": {"type": "string"}, "address": {"type": "string"}}} 176 | ``` 177 | 178 | 라고 스키마가 나옵니다. 179 | 180 | 그런데 이쁘게 (pprint) 출력하려면 `-i 4` 라고 옵션을 추가해봅니다. 181 | 182 | ```bash 183 | $ genson -i 4 user.json 184 | { 185 | "required": [ 186 | "address", 187 | "age", 188 | "cellphone", 189 | "name" 190 | ], 191 | "type": "object", 192 | "properties": { 193 | "cellphone": { 194 | "type": "string" 195 | }, 196 | "age": { 197 | "type": "integer" 198 | }, 199 | "name": { 200 | "type": "string" 201 | }, 202 | "address": { 203 | "type": "string" 204 | } 205 | } 206 | } 207 | ``` 208 | 209 | **와우!** 잘 되는군요. 위에 `"required"` 는 꼭 나와야 하는 것을 지정하는 부분입니다. 210 | 각 문자열에서의 세세한 설정부분은 [string](JSON-Schema.md#string) 에서 `length`, `정규식`, `format` 을 참조합니다. 211 | 212 | 진짜 필요한 항목이나 아니면 선택 사항등을 수정합니다. 이부분은 [JSON 스키마](JSON-Schema.md)를 일독하고 잘 사용하시기를 바랍니다. 213 | 214 | 약간 더 수정을 하여 다음과 같이 해 보았습니다. 215 | 216 | ```bash 217 | $ cat user-schema.json 218 | { 219 | "type": "object", 220 | "properties": { 221 | "cellphone": { 222 | "type": "string", 223 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 224 | }, 225 | "age": { 226 | "type": "integer", 227 | "minimum": 0, 228 | "maximum": 150 229 | }, 230 | "name": { 231 | "type": "string" 232 | }, 233 | "address": { 234 | "type": "string", 235 | "maxLength": 50 236 | } 237 | }, 238 | "required": [ 239 | "age", 240 | "name" 241 | ] 242 | } 243 | ``` 244 | 245 | 다음과 같이 변경하였습니다. 246 | 247 | * `"age"`와 `"name"` 은 꼭 나오고 다른 항목은 선택적으로 나올 수도 있고 안 나올 수도 있습니다. 248 | * `"cellphone"` 에 `"pattern"`을 주어 010-1111-3829 와 같이 나오면 성공하도록 정규식 패턴을 추가하였습니다. 249 | * 0 <= `"age"` <= 150 사이의 값이 나오도록 `minimum` 및 `maximum`을 지정하였습니다. 250 | * `"address"`의 최대 길이를 50으로 제한합니다. 251 | 252 | 그 밖에도 object 등을 그 안에 정해서 중첩시킬 수도 있습니다. 253 | 254 | 그 밖에 페친이 알려주신 [jsonschema.net](http://jsonschema.net/#/) 이라는 사이트가 있는데 위에 genson 보다 더 상세하고 자세히 스키마를 생성해 낼 수 있습니다. (API 로 제공하면 좋겠다는 생각이...) 255 | 256 | ## 3 특정 JSON 데이터가 해당 스키마에 맞는지 검증 257 | 258 | 파이썬에 [jsonschema](https://github.com/Julian/jsonschema) 라는 모듈을 이용하면 JSON의 데이터가 해당 스키마와 맞는지 검증해 봅니다. 259 | 260 | 이제 `user_validate.py` 라는 파이썬 파일을 만들어 보았습니다. 261 | 262 | ```python 263 | #!/usr/bin/env python 264 | # coding=utf-8 265 | 266 | import sys 267 | from jsonschema import validate 268 | from jsonschema.exceptions import ValidationError 269 | 270 | schema = { 271 | "type": "object", 272 | "properties": { 273 | "cellphone": { 274 | "type": "string", 275 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 276 | }, 277 | "age": { 278 | "type": "integer", 279 | "minimum": 0, 280 | "maximum": 150 281 | }, 282 | "name": { 283 | "type": "string" 284 | }, 285 | "address": { 286 | "type": "string", 287 | "maxLength": 50 288 | } 289 | }, 290 | "required": [ 291 | "age", 292 | "name" 293 | ] 294 | } 295 | 296 | data = [ 297 | { 298 | "name": "홍길동", 299 | "cellphone": "010-1345-7764", 300 | "address": "이상국 행복리 234", 301 | "age": 33 302 | }, 303 | { 304 | "name": "홍길동", 305 | "age": 33 306 | }, 307 | { 308 | "name": "홍길동", 309 | "address": "이상국 행복리 234", 310 | }, 311 | { 312 | "name": "홍길동", 313 | "cellphone": "012-1345-7764", 314 | "age": 33 315 | } 316 | ] 317 | 318 | print("Validating the input data using jsonschema:") 319 | for idx, item in enumerate(data): 320 | try: 321 | validate(item, schema) 322 | sys.stdout.write("Record #{}: OK\n".format(idx)) 323 | except ValidationError as ve: 324 | sys.stderr.write("Record #{}: ERROR\n".format(idx)) 325 | sys.stderr.write(str(ve) + "\n") 326 | ``` 327 | 328 | 이 결과를 확인하면, 329 | 330 | ```sh 331 | Validating the input data using jsonschema: 332 | Record #0: OK 333 | Record #1: OK 334 | Record #2: ERROR 335 | 'age' is a required property 336 | 337 | Failed validating 'required' in schema: 338 | {'properties': {'address': {'maxLength': 50, 'type': 'string'}, 339 | 'age': {'maximum': 150, 340 | 'minimum': 0, 341 | 'type': 'integer'}, 342 | 'cellphone': {'pattern': '^[01]{3}-[0-9]{4}-[0-9]{4}$', 343 | 'type': 'string'}, 344 | 'name': {'type': 'string'}}, 345 | 'required': ['age', 'name'], 346 | 'type': 'object'} 347 | 348 | On instance: 349 | {'address': '\xe2\x80\x8b\xec\x9d\xb4\xec\x83\x81\xea\xb5\xad \xed\x96\x89\xeb\xb3\xb5\xeb\xa6\xac 234', 350 | 'name': '\xed\x99\x8d\xea\xb8\xb8\xeb\x8f\x99'} 351 | Record #3: ERROR 352 | '012-1345-7764' does not match '^[01]{3}-[0-9]{4}-[0-9]{4}$' 353 | 354 | Failed validating 'pattern' in schema['properties']['cellphone']: 355 | {'pattern': '^[01]{3}-[0-9]{4}-[0-9]{4}$', 'type': 'string'} 356 | 357 | On instance['cellphone']: 358 | '012-1345-7764' 359 | ``` 360 | 361 | **우와~~!!** 이렇게 성공과 실패도 잡아내는 것도 신통하지만 정확하게 왜 검증에 틀렸는가를 정확하게 알려줍니다. 그 메시지를 거의 그대로 사용자에게 보여줘도 될 만 합니다. (대신 영어로 나오네요~ ^^) 362 | 363 | 위에 파이썬 모듈을 확인해 보았지만 [언어별 JSON 스키마 검증](http://json-schema.org/implementations.html)을 보면 많은 언어에서도 모두 지원되는 모듈들이 있음을 알 수 있습니다. 364 | 365 | 특히 Client-Side의 JavaScript에서 이용할 만한 [ajv](https://github.com/epoberezkin/ajv)가 있습니다. 이제는 사용자가 입력한 자료로 JSON으로 만들어 이 JSON 스키마 검증 모듈로 미리 체크할 수도 있고, 또 API 안에서도 체크할 수 있고... 366 | 367 | 지금까지 이런 검증 코드를 하나 하나 노가다를 뛰며 했었던 것을 생각해 보면... 368 | 369 | 역시 손발이 게으른 개발자가 이런 훌륭한 방안을 고안하고 구현하고 하는 듯 합니다. 370 | 우리 같은 일반 개발자는 이런 것을 잘 사용하는 것 만으로도 훨씬 효과적이고 경제적인 코드를 할 수 있다고 확신합니다. 371 | 372 | ## 4 해당 스키마에 맞는 자동 데이터 생성 373 | 374 | 이제 다른 요구 사항이 생겼습니다. RPC 세계에서 STUB 라는 dummy 코드라는 개념이 있었습니다. 비슷한 개념인데 Front-End와 Back-End 에서 일을 하는데 위와 같은 API 를 먼저 설계하고 여이~ 땅 작업을 시작한다고 하면 dummy API 코드를 생성해서 작업을 한다고 가정합시다. 375 | 376 | 그러면 위와 같은 JSON 스키마를 가지고 거꾸로 해당 스키마를 만족하는 랜덤 JSON 데이터를 뽑아낼 필요가 있습니다. API 패러미터 입장에서는 외부에서 해당 JSON 스키마를 만족하는 랜덤 데이터를 갖는 JSON 데이터 자동으로 생산하고 또한 결과의 JSON 또한 JSON 스키마를 정해놓고 거기에 맞는 결과가 나오도록 JSON 데이터를 자동 생성하면 우리는 해당 코드의 실제 코드 없이도 이미 패러미터 검증 및 그 결과를 가짜로 만들어 넘겨줌으로써 호출하는 곳에서 해당 API가 이미 되었다고 생각하고 작업을 할 수 있을 수 있습니다. 377 | 378 | 그래서 이런 모듈이 있을까 확인해 보았더니... 379 | 380 | [apitools](https://github.com/hamstah/apitools) 라는 모듈이 있습니다. 자세히는 못 보았지만 정확히 JSON 스키마를 넣고 임의 JSON 데이터를 만들 수 있습니다. 381 | 382 | ``` txt 383 | apitools==0.1.4 384 | rstr==2.2.5 385 | ``` 386 | apitools 를 선택하는데 rstr 이라는 정규식에서 일치하는 랜덤 데이터를 생성하는 모듈 또한 설치해야 합니다. 387 | 388 | > gen_jsondata.py 389 | 390 | 라는 파이썬 파일을 다음과 같이 만들어 테스트를 해 보았습니다. 391 | 392 | ``` python 393 | #!/usr/bin/env python 394 | # coding=utf-8 395 | 396 | import sys 397 | from apitools.datagenerator import DataGenerator 398 | from jsonschema import validate 399 | from jsonschema.exceptions import ValidationError 400 | 401 | generator = DataGenerator() 402 | 403 | user_schema = { 404 | "type": "object", 405 | "properties": { 406 | "cellphone": { 407 | "type": "string", 408 | "pattern": "^[01]{3}-[0-9]{4}-[0-9]{4}$" 409 | }, 410 | "age": { 411 | "type": "integer", 412 | "minimum": 0, 413 | "maximum": 150 414 | }, 415 | "name": { 416 | "type": "string" 417 | }, 418 | "address": { 419 | "type": "string", 420 | "maxLength": 50 421 | } 422 | }, 423 | "required": [ 424 | "age", 425 | "name" 426 | ] 427 | } 428 | 429 | while True: 430 | r = generator.random_value(user_schema) 431 | try: 432 | validate(r, user_schema) 433 | break 434 | except ValidationError as ve: 435 | sys.stderr.write(str(ve) + "\n") 436 | print(r) 437 | ``` 438 | 439 | 해당 결과를 돌려보면 440 | 441 | ```sh 442 | $ python gen_jsondata.py 443 | 'age' is a required property 444 | 445 | Failed validating 'required' in schema: 446 | {'properties': {'address': {'maxLength': 50, 'type': 'string'}, 447 | 'age': {'maximum': 150, 448 | 'minimum': 0, 449 | 'type': 'integer'}, 450 | 'cellphone': {'pattern': '^[01]{3}-[0-9]{4}-[0-9]{4}$', 451 | 'type': 'string'}, 452 | 'name': {'type': 'string'}}, 453 | 'required': ['age', 'name'], 454 | 'type': 'object'} 455 | 456 | On instance: 457 | {'cellphone': u'001-0033-5228'} 458 | 'age' is a required property 459 | 460 | Failed validating 'required' in schema: 461 | {'properties': {'address': {'maxLength': 50, 'type': 'string'}, 462 | 'age': {'maximum': 150, 463 | 'minimum': 0, 464 | 'type': 'integer'}, 465 | 'cellphone': {'pattern': '^[01]{3}-[0-9]{4}-[0-9]{4}$', 466 | 'type': 'string'}, 467 | 'name': {'type': 'string'}}, 468 | 'required': ['age', 'name'], 469 | 'type': 'object'} 470 | 471 | On instance: 472 | {'address': 'aXfUmZWMhZ9q7QXAQoWpBzZYjORMjVCzfO1BLmbIE', 473 | 'name': 'hFOxGGOh'} 474 | 'age' is a required property 475 | 476 | Failed validating 'required' in schema: 477 | {'properties': {'address': {'maxLength': 50, 'type': 'string'}, 478 | 'age': {'maximum': 150, 479 | 'minimum': 0, 480 | 'type': 'integer'}, 481 | 'cellphone': {'pattern': '^[01]{3}-[0-9]{4}-[0-9]{4}$', 482 | 'type': 'string'}, 483 | 'name': {'type': 'string'}}, 484 | 'required': ['age', 'name'], 485 | 'type': 'object'} 486 | 487 | On instance: 488 | {} 489 | {'age': 12, 'name': 'nYk4PlSoQxx'} 490 | ``` 491 | 492 | 아직 `"required"` 속성을 제대로 인식 못하는 것 같은데... 좀더 확인을 해 봐야 되겠습니다. 493 | 494 | `apitools` 라는 이름이 의미하듯이 JSON 스키마를 통하여 RestfulAPI 같이 테스트 API 서버 기능도 제공하는것 같습니다. 495 | 496 | 참고로 웹사이트에 직접 들어가서 생성하는 것도 있습니다. 497 | [json-generator](http://www.json-generator.com) 라는 사이트 인데 표준 JSON 스키마 처럼 보이지는 않았습니다. 498 | 499 | 500 | ## 5 기타 필요 모듈 및 사이트 501 | 502 | 파이썬에서 JSON 스키마를 받아들이고 이를 바탕으로 자동 파이썬 클래스화 해 주는 모듈이 있습니다. 503 | 504 | * [python-jsonschema-objects](https://github.com/cwacek/python-jsonschema-objects) 505 | * [warlock](https://github.com/bcwaldon/warlock) 506 | 507 | 나중에 필요에 따라 이용하면 좋겠다는 느낌이 들었습니다. python-jsonschema-objects 가 좀 더 나아 보입니다. 508 | 509 | 페친이 알려주신 [swagger-flex](https://github.com/pipermerriam/flex) 라고 있는데 아마도 API 스펙 및 자동화 등에 관한 것인 듯 합니다. (언제 시간되면 함 봐야 되겠네요... 문제는 시간이 없어서... T.T) 510 | 511 | 512 | 513 | ## 6 TODO 514 | 515 | API 들에 대한 정보를 넣고 그 목록을 자동으로 API (RestFulAPI)를 생성하는데 JSON 스키마를 가지고 이용하도록 합니다. API Stub 내용을 넣는데 해당 패러미터와 리턴 JSON 데이터가 맞는지 Validation을 하고 Return 값을 자동으로 만들어 리턴하도록 하는 자동 함수 generation를 만들어 사용하면 좋을 듯 합니다. 516 | -------------------------------------------------------------------------------- /JSON-Schema.md: -------------------------------------------------------------------------------- 1 | #[JSON Schema](https://spacetelescope.github.io/understanding-json-schema/index.html) 2 | 3 | ![문어그림](https://spacetelescope.github.io/understanding-json-schema/_images/octopus.svg) 4 | 5 | JSON 스키마는 XML 스키마와 유사하게 JSON 으로 표현된 JSON 객체의 검증을 하기 위한 표현 방법이라 생각하면 됩니다. (역자주) 6 | 7 | ## 스키마란? 8 | 9 | 만약 RelaxNG 또는 ASN.1 과 같은 XML 스키마를 알고 있다면 JSON 스키마를 동일한 개념으로 이해해도 됩니다. 우선 JSON 스키마를 정의하기 위해서는 JSON이 무엇인지 알 필요가 있습니다. 10 | [JSON(JavaScript Object Notation)](http://www.json.org/json-ko.html)은 JavaScript를 이용한 경량의 데이터 교환 형식입니다. 11 | 12 | ### 자료형 13 | 14 | 다음과 같은 종류의 JSON 데이터 구조가 존재합니다. 15 | 16 | * object 17 | 18 | ``` json 19 | { "key1": "value1", "key2": "value2" } 20 | ``` 21 | 22 | * number 23 | 24 | ``` json 25 | 42 26 | 3.1415926 27 | ``` 28 | 29 | * string 30 | 31 | ``` json 32 | "This is a string" 33 | ``` 34 | 35 | > 파이썬의 문자열은 ' 및 " 로 모두 문자열 지정이 가능하지만 JSON에서는 " 만 허용됩니다. 36 | 37 | * boolean 38 | 39 | ``` json 40 | true 41 | false 42 | ``` 43 | 44 | * null 45 | 46 | ``` json 47 | null 48 | ``` 49 | 50 | JSON과 파이썬은 아주 유사하게 1:1 표현이 가능하며 쉽게 이용할 수 있습니다. 51 | 52 | 53 | ### 첫번째 예제 54 | 55 | 이런 방식을 이용하여 다음과 같은 여러가지 방식의 자료를 표현할 수 있습니다. 56 | 57 | ``` json 58 | { 59 | "name": "George Washington", 60 | "birthday": "February 22, 1732", 61 | "address": "Mount Vernon, Virginia, United States" 62 | } 63 | 64 | { 65 | "first_name": "George", 66 | "last_name": "Washington", 67 | "birthday": "1732-02-22", 68 | "address": { 69 | "street_address": "3200 Mount Vernon Memorial Highway", 70 | "city": "Mount Vernon", 71 | "state": "Virginia", 72 | "country": "United States" 73 | } 74 | } 75 | ``` 76 | 77 | 이제 맛보기로 스키마가 어떻게 생겼나 보겠습니다. 78 | 79 | ``` json 80 | { 81 | "type": "object", 82 | "properties": { 83 | "first_name": { "type": "string" }, 84 | "last_name": { "type": "string" }, 85 | "birthday": { "type": "string", "format": "date-time" }, 86 | "address": { 87 | "type": "object", 88 | "properties": { 89 | "street_address": { "type": "string" }, 90 | "city": { "type": "string" }, 91 | "state": { "type": "string" }, 92 | "country": { "type" : "string" } 93 | } 94 | } 95 | } 96 | } 97 | ``` 98 | 99 | 첫번째 데이터를 검증해보면 실패할 것이고, 100 | 101 | ``` json 102 | { 103 | "name": "George Washington", 104 | "birthday": "February 22, 1732", 105 | "address": "Mount Vernon, Virginia, United States" 106 | } 107 | ``` 108 | 109 | 두번째 데이터는 검증에 성공할 것입니다. 110 | 111 | ``` json 112 | { 113 | "first_name": "George", 114 | "last_name": "Washington", 115 | "birthday": "22-02-1732", 116 | "address": { 117 | "street_address": "3200 Mount Vernon Memorial Highway", 118 | "city": "Mount Vernon", 119 | "state": "Virginia", 120 | "country": "United States" 121 | } 122 | } 123 | ``` 124 | 125 | ## 기본 126 | 127 | ### 시작하기 128 | 129 | 만약 JSON 스키마가 130 | 131 | ``` json 132 | { } 133 | ``` 134 | 과 같다면 어떤일이 벌어질까요? 135 | 136 | 단순 JSON 자료형 혹은 JSON 이라면 해당 결과는 모두 검증에 성공합니다. 137 | 138 | > 42 139 | > 140 | > "I'm a string" 141 | > 142 | > { "an": [ "arbitrarily", "nested" ], "data": "structure" } 143 | 144 | 위에 세 가지 경우 모두 검증에 성공합니다. 145 | 146 | 147 | ### `type` 키워드 148 | 149 | JSON 형식에서 특정 형식의 데이터만 오도록 지정하기 위하여 `type` 키워드를 이용합니다. 150 | 151 | 만약 152 | 153 | ``` json 154 | { "type": "string" } 155 | ``` 156 | 라고 JSON 스키마를 지정했다면 157 | 158 | > "I'm a string" 159 | 160 | 는 성공할 것이고 161 | 162 | > 42 163 | 164 | 는 실패합니다. 165 | 166 | ### JSON 스키마 선언 167 | 168 | JSON 스키마 또한 JSON 이기 떄문에 해당 스키마가 단순 JSON 데이터 인지 아니면 스키마 인지 알 수 없는 경우가 있습니다. 이런 경우 다음과 같이 `$schema` 라는 것을 지정해 줍니다. 169 | 170 | ```json 171 | { "$schema": "http://json-schema.org/schema#" } 172 | ``` 173 | 174 | ### 고유 식별자 선언 175 | 176 | `id`라는 식별자를 각각의 스키마에 고유하게 지정할 수 있습니다. 각각은 공유 URL을 지정해 줍니다. 177 | 178 | ```json 179 | { "id": "http://yourdomain.com/schemas/myschema.json" } 180 | ``` 181 | 182 | ## JSON 스키마 참조 183 | 184 | ### 자료형에 따른 키워드 185 | 186 | 187 | #### 파이썬과의 자료형 비교 188 | 189 | 다음은 JSON으로 표현되는 javascript의 개체와 Python의 자료형에 대한 비교표 입니다. 190 | 191 | | 언어 | JavaScript | Python | 192 | |---|---|---| 193 | | *문자열* | string | string [^1] | 194 | | *숫자* | number | int/float [^2] | 195 | | *사전형* | object | dict | 196 | | *목록* | array | list | 197 | | *불리언* | boolean | bool [^3] | 198 | | *널* | null | None | 199 | 200 | 201 | [^1]: 파이썬 2.x에서는 `unicode`, 파이썬 3.x 에서는 `str` 과 같은 데이터 형식 202 | [^2]: 파이썬에는 int 형과 float 형이 개별로 존재하지만 javascript에는 별도 형식이 존재하지 않습니다. 또한 파이썬 2.x는 int 외에 long 형이 개별로 존재하였지만 파이썬 3.x에서는 int 형만 존재합니다. 203 | [^3]: javascript에서는 `true/false` 로 구분되고 파이썬에서는 `True/False` 가 불리언 상수입니다. 204 | 205 | 206 | `type` 키워드 자체는 문자열 이거나 목록 입니다. 207 | * 만약 문자열이면 위의 JavaScript 에 정의된 자료형 중에 하나가 올 수 있습니다. 208 | * 만약 목록이라면 문자열의 목록이 나타나는데 각 문자열 역시 위에 JavaScript에 정의된 자료형 입니다. 209 | 210 | ``` json 211 | { "type": "number" } 212 | ``` 213 | 라고 스키마가 정의되면, 214 | 215 | > 42 216 | > 42.0 217 | 218 | 등은 검증에서 성공하고, 219 | 220 | > "42" 221 | 222 | 는 검증에서 실패합니다. 223 | 224 | 그런데 다음과 같이 225 | 226 | ``` json 227 | { "type": ["number", "string"] } 228 | ``` 229 | 230 | 와 같이 목록으로 하나 이상의 자료형으로 정의되어 있다면 231 | 232 | > 42 233 | > "Life, the universe, and everythin" 234 | 235 | 은 검증에 성공하지만, 236 | 237 | > ["Life", "the universe", "and everything"] 238 | 239 | 는 실패할 것입니다. ("array" 가 목록에 포함되어야 성공 합니다) 240 | 241 | ### string 242 | 243 | `string` 자료형은 문자열을 나타냅니다. 244 | 245 | ``` json 246 | { "type": "string" } 247 | ``` 248 | 249 | > "This is a string" 250 | 251 | > "Déjà vu" 252 | 253 | > "" 254 | 255 | > "42" 256 | 257 | 등은 모두 성공할 것이고 258 | 259 | > 42 260 | 261 | 는 문자열이 아니기 때문에 (number 자료형) 실패합니다. 262 | 263 | #### Length 264 | 문자열은 각각 `minLength` 및 `maxLength` 키워드에 의하여 다음과 같이 그 길이를 제한할 수 있습니다. 265 | 266 | ``` json 267 | { 268 | "type": "string", 269 | "minLength": 2, 270 | "maxLength": 3 271 | } 272 | ``` 273 | 274 | > "AB" 275 | 276 | > "ABC" 277 | 278 | 는 각각 검증에 성공하는데 반해 279 | 280 | > "A" 281 | 282 | 및 283 | 284 | > "ABCD" 285 | 286 | 는 검증에 실패합니다. 287 | 288 | #### 정규식 289 | 290 | `pattern` 키워드를 이용하여 문자열의 정규식 매칭을 통한 검증이 가능하빈다. 정규식 문법은 JavaScript([ECMA 262](http://www.ecma-international.org/publications/standards/Ecma-262.htm))에 정의된 정규식을 따릅니다. 정규식에 대한 더 자세한 설명은 [정규식](https://spacetelescope.github.io/understanding-json-schema/reference/regular_expressions.html#regular-expressions)을 참조하십시오. 291 | 292 | ``` json 293 | { 294 | "type": "string", 295 | "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 296 | } 297 | ``` 298 | 위와 같이 스키마를 설정하면, 299 | 300 | > "555-1212" 301 | 302 | > "(888)555-1212" 303 | 304 | 는 검증에 성공하지만, 305 | 306 | > "(888)555-1212 ext. 532" 307 | 308 | 또는 309 | 310 | > "(800)FLOWERS" 311 | 312 | 등은 검증에 실패합니다. 313 | 314 | #### format 315 | 316 | `format` 키워드는 시각, 이메일 등과 같이 일반적으로 사용되는 문자열에 대한 검증용으로 이용됩니다. 317 | 318 | > 노트: JSON 스키마를 구현하는데 있어 이 부분은 꼭 구현해야하는 것이 아니며 실제 열 구현체 들이 이 스펙을 구현하지 않고 있으므로 구현체를 확인하십시오. 319 | 320 | ##### 미리 정의된 format 321 | 322 | 다음과 같은 문자열에 대하여 사전 정의된 format이 존재 합니다. 323 | 324 | * `"date-time"`: [RFC 3339, section 5.6](https://tools.ietf.org/html/rfc3339)에 정의된 날짜 표현 325 | * `"email"`: [RFC 5322, section 3.4.1](https://tools.ietf.org/html/rfc5322)에 정의된 이메일 표현 326 | * `"hostname"`: [RFC 1034, section 3.1](https://tools.ietf.org/html/rfc1034)에 정의된 호스트명 표현 327 | * `"ipv4"`: [RFC 2673, section 3.2](https://tools.ietf.org/html/rfc2673)에 정의된 IPv4 주소 표현 328 | * `"ipv6"`: [RFC 2373, section 2.2](hhttps://tools.ietf.org/html/rfc2373#section-2.2)에 정의된 IPv6 주소 표현 329 | * `"uri"`: [RFC3986](https://tools.ietf.org/html/rfc3986)에 정의된 ​universal resource identifier 표현 330 | 331 | 332 | ### 숫자형 333 | 334 | JSON 스키마에서 정의된 숫자형으로 `integer`와 `number` 가 있습니다. 335 | 336 | #### integer 337 | `integer` 정수형을 나타냅니다. 338 | 339 | ```json 340 | { "type": "integer" } 341 | ``` 342 | 과 같이 JSON 스키마가 정의되어 있으면 343 | 344 | > 42 345 | 346 | > -1 347 | 348 | 은 검증에 성공하고, 349 | 350 | > 3.14 351 | 352 | > "42" 353 | 354 | 등은 실패합니다. 355 | 356 | #### number 357 | 358 | 정수형 또는 실수형을 표현하기 위하여 `number` 형을 이용합니다. 359 | 360 | ``` json 361 | { "type": "number" } 362 | ``` 363 | 364 | > 42 365 | > 366 | > -1 367 | 368 | 과 같은 정수형 뿐만 아니라 369 | 370 | > 5.0 371 | > 372 | > 2.99792458e8 373 | 374 | 과 같은 실수형 모두 검증에 성공하지만, 375 | 376 | > "42" 377 | 378 | 와 같은 문자열은 실패합니다. 379 | 380 | #### multiples 381 | 382 | `multipleOf` 키워드를 이용하여 특정 정수 또는 실수의 배수만 허용하도록 할 수 있습니다. 383 | 384 | ``` json 385 | { 386 | "type" : "number", 387 | "multipleOf" : 10 388 | } 389 | ``` 390 | 라고 JSON 스키마를 지정하면, 391 | 392 | > 0 393 | > 394 | > 10 395 | > 396 | > 20 397 | 398 | 등은 검증에 성공하는 반면, 399 | 400 | > 23 401 | 402 | 은 검증에 실패합니다. 403 | 404 | #### Range 405 | 숫자 값이 특정 영역에 속해있는가를 표현하는 `minimum`, `maximum` 과 `exclusiveMinimum` 및 `exclusiveMaximum` 키워드가 있습니다. 406 | 407 | * `minimum`는 최소 허용값을 나타냅니다. 408 | * `exclusiveMinimum`는 불리언 값을 가지는데 `true`이면 최소값을 허용하지 않고 (x > min) `false`이면 최소값을 포함합니다. (x >= min) 디폴트는 `false` 입니다. 409 | * `maximum`는 최대 허용값을 나타냅니다. 410 | * `exclusiveMaximum`는 불리언 값을 가지는데 `true`이면 최대값을 허용하지 않고 (x < max) `false`이면 최대값을 포함합니다. (x <= max) 디폴트는 `false` 입니다. 411 | 412 | 다음과 같이, 413 | 414 | ``` json 415 | { 416 | "type": "number", 417 | "minimum": 0, 418 | "maximum": 100, 419 | "exclusiveMaximum": true 420 | } 421 | ``` 422 | JSON 스키마가 정의되어 있으면, 423 | 424 | > -1 425 | 426 | 과 같이 최소값보다 작은 것은 검증에 실패하고 427 | 428 | > 0 429 | > 430 | > 10 431 | > 432 | > 99 433 | 434 | 등은 검증에 성공합니다. 435 | 436 | 그런데 ​`exclusiveMaximum`가 `true` 이므로 437 | 438 | > 100 439 | 440 | 은 실패합니다. 441 | 442 | 443 | ### object 444 | 파이썬의 dict와 같은 형식입니다. 이것은 중첩(nested) 자료형을 의미합니다. 445 | 446 | ```json 447 | { "type": "object" } 448 | ``` 449 | 와 같이 JSON 스키마가 정의 되어 있다면, 450 | 451 | ```json 452 | { 453 | "key" : "value", 454 | "another_key" : "another_value" 455 | } 456 | ``` 457 | 458 | 및 459 | 460 | ```json 461 | { 462 | "Sun" : 1.9891e30, 463 | "Jupiter" : 1.8986e27, 464 | "Saturn" : 5.6846e26, 465 | "Neptune" : 10.243e25, 466 | "Uranus" : 8.6810e25, 467 | "Earth" : 5.9736e24, 468 | "Venus" : 4.8685e24, 469 | "Mars" : 6.4185e23, 470 | "Mercury" : 3.3022e23, 471 | "Moon" : 7.349e22, 472 | "Pluto" : 1.25e22 473 | } 474 | ``` 475 | 476 | 는 검증에 성공할 것이고 477 | 478 | 다음과 같은, 479 | 480 | ```json 481 | { 482 | 0.01 : "cm" 483 | 1 : "m", 484 | 1000 : "km" 485 | } 486 | ``` 487 | 488 | > `키`는 숫자가 아니라 문자열이어야 함 489 | 490 | ```json 491 | "Not an object" 492 | ``` 493 | > 단순 문자열은 object (dict) 가 아님 494 | 495 | ``` json 496 | ["An", "array", "not", "an", "object"] 497 | ``` 498 | 499 | > array (list)는 object(dict)가 아님 500 | 501 | 502 | #### Properties 503 | 504 | 키-값의 쌍을 속성 (Properties) 라고 하는데 객체는 이 `properties` 키워드로 정의합니다. `properties` 값은 object이며 각각의 키는 객체의 키가 되고 그 값은 자료형을 나타내는 정의가 됩니다. 505 | 506 | 예를 들어, 507 | 508 | ``` json 509 | { 510 | "type": "object", 511 | "properties": { 512 | "number": { "type": "number" }, 513 | "street_name": { "type": "string" }, 514 | "street_type": { "type": "string", 515 | "enum": ["Street", "Avenue", "Boulevard"] 516 | } 517 | } 518 | } 519 | ``` 520 | 와 같은 JSON 스키마가 정의 되어 있다면 521 | 522 | > { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" } 523 | 524 | 는 검증이 성공하는데 반해 525 | 526 | > { "number": "1600", "street_name": "Pennsylvania", "street_type": "Avenue" } 527 | 528 | 는 검증에 실패합니다. ("number" 키가 숫자 1600이 아니라 문자열 "1600" 임) 529 | 530 | 그런데 아래와 같이, 531 | 532 | > { "number": 1600, "street_name": "Pennsylvania" } 533 | 534 | 해당 키가 삭제되어도 성공입니다. (해당 키는 선택적으로 빠질 수 있습니다만 `required` 속성을 지정하면 꼭 등장해야 합니다) 535 | 536 | 따라서, 537 | 538 | > {} 539 | 540 | 도 검증에 성공합니다. 541 | 542 | 다음과 같은 543 | 544 | > ​{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" } 545 | 546 | JSON object도 검증에 성공하는데 `"direction":"NW"` 라는 속성이 추가되어도 문법에는 이상이 없습니다. 하지만 다음과 같이 547 | 548 | ```json 549 | { 550 | "type": "object", 551 | "properties": { 552 | "number": { "type": "number" }, 553 | "street_name": { "type": "string" }, 554 | "street_type": { "type": "string", 555 | "enum": ["Street", "Avenue", "Boulevard"] 556 | } 557 | }, 558 | "additionalProperties": false 559 | } 560 | ``` 561 | 562 | `additionalProperties` 라는 속성을 `false`라고 주면 위의 563 | 564 | > ​{ "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" } 565 | 566 | 검증은 실패할 것입니다. 567 | 568 | 그런데 다음과 같이 `additionalProperties` 의 값이 불리언이 아니라 object 이면, 569 | 570 | ```json 571 | { 572 | "type": "object", 573 | "properties": { 574 | "number": { "type": "number" }, 575 | "street_name": { "type": "string" }, 576 | "street_type": { "type": "string", 577 | "enum": ["Street", "Avenue", "Boulevard"] 578 | } 579 | }, 580 | "additionalProperties": { "type": "string" } 581 | } 582 | ``` 583 | 584 | > { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue" } 585 | 586 | 또는 587 | 588 | > { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "direction": "NW" } 589 | 590 | 는 검증에 성공하지만, 591 | 592 | > { "number": 1600, "street_name": "Pennsylvania", "street_type": "Avenue", "office_number": 201 } 593 | 594 | 는 실패합니다. 부가적인 속성 ​`"office_number"`의 값이 201 이라는 숫자이기 때문입니다. 595 | 596 | #### required 속성 597 | 598 | 위에서 언급하였듯이 `properties`에서 지정한 속성은 꼭 있어야 할 필요가 없습니다. 하지만 `required` 키워드를 이용하여 꼭 있어야 하는 속성을 키-값을 지정할 수 있습니다. 599 | 600 | ```json 601 | { 602 | "type": "object", 603 | "properties": { 604 | "name": { "type": "string" }, 605 | "email": { "type": "string" }, 606 | "address": { "type": "string" }, 607 | "telephone": { "type": "string" } 608 | }, 609 | "required": ["name", "email"] 610 | } 611 | ``` 612 | 613 | 이라는 JSON 스키마가 존재하면 614 | 615 | ``` json 616 | { 617 | "name": "William Shakespeare", 618 | "email": "bill@stratford-upon-avon.co.uk" 619 | } 620 | ``` 621 | 622 | 과 623 | 624 | ```json 625 | { 626 | "name": "William Shakespeare", 627 | "email": "bill@stratford-upon-avon.co.uk", 628 | "address": "Henley Street, Stratford-upon-Avon, Warwickshire, England", 629 | "authorship": "in question" 630 | } 631 | ``` 632 | 633 | 는 검증에 성공하지만, 634 | 635 | ``` json 636 | { 637 | "name": "William Shakespeare", 638 | "address": "Henley Street, Stratford-upon-Avon, Warwickshire, England", 639 | } 640 | ``` 641 | 642 | 는 검증에 실패합니다. 643 | 644 | 꼭 나타나야 하는 `"email"` 키가 없기 때문입니다. 645 | 646 | #### size 647 | 648 | `minProperties` 과 `maxProperties` 키워드를 통하여 키-값의 개수를 지정할 수 있습니다. 649 | 650 | 예를 들어, 651 | 652 | ``` json 653 | { 654 | "type": "object", 655 | "minProperties": 2, 656 | "maxProperties": 3 657 | } 658 | ``` 659 | 라고 JSON 스키마를 지정하면 660 | 661 | > {} 662 | > 663 | > { "a": 0 } 664 | > 665 | > { "a": 0, "b": 1, "c": 2, "d": 3 } 666 | 667 | 는 검증에 실패할 것이지만 668 | 669 | > { "a": 0, "b": 1 } 670 | > 671 | > { "a": 0, "b": 1, "c": 2 } 672 | 673 | 는 검증에 성공합니다. 674 | 675 | #### 의존성(Dependencies) 676 | 677 | > 주의: 이부분은 JSON 스키마의 세세한 정의로 계속 발전할 수 있습니다. 678 | 679 | `dependencies` 키워드는 어느 특정 속성이 존재하여야 하는 것을 정의합니다. 680 | 681 | 다음과 같이 두가지 종류의 ​`dependencies` 가 있습니다. 682 | 683 | * `Property dependencies`는 주어진 속성이 존재한다면 다른 어떤 속성이 존재해야 함을 의미 684 | * `Schema dependencies`는 주어진 속성이 존재한다면 스카마가 변경됨을 선언합니다. 685 | 686 | ##### 속성 의존성 (Property dependencies) 687 | 688 | ```json 689 | { 690 | "type": "object", 691 | 692 | "properties": { 693 | "name": { "type": "string" }, 694 | "credit_card": { "type": "number" }, 695 | "billing_address": { "type": "string" } 696 | }, 697 | 698 | "required": ["name"], 699 | 700 | "dependencies": { 701 | "credit_card": ["billing_address"] 702 | } 703 | } 704 | ``` 705 | 706 | 위와 같이 JSON 스키마가 정의되어 있습니다. 이는 *고객* 을 담고 있다고 가정합니다. 그런데 위에서 보면 `"name"` 이라는 속성은 꼭 나와야 하는데 `"credit_card"` 라는 속성은 나올 수도 있고 안 나올 수도 있습니다. 그런데 만약 `"credit_card"` 속성이 존재한다면 꼭 `"billing_address"` 속성이 나와야 한다고 정할 필요가 있는데 그것을 위에서 처럼 `"dependencies"`에 정의한 것입니다. 707 | 708 | 따라서 다음과 같이, 709 | 710 | ```json 711 | { 712 | "name": "John Doe" 713 | } 714 | ``` 715 | 716 | 는 `"credit_card"` 속성과 `"billing_address"` 속성 모두 없으므로 검증에 성공합니다. 717 | 718 | 또한, 719 | 720 | ```json 721 | { 722 | "name": "John Doe", 723 | "credit_card": 5555555555555555, 724 | "billing_address": "555 Debtor's Lane" 725 | } 726 | ``` 727 | 728 | 는 `"credit_card"` 속성과 `"billing_address"` 속성이 모두 있으므로 검증에 성공합니다. 729 | 730 | 마찬가지로 731 | 732 | ```json 733 | { 734 | "name": "John Doe", 735 | "billing_address": "555 Debtor's Lane" 736 | } 737 | ``` 738 | 739 | `"billing_address"` 속성만 있어도 검증에 성공합니다. 740 | 741 | 대신, 742 | 743 | ```json 744 | { 745 | "name": "John Doe", 746 | "credit_card": 5555555555555555 747 | } 748 | ``` 749 | 750 | 과 같은 경우는 ​ `"credit_card"` 속성이 존재하지만 `"billing_address"` 속성이 없으므로 검증에 실패합니다. 751 | 752 | 그런데 여기에서 `"billing_address"` 속성이 나왔을 때에도 `"credit_card"` 속성이 존재해만 한다면, 753 | 754 | ```json 755 | { 756 | "type": "object", 757 | 758 | "properties": { 759 | "name": { "type": "string" }, 760 | "credit_card": { "type": "number" }, 761 | "billing_address": { "type": "string" } 762 | }, 763 | 764 | "required": ["name"], 765 | 766 | "dependencies": { 767 | "credit_card": ["billing_address"], 768 | "billing_address": ["credit_card"] 769 | } 770 | } 771 | ``` 772 | 773 | 과 같이 상호 `"dependencies"` 를 주어 해결할 수 있습니다. 774 | 775 | ##### 스키마 의존성 (Schema dependencies) 776 | 777 | 스키마 의존성은 위의 속성 의존성과 달리 별도의 `properties` 선언을 포함합니다. 778 | 779 | 다음과 같이, 780 | 781 | ```json 782 | { 783 | "type": "object", 784 | 785 | "properties": { 786 | "name": { "type": "string" }, 787 | "credit_card": { "type": "number" } 788 | }, 789 | 790 | "required": ["name"], 791 | 792 | "dependencies": { 793 | "credit_card": { 794 | "properties": { 795 | "billing_address": { "type": "string" } 796 | }, 797 | "required": ["billing_address"] 798 | } 799 | } 800 | } 801 | ``` 802 | JSON 스키마가 정의되어 있다면, 803 | 804 | ```json 805 | { 806 | "name": "John Doe", 807 | "credit_card": 5555555555555555, 808 | "billing_address": "555 Debtor's Lane" 809 | } 810 | ``` 811 | 812 | 또는 813 | 814 | ``` Json 815 | { 816 | "name": "John Doe", 817 | "billing_address": "555 Debtor's Lane" 818 | } 819 | ``` 820 | 821 | 는 검증에 성공하고, 822 | 823 | ```json 824 | { 825 | "name": "John Doe", 826 | "credit_card": 5555555555555555 827 | } 828 | ``` 829 | 는 검증에 실패합니다. 830 | 831 | #### 패턴 속성 (Pattern Properties) 832 | 833 | `additionalProperties`에서 확인한 것과 같이 추가적인 속성이 나올 수 없도록 제한하거나 그 형식을 제한 하는 것과 유사하게 `patternProperties` 키워드를 이용하여 특정 패턴의 키가 오도록 제한 할 수 있습니다. 834 | 835 | 다음의 예제에서는 키가 `S_`로 시작하는 경우에는 문자열 값이, `I_`로 시작하는 경우에는 정수값이 오도록 제한하는 것을 보여줍니다. 836 | 837 | ```json 838 | { 839 | "type": "object", 840 | "patternProperties": { 841 | "^S_": { "type": "string" }, 842 | "^I_": { "type": "integer" } 843 | }, 844 | "additionalProperties": false 845 | } 846 | ``` 847 | 848 | 그러면, 849 | 850 | > { "S_25": "This is a string" } 851 | > 852 | > { "I_0": 42 } 853 | 854 | 는 검증에 성공하지만, 855 | 856 | > { "S_0": 42 } 857 | > 858 | > { "I_42": "This is a string" } 859 | > 860 | > { "keyword": "value" } 861 | 862 | 는 검증에 실패합니다. 863 | 864 | 하지만 스키마가 865 | 866 | ```json 867 | { 868 | "type": "object", 869 | "properties": { 870 | "builtin": { "type": "number" } 871 | }, 872 | "patternProperties": { 873 | "^S_": { "type": "string" }, 874 | "^I_": { "type": "integer" } 875 | }, 876 | "additionalProperties": { "type": "string" } 877 | } 878 | ``` 879 | 880 | 와 같이 `additionalProperties`에 문자열이 오도록 제한을 가했을 경우에는 881 | 882 | > { "builtin": 42 } 883 | > 884 | > { "keyword": "value" } 885 | 886 | 는 검증에 성공하지만, 887 | 888 | > { "keyword": 42 } 889 | 890 | 는 검증에 실패합니다. 891 | 892 | 893 | ### 목록 (array) 자료형 894 | 895 | 목록은 순서데로 값을 가지고 있는 내용입니다. JSON에서 각 항목은 서로 다른 자료형일 수 있습니다. 896 | 897 | ```json 898 | { "type": "array" } 899 | ``` 900 | 이라는 JSON 스키마가 있다면 901 | 902 | > [1, 2, 3, 4, 5] 903 | > 904 | > [3, "different", { "types" : "of values" }] 905 | 906 | 은 검증에 성공하는 반면, 907 | 908 | > {"Not": "an array"} 909 | 910 | 는 실패합니다. 911 | 912 | #### 항목 (items) 913 | 914 | 목록은 `items`와 `additionalItems` 키워드를 통하여 스키마를 정의합니다. 915 | 일반적으로 다음과 같은 두가지 방식의 목록이 있습니다. 916 | * 목록 검증 (List validation): 동일한 자료형이 각 항목으로 존재하도록 규약 917 | * 튜플 검증 (Tuple validation): 각각의 항목이 서로 다른 자료형이 존재할 때 규약 (파이썬의 `tuple`과 유사하지만 꼭 그 갯수를 맞출 필요는 없습니다) 918 | 919 | ##### 목록 검증 (List validation) 920 | 921 | 목록 검증은 임의의 길이를 갖는 동일한 자료형의 목록을 정의하는 스키마입니다. 922 | 923 | > 노트: `items` 은 한번만 나오는 것이며 `additionalItems`는 의미 없게 됩니다. 924 | 925 | ```json 926 | { 927 | "type": "array", 928 | "items": { 929 | "type": "number" 930 | } 931 | } 932 | ``` 933 | 위와 같은 JSON 스키마가 있다면 934 | 935 | > [1, 2, 3, 4, 5] 936 | 937 | 은 검증에 성공하지만, 938 | 939 | > [1, 2, "3", 4, 5] 940 | 941 | 는 실패합니다. 942 | 943 | ##### 튜플 검증 (Tuple validation) 944 | 945 | 튜플 검증은 특정 자료형을 갖는 목록을 지정할 수 있습니다. 946 | 947 | 예를 들어 다음과 같은 948 | > [1600, "Pennsylvania", "Avenue", "NW"] 949 | 950 | 라고 주소를 951 | 952 | > [number, street_name, street_type, direction] 953 | 954 | 의 4개 항목으로 표현한다면, 955 | 956 | * `number`: 거리 숫자 957 | * `​street_name`: 거리명으로 문자 958 | * `street_type`: 거리 형태, 문자 959 | * `direction`: 방향, 문자 960 | 961 | 위와 같은 경우, 962 | 963 | ```json 964 | { 965 | "type": "array", 966 | "items": [ 967 | { 968 | "type": "number" 969 | }, 970 | { 971 | "type": "string" 972 | }, 973 | { 974 | "type": "string", 975 | "enum": ["Street", "Avenue", "Boulevard"] 976 | }, 977 | { 978 | "type": "string", 979 | "enum": ["NW", "NE", "SW", "SE"] 980 | } 981 | ] 982 | } 983 | ``` 984 | 이라고 스키마를 지정하면, 985 | 986 | > [1600, "Pennsylvania", "Avenue", "NW"] 987 | 988 | 는 검증에 성공하지만, 989 | 990 | > [24, "Sussex", "Drive"] 991 | 992 | 또는 993 | 994 | > ["Palais de l'Élysée"] 995 | 996 | 는 모두 실패합니다. 997 | 998 | 하지만 각 항목의 갯수를 맞출 필요는 없습니다. 999 | 1000 | > [10, "Downing", "Street"] 1001 | 1002 | 네번째 항목이 빠져도 성공이고, 1003 | 1004 | > [1600, "Pennsylvania", "Avenue", "NW", "Washington"] 1005 | 1006 | 항목이 하나 더 나와도 성공입니다. 1007 | 1008 | 여기서 `additionalItems`가 사용될 수 있습니다. 1009 | 1010 | ```json 1011 | { 1012 | "type": "array", 1013 | "items": [ 1014 | { 1015 | "type": "number" 1016 | }, 1017 | { 1018 | "type": "string" 1019 | }, 1020 | { 1021 | "type": "string", 1022 | "enum": ["Street", "Avenue", "Boulevard"] 1023 | }, 1024 | { 1025 | "type": "string", 1026 | "enum": ["NW", "NE", "SW", "SE"] 1027 | } 1028 | ], 1029 | "additionalItems": false 1030 | } 1031 | ``` 1032 | 1033 | `"additionalItems"` 항목이 `false`라면 1034 | 1035 | > [1600, "Pennsylvania", "Avenue", "NW"] 1036 | > 1037 | > ​[1600, "Pennsylvania", "Avenue"] 1038 | 1039 | 정확히 4개인 항목과 하나 부족한 것은 검증에 성공하지만, 1040 | 1041 | > [1600, "Pennsylvania", "Avenue", "NW", "Washington"] 1042 | 1043 | 하나 더 많은 것은 실패하게 됩니다. 1044 | 1045 | ##### Length 1046 | 1047 | 목록의 길이는 `minItems` 와 `maxItems`로 지정할 수 있습니다. 1048 | 1049 | ```json 1050 | { 1051 | "type": "array", 1052 | "minItems": 2, 1053 | "maxItems": 3 1054 | } 1055 | ``` 1056 | 1057 | > [1, 2] 1058 | > 1059 | > [1, 2, 3] 1060 | 1061 | 는 검증에 성공하지만, 1062 | 1063 | > [] 1064 | > 1065 | > [1] 1066 | > 1067 | > [1,2,3,4] 1068 | 1069 | 는 검증에 실패합니다. 1070 | 1071 | 1072 | ##### Uniqueness 1073 | 1074 | 목록에 각 항목의 내용이 중복되지 않도록 하는 것으로 `uniqueItems` 키워드를 `true`로 설정하여 이용할 수 있습니다. 1075 | 1076 | ``` json 1077 | { 1078 | "type": "array", 1079 | "uniqueItems": true 1080 | } 1081 | ``` 1082 | 라는 JSON 스키마가 있다면, 1083 | 1084 | > [1, 2, 3, 4, 5] 1085 | > 1086 | > [] 1087 | 1088 | 빈 목록을 포함하여 검증에 성공하지만, 1089 | 1090 | > [1, 2, 3, 3, 4] 1091 | 1092 | 는 중복된 항목 3이 있으므로 실패합니다. 1093 | 1094 | 1095 | ### boolean 1096 | 1097 | 블리언 형식은 `true` 또는 `false`를 갖는 형식입니다. (파이썬의 `True` 및 `False` 와는 달리 모두 소문자 임을 주의합니다) 1098 | 1099 | ```json 1100 | { "type": "boolean" } 1101 | ``` 1102 | 1103 | > true 1104 | > 1105 | > false 1106 | 1107 | 는 검증에 성공하고 1108 | 1109 | > "true" 1110 | > 1111 | > 0 1112 | 1113 | 은 실패합니다. 1114 | 1115 | 1116 | ### null 1117 | 1118 | null 형식은 아무런 값도 가지고 있지 않다라는 것을 나타냅니다. (파이썬의 None 객체와 유사하다 생각하면 됩니다. JavaScript의 null 의미입니다) 1119 | 1120 | ```json 1121 | { "type": "null" } 1122 | ``` 1123 | 라고 JSON 스키마가 정의되어 있다면 1124 | 1125 | > null 1126 | 1127 | 은 성공하지만, 1128 | 1129 | > false 1130 | > 1131 | > 0 1132 | > 1133 | > "" 1134 | 1135 | 등은 모두 실패합니다. 1136 | 1137 | 1138 | ## Generic keywords 1139 | 1140 | 이곳에서는 JSON 형식에서 가능한 기타 키워드 등을 기술합니다. 1141 | 1142 | ### Metadata 1143 | 1144 | JSON 스키마에는 `title`, `description`, 및 `default`라는 키워드가 있어 직접 검증에 이용되지는 않지만 스키마의 일부분을 정의하는 메타 속성을 정의합니다. 1145 | 1146 | `title` 과 `description` 키워드는 문자열 값을 가집니다. 해당 스키마의 제목과 설명을 기술하면 됩니다. 1147 | 1148 | `defaul` 키워드는 해당 속성의 디폴트 값을 지정합니다. 아직 많은 JSON 스키마 검증 프로그램이 이 키워드를 제대로 지원하지 않지만 특정 키-값이 빠졌을 때 디폴트 값을 갖는 키-값 이 적용되도록 JSON 처리기가 동작하도록 할 수 있습니다. 1149 | 1150 | 다음과 같이 적용됩니다. 1151 | 1152 | ```json 1153 | { 1154 | "title" : "Match anything", 1155 | "description" : "This is a schema that matches anything.", 1156 | "default" : "Default value" 1157 | } 1158 | ``` 1159 | 1160 | ### 열거형 값 (Enumerated values) 1161 | 1162 | `enum` 키워드는 주어진 세트의 값으로만 구성될 때 이용할 수 있습니다. 중복되지 않으며 적어도 하나 이상의 값을 가지고 있는 목록으로 기술됩니다. 1163 | 1164 | ``` json 1165 | { 1166 | "type": "string", 1167 | "enum": ["red", "amber", "green"] 1168 | } 1169 | ``` 1170 | 1171 | > "red" 1172 | 1173 | 는 검증에 성공하지만, 1174 | 1175 | > "blue" 1176 | 1177 | 는 검증에 실패합니다. 1178 | 1179 | `type`을 가지지 않고 `enum` 만을 이용하여 서로 다른 자료형의 값 세트를 정할 수 있습니다. 1180 | 1181 | ```json 1182 | { 1183 | "enum": ["red", "amber", "green", null, 42] 1184 | } 1185 | ``` 1186 | 이라고 JSON 스키마가 있다면 1187 | 1188 | > "red" 1189 | > 1190 | > null 1191 | > 1192 | > 42 1193 | 1194 | 는 모두 검증에 성공하지만, 1195 | 1196 | > 0 1197 | 1198 | 은 검증에 실패합니다. 1199 | 1200 | ```json 1201 | { 1202 | "type": "string", 1203 | "enum": ["red", "amber", "green", null] 1204 | } 1205 | ``` 1206 | 와 같이 섞여 있는 경우도 있는데 1207 | 1208 | > "red" 1209 | 1210 | 는 검증에 성공하지만, 1211 | 1212 | > null 1213 | 1214 | 이 오면 `"type": "string"` 에 의해 검증은 실패하게 됩니다. 1215 | 1216 | 1217 | ### 스키마 결합 1218 | 1219 | JSON 스키마는 스키마 정의를 결합아여 사용할 수 있습니다. 이것은 여러 JSON 스키마 파일이나 JSON 트리를 서로 결합하는 것을 의미하지 않고 대신 여러 정의 어떤 식으로 이용하는가를 정의합니다. 1220 | 1221 | 예를 들어 다음과 같은 `anyOf` 라는 키워드를 이용하여 1222 | 1223 | ```json 1224 | { 1225 | "anyOf": [ 1226 | { "type": "string", "maxLength": 5 }, 1227 | { "type": "number", "minimum": 0 } 1228 | ] 1229 | } 1230 | ``` 1231 | 라고 JSON 스키마를 지정하면 두 개의 조건을 만족하는 것이면 모두 검증에 성공합니다. 1232 | 1233 | 즉, 1234 | 1235 | > "short" 1236 | > 1237 | > 12 1238 | 1239 | 는 두 조건에 모두 만족하는 것이므로 검증에 성공하지만, 1240 | 1241 | > "too long" 1242 | > 1243 | > -5 1244 | 는 두 조건에 만족하지 않으므로 검증에 실패합니다. 1245 | 1246 | 이런 결합 키워드로는 다음과 같이 세 가지가 있습니다. 1247 | 1248 | * `allOf`: 결합된 하위 스키마 모두를 만족해야 검증에 성공합니다 1249 | * `anyOf`: 결합된 하위 스키마 중에 하나로도 만족하면 검증에 성공합니다. 1250 | * `oneOf`: 결합된 하위 스키마 중에 단 하나의 조건에만 만족해야 검증에 성공합니다. 1251 | 1252 | 또한 1253 | 1254 | * `not`: 주어진 스키마가 거짓인 경우 검증에 성공합니다 1255 | 1256 | #### allOf 1257 | 1258 | ```json 1259 | { 1260 | "allOf": [ 1261 | { "type": "string" }, 1262 | { "maxLength": 5 } 1263 | ] 1264 | } 1265 | ``` 1266 | 위와 같은 JSON 스키마가 있으면 1267 | 1268 | > "short" 1269 | 1270 | 은 검증에 성공하고 1271 | 1272 | > "too long" 1273 | 1274 | 은 검증에 실패합니다. 1275 | 1276 | 그런데 만약 1277 | 1278 | ```json 1279 | { 1280 | "allOf": [ 1281 | { "type": "string" }, 1282 | { "type": "number" } 1283 | ] 1284 | } 1285 | ``` 1286 | 라고 주었다면 1287 | 1288 | > "No way" 1289 | > 1290 | > -1 1291 | 1292 | 모두 조건을 동시에 만족하지 않으므로 검증에 실패합니다. 1293 | 1294 | `allOf`, `anyOf` 또는 `oneOf`에 기술된 하위 스키마 목록에서 서로 서로 관계가 없다는 것을 알 필요가 있다. 반면 `allOf` 스키마 결합이 객체 지향 프로그램에서의 상속과 같은 개념으로 확장에 이용된지 않음을 알아야 한다. 예를 들어 다음과 같이 `definitions` 섹션에서 주소 스키마를 가지고 있고 주소 type이 또한 필요하다면, 1295 | 1296 | ``` json 1297 | { 1298 | "definitions": { 1299 | "address": { 1300 | "type": "object", 1301 | "properties": { 1302 | "street_address": { "type": "string" }, 1303 | "city": { "type": "string" }, 1304 | "state": { "type": "string" } 1305 | }, 1306 | "required": ["street_address", "city", "state"] 1307 | } 1308 | }, 1309 | 1310 | "allOf": [ 1311 | { "$ref": "#/definitions/address" }, 1312 | { "properties": { 1313 | "type": { "enum": [ "residential", "business" ] } 1314 | } 1315 | } 1316 | ] 1317 | } 1318 | ``` 1319 | 1320 | 와 같이 JSON 스키마가 만들어져 있고, 1321 | 1322 | ```json 1323 | { 1324 | "street_address": "1600 Pennsylvania Avenue NW", 1325 | "city": "Washington", 1326 | "state": "DC", 1327 | "type": "business" 1328 | } 1329 | ``` 1330 | 는 검증에 성공합니다. 1331 | 1332 | 하지만, 1333 | 1334 | ```json 1335 | { 1336 | "definitions": { 1337 | "address": { 1338 | "type": "object", 1339 | "properties": { 1340 | "street_address": { "type": "string" }, 1341 | "city": { "type": "string" }, 1342 | "state": { "type": "string" } 1343 | }, 1344 | "required": ["street_address", "city", "state"] 1345 | } 1346 | }, 1347 | 1348 | "allOf": [ 1349 | { "$ref": "#/definitions/address" }, 1350 | { "properties": { 1351 | "type": { "enum": [ "residential", "business" ] } 1352 | } 1353 | } 1354 | ], 1355 | 1356 | "additionalProperties": false 1357 | } 1358 | ``` 1359 | 와 같이 ` "additionalProperties": false`가 포함되면 위에 성공했던 JSON 자료는 실패하게 됩니다. 뿐만 아니라 어떤 JSON 데이터도 모두 실패합니다. 1360 | 1361 | #### anyOf 1362 | 1363 | ```json 1364 | { 1365 | "anyOf": [ 1366 | { "type": "string" }, 1367 | { "type": "number" } 1368 | ] 1369 | } 1370 | ``` 1371 | 위와 같이 `anyOf`를 사용하면 1372 | 1373 | > "Yes" 1374 | > 1375 | > 42 1376 | 1377 | 는 검증에 성공하고 1378 | 1379 | > { "Not a": "string or number" } 1380 | 1381 | 는 object 이므로 검증에 실패합니다. 1382 | 1383 | #### oneOf 1384 | 1385 | ``` json 1386 | { 1387 | "oneOf": [ 1388 | { "type": "number", "multipleOf": 5 }, 1389 | { "type": "number", "multipleOf": 3 } 1390 | ] 1391 | } 1392 | ``` 1393 | 이면 1394 | 1395 | > 10 1396 | > 1397 | > 9 1398 | 1399 | 는 5의 배수 이거나 3의 배수라 검증에 성공하지만, 1400 | 1401 | > 2 1402 | 1403 | 는 5와 3의 배수가 모두 아니므로 실패하고, 1404 | 1405 | > 15 1406 | 1407 | 는 5와 3의 배수가 동시에 되므로 (하나만 만족하는 조건에 위배되어) 실패합니다. 1408 | 1409 | 위의 스키마는 다음과 같이 표현될 수도 있습니다. 1410 | 1411 | ```json 1412 | { 1413 | "type": "number", 1414 | "oneOf": [ 1415 | { "multipleOf": 5 }, 1416 | { "multipleOf": 3 } 1417 | ] 1418 | } 1419 | ``` 1420 | 1421 | #### not 1422 | 1423 | ``` json 1424 | { "not": { "type": "string" } } 1425 | ``` 1426 | 라는 JSON 스키마가 정의되어 있다면, 1427 | 1428 | > 42 1429 | > 1430 | > { "key": "value" } 1431 | 1432 | 는 문자열이 아니므로 검증에 성공하지만 1433 | 1434 | > "I am a string" 1435 | 1436 | 는 문자열이라 검증에 실패합니다. 1437 | 1438 | ### $schema 키워드 1439 | 1440 | JSON 스키마의 표준 혹은 버전 등을 명시하는데 사용됩니다. 모든 JSON 스키마는 `$schema` 를 시작에 포함하도록 권고됩니다. 1441 | 1442 | ```json 1443 | "$schema": "http://json-schema.org/schema#" 1444 | ``` 1445 | 1446 | ### 정규식 1447 | 1448 | ``` json 1449 | { 1450 | "type": "string", 1451 | "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 1452 | } 1453 | ``` 1454 | 라고 하면 1455 | 1456 | > "555-1212" 1457 | > 1458 | > "(888)555-1212" 1459 | 1460 | 은 검증에 성공하고 1461 | 1462 | > "(888)555-1212 ext. 532" 1463 | > 1464 | > "(800)FLOWERS" 1465 | 1466 | 은 검증에 실패합니다. 1467 | 1468 | 1469 | ## 복잡한 스키마 구성 1470 | 1471 | 일반 프로그램을 작성할 때에도 재사용할 함수나 코드를 잘 구조화하고 이를 이용하는 것 처럼 JSON 스키마도 구성할 수 있습니다. 이런 방식을 잘 사용하여 복잡하고 어려운 구조도 보다 잘 구조화 할 수 있습니다. 1472 | 1473 | ### Reuse 1474 | 1475 | 다음과 같이 일반 고객을 담고 있는 스키마를 구성하고 있습니다. 1476 | 1477 | > 노트: 재사용은 draft 3에는 없고 draft 4에 나타납니다. 1478 | 1479 | ```json 1480 | { 1481 | "type": "object", 1482 | "properties": { 1483 | "street_address": { "type": "string" }, 1484 | "city": { "type": "string" }, 1485 | "state": { "type": "string" } 1486 | }, 1487 | "required": ["street_address", "city", "state"] 1488 | } 1489 | ``` 1490 | 재사용을 목적으로 하면 `definitions` 키워드를 이용합니다. 1491 | 1492 | ``` json 1493 | { 1494 | "definitions": { 1495 | "address": { 1496 | "type": "object", 1497 | "properties": { 1498 | "street_address": { "type": "string" }, 1499 | "city": { "type": "string" }, 1500 | "state": { "type": "string" } 1501 | }, 1502 | "required": ["street_address", "city", "state"] 1503 | } 1504 | } 1505 | } 1506 | ``` 1507 | 1508 | 그러면 `$ref` 키워드를 이용하여 해당 스키마를 사용합니다. 1509 | 1510 | ```json 1511 | { "$ref": "#/definitions/address" } 1512 | ``` 1513 | 1514 | `$ref`의 값은 JSON 포인터라고 불리우는 해당 스키마의 위치를 나타냅니다. 1515 | 1516 | > 노트: JSON 포인터는 XML에서의 XPath와 같은 목적으로 사용되지만 문법은 차이가 있습니다. 1517 | 1518 | `#` 기호는 현재 문서를 나타내고 각 구분자는 `/` 기호를 이용합니다. 따라서 `"#/definitions/address"`가 의미하는 것은 1519 | 1520 | 1. 현재 문서의 최상위로 간다 1521 | 2. `"definitions"` 이라는 키를 찾아 들어간다 1522 | 3. 그 아래에 `"address"`라는 키를 가진 객체에 위치한다 1523 | 1524 | `$ref`는 또는 URI와 같은 기술이 가능합니다. 따라서 1525 | 1526 | ```json 1527 | { "$ref": "definitions.json#/address" } 1528 | ``` 1529 | 라고 하여 `definitions.json` 파일에서 최상위 (`#`)에서 `address` 키의 값을 참조합니다. 1530 | 1531 | ```json 1532 | { 1533 | "$schema": "http://json-schema.org/draft-04/schema#", 1534 | 1535 | "definitions": { 1536 | "address": { 1537 | "type": "object", 1538 | "properties": { 1539 | "street_address": { "type": "string" }, 1540 | "city": { "type": "string" }, 1541 | "state": { "type": "string" } 1542 | }, 1543 | "required": ["street_address", "city", "state"] 1544 | } 1545 | }, 1546 | 1547 | "type": "object", 1548 | 1549 | "properties": { 1550 | "billing_address": { "$ref": "#/definitions/address" }, 1551 | "shipping_address": { "$ref": "#/definitions/address" } 1552 | } 1553 | } 1554 | ``` 1555 | 1556 | 위와 같이 정의하면, ​` "billing_address"` 및 `"shipping_address"`의 키가 해당 address의 object 구조를 갖습니다. 1557 | 1558 | ``` json 1559 | { 1560 | "shipping_address": { 1561 | "street_address": "1600 Pennsylvania Avenue NW", 1562 | "city": "Washington", 1563 | "state": "DC" 1564 | }, 1565 | "billing_address": { 1566 | "street_address": "1st Street SE", 1567 | "city": "Washington", 1568 | "state": "DC" 1569 | } 1570 | } 1571 | ``` 1572 | 1573 | 위의 내용은 검증에 성공합니다. 1574 | 1575 | ### id 속성 1576 | 1577 | `id` 속성은 두 가지 목적을 갖습니다. 1578 | * 특정 스키마의 고유한 ID를 정의합니다 1579 | * `$ref` 로 URL에 정의되어 질 수 있는 base가 됩니다 1580 | 1581 | 만약 `foo.bar` 라는 도메인을 가지고 있다면 다음과 같이 고유한 `id`를 URL 형식으로 가지고 있을 수 있습니다. 1582 | 1583 | ```json 1584 | "id": "http://foo.bar/schemas/address.json" 1585 | ``` 1586 | 1587 | 위의 것은 어디에서 다운로드 받을 수 있는가를 나타내는 URL 정보 뿐만 아니라 특정 스키마의 고유 정보를 나타냅니다. 1588 | 1589 | 그리고 또 다른 활용 방법으로써 `id` 속성을 이용하여 상대 경로의 내용을 `$ref`로 이용할 수 있습니다. 1590 | 1591 | 예를 들어, 1592 | 1593 | ```json 1594 | { "$ref": "person.json" } 1595 | ``` 1596 | 라고 지정하면 address.json 이 있었던 위치에서 person.json을 참조합니다. 즉, `http://foo.bar/schemas/person.json`를 참조합니다. (로컬에 있었어도 마찬가지 입니다) 1597 | 1598 | 1599 | ### 확장 (Extending) 1600 | 1601 | `$ref`의 진정한 확장성은 `allOf`, `anyOf` 및 `oneOf`과 같은 스키마 결합 기능과 함께 사용하면 더 배가됩니다. 1602 | 1603 | 위의 예에서 처럼, 1604 | 1605 | ``` json 1606 | "shipping_address": { "$ref": "#/definitions/address" } 1607 | ``` 1608 | 라고 참조하는 대신 1609 | 1610 | ```json 1611 | "shipping_address": { 1612 | "allOf": [ 1613 | // Here, we include our "core" address schema... 1614 | { "$ref": "#/definitions/address" }, 1615 | 1616 | // ...and then extend it with stuff specific to a shipping 1617 | // address 1618 | { "properties": { 1619 | "type": { "enum": [ "residential", "business" ] } 1620 | }, 1621 | "required": ["type"] 1622 | } 1623 | ] 1624 | } 1625 | ``` 1626 | 위와 같이 `allOf` 키워드를 이용하여 기존의 `address` 에 `type` 이라는 것을 더 확장할 수 있습니다. 1627 | 1628 | 위에 이야기 한 것을 모두 하나의 스키마로 표현해 보면, 1629 | 1630 | ```json 1631 | { 1632 | "$schema": "http://json-schema.org/draft-04/schema#", 1633 | 1634 | "definitions": { 1635 | "address": { 1636 | "type": "object", 1637 | "properties": { 1638 | "street_address": { "type": "string" }, 1639 | "city": { "type": "string" }, 1640 | "state": { "type": "string" } 1641 | }, 1642 | "required": ["street_address", "city", "state"] 1643 | } 1644 | }, 1645 | 1646 | "type": "object", 1647 | 1648 | "properties": { 1649 | "billing_address": { "$ref": "#/definitions/address" }, 1650 | "shipping_address": { 1651 | "allOf": [ 1652 | { "$ref": "#/definitions/address" }, 1653 | { "properties": 1654 | { "type": { "enum": [ "residential", "business" ] } }, 1655 | "required": ["type"] 1656 | } 1657 | ] 1658 | } 1659 | } 1660 | } 1661 | ``` 1662 | 과 같이 확장 스키마를 정할 수 있으며, 1663 | 1664 | ```json 1665 | { 1666 | "shipping_address": { 1667 | "street_address": "1600 Pennsylvania Avenue NW", 1668 | "city": "Washington", 1669 | "state": "DC", 1670 | "type": "business" 1671 | } 1672 | } 1673 | ``` 1674 | 과 같은 JSON 데이터는 검증에 성공하지만, 1675 | 1676 | ```json 1677 | { 1678 | "shipping_address": { 1679 | "street_address": "1600 Pennsylvania Avenue NW", 1680 | "city": "Washington", 1681 | "state": "DC" 1682 | } 1683 | } 1684 | ``` 1685 | 과 같은 결과는 검증에 실패합니다. 1686 | 1687 | ## [관련 저자](https://spacetelescope.github.io/understanding-json-schema/credits.html) 1688 | 1689 | ## 역자정보 [채문창](https://www.facebook.com/mcchae) 1690 | 1691 | 어느분께는 도움이 되셨기를... 1692 | --------------------------------------------------------------------------------