class: center, middle ## Introduce # JPA 2.2, JSON-B,JSON-P ## Specification
.right[이명현 (Naver Corp)] .right[https://github.com/mhyeon-lee/ksug-json] --- class: middle ### JPA 2.2 1. @Repeatable makred 2. Java 8 Date and Time types 3. Stream for query result 4. AttributeConverters to support CDI injection
### JSON-B, JSON-P 1. Json Serialize, Deserialize 개념 원리 2. JSON-B Specification 3. What's new in JSON-P 4. JSON-B, JSON-P 구현체들 5. Java JSON benchmark --- class: center, middle ## JPA 2.2 Specification #### JSR-338 (2017.08) --- class: middle ## What's new in JPA 2.2 - @Repeatable makred > https://github.com/javaee/jpa-spec/issues/115 - Java 8 Date and Time types > https://github.com/javaee/jpa-spec/issues/63 - Stream for query result > https://github.com/javaee/jpa-spec/issues/99 - AttributeConverters to support CDI injection > https://github.com/javaee/jpa-spec/issues/109 --- class: middle ### JPA 2.2 @Repeatable makred #### javax.persistences - @AssociationOverride, @AttributeOverride - @Convert - @JoinColumn, @PrimaryKeyJoinColumn, @MapKeyJoinColumn - @NamedQuery, @NamedNativeQuery - @NamedEntityGraph, @NamedStoredProcedureQuery - @PersistenceContext, @PersistenceUnit - @SecondaryTable, @SqlResultSetMapping - @SequenceGenerator, @TableGenerator --- class: middle ### 불필요하게 묶어놓은 Annotation ``` java @Entity @NamedQueries({ @NamedQuery(name = "Project.findByName", query = "SELECT p FROM Project p WHERE p.name = :name"), @NamedQuery(name = "Project.findByPolicy", query = "SELECT p FROM Project p WHERE p.policy = "policy") }) public class Project { ... } ```
### @Repeatable ``` java @Entity @NamedQuery(name = "Project.findByName", query = "SELECT p FROM Project p WHERE p.name = :name"), @NamedQuery(name = "Project.findByPolicy", query = "SELECT p FROM Project p WHERE p.policy = "policy") public class Project { ... } ``` --- class: middle ## Hinberate 5.2 @Repeatable makred #### org.hibernate.annotation package - @AnyMetaDef - @ColumnTransformer - @FetchProfile - @Filter, @FilterDef, @FilterJoinTable - @GenericGenerator - @JoinColumnOrFormula - @NamedQuery, @NamedNativeQuery - @Table - @Tuplizer - @TypeDef --- class: middle ### Java 8 Date and Time types mapping support
- java.time.LocalDate - java.time.LocalTime - java.time.LocalDateTime - java.time.OffsetTime - java.time.OffsetDateTime
- java.time.ZonedDateTime 은 time_zone 문제로 보류 - Hibernate 5.2 에서는 위의 time 및 ZonedDateTime 지원 - 단, 원하는 시간대 설정을 위해 `hibernate.jdbc.time_zone=UTC` 설정 필요 - MySQL: useLegacyDatetimeCode=false --- class: middle ### Stream for query result
- javax.persistence.Query, javax.persistence.TypedQuery interface - Stream 을 반환하는 `getResultStream()` 메소드 추가
``` java default Stream getResultStream() { return getResultList().stream(); } ``` --- class: middle ### Hibernate with stream result
- 단순히 List 를 Stream 으로 변환하는 것은 메모리에 List 를 모두 바인딩한 후 메모리에 적재하게 된다. - Hibernate 5.2 에서는 org.hibernate.query.Query 인터페이스에 Stream 을 반환하는 `stream()` 메소드가 추가됨 - `stream()` 메소드에서는 ScrollableResult 를 반환하여, 필요한 결과만 메모리에 적재 > org.hibernate.query.internal.AbstractProduceQuery - Hinberate 가 JPA 2.2 지원시, `stream()` 메소드로 `getResultStream()` 구현에 활용할 것 - Spring-Data-Jpa 에서 구현체로 Hinbernate를 사용하면, CrudRepository 에서 Stream 응답으로 `stream()` 메소드 사용한다. ``` java @Transactional(readOnly = true) public interface IssueRepository extends JpaRepository
{ @QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE)) @Query("select i from Issue i") Stream
readAll(); } ``` --- class: middle ### Stream result 사용시 고려사항
- ResultSet.TYPE_FORWARD_ONLY - Hibernate 의 stream() 에서 설정됨 - ResultSet.CONCUR_READ_ONLY - @Transactional(readOnly=true) - Stream 에서 결과를 읽을때도 Transaction Scope 이 유지되어야 한다. - 사용하는 DBMS 에 따른 최적화 및 주의사항을 고려해야 한다. > MySQL: stmt.setFetchSize(Integer.MIN_VALUE);
- 결과 집합을 최소화하는 연산은 데이터베이스에서 최적화 되어 있다. - filter(where), paging(limit, offset), sorting(order by) 등은 java stream 이 아닌 `query` 에서 처리해야 한다. - 단, 메모리 적재와 관련된 최적화에 활용할 수 있다. (파일 쓰기, batch 작업 등) --- class: middle ### Exporting results of large query as a CSV file - PAGING ``` java PrintWriter out = response.getWriter(); int page = 0; Page
issuePage; do { issuePage = issueRepository.findAll(new PageRequest(page, PAGE_SIZE)); for (Issue issue : issuePage) { String line = issueToCsv(issue.getContent()); out.write(line); out.write("\n"); } entityManager.clear(); page++; } while (issuePage.hasNext()); out.flush(); ``` .center[![page-result](./imgs/page-result.jpg)] --- class: middle ### Exporting results of large query as a CSV file - STREAM ``` java PrintWriter out = response.getWriter(); Stream
issueStream = issueRepository.readAll(); issueStream.forEach(issue -> { String line = issueToCsv(issue); out.write(line); out.write("\n"); entityManager.detach(issue); }); out.flush(); ``` .center[![scroll-result](./imgs/scroll-result.jpg)] --- class: middle ### AttributeConverters to support CDI injection - CDI 가 enabled 라면, AttributeConverter 에 Context 를 주입할 수 있다. - @PostConstruct, @PreDestory 를 지원한다. - Spring-Data-Jpa 에서 구현 지원이 필요하다. ``` java @Converter(autoApply = false) public class MyAttributeConverter implements AttributeConverter
{ @Autowired private Encryptor encryptor; @Override public String convertToDatabaseColumn(String obj) { return encryptor.encrypt(obj); } @Override public MyObject convertToEntityAttribute(String s) { return encryptor.decrypt(s); } } ``` --- class: center, middle ## JSON-B, JSON-P --- class: middle ### JSON new Specifications
#### JSON-B - Java API for `JSON Binding` - JSR-367 Specification - javax.json.bind-api 1.0 (2017.07.07) - http://json-b.net/
#### JSON-P - Java API for `JSON Processing` - JSR-374 Specification - javax.json-api 1.1 (2017.05.24) - https://javaee.github.io/jsonp/ --- class: center, middle #### JSON Binding 과 Processing 을 이해하기 위한 ## Serialize, Deserialize 개념 원리 #### 가장 많이 사용하는 Jackson 을 기준으로 알아보자. --- class: middle ## ObjectMapper - Jackson Mapping Component --- class: middle ## jackson-databind - SerializerProvider * `JsonSerializer`를 관리하고 실행한다. - DeserializationContext * `JsonDeserializer`를 관리하고 실행한다.
- JsonSerializer * Object를 `JsonGenerator`를 사용하여 serialize 한다. - JsonDeseriazlier * String(Json)을 `JsonParser`를 사용하여 Object 로 Deserialize 한다. --- class: middle ## jackson-core (streaming) - JsonFactory * `JsonGenerator`, `JsonParser` 를 생성한다.
- JsonGenerator * `Writer(OutputStream)`에 Object 를 Json 형식으로 generate 한다. * key, value 를 `{"key" : "value"}` 포맷으로 쓴다. - JsonParser * `Reader(InputStream)`로 부터 Json 형식의 key, value 를 parsing 한다. * String(Json) 에서 `"key", "value"` 를 읽는다. --- class: center ### Jackson Serialize ![jackson-serialize](./imgs/jackson-serialize.jpg) --- class: center #### `ObjectMapper`에게 변환하고자 하는 Object를 넘긴다. ![jackson-serialize](./imgs/jackson-serialize2.jpg) --- class: center #### `JsonFactory`는 `JsonGenerator`를 생성한다. ![jackson-serialize](./imgs/jackson-serialize3.jpg) --- class: center #### `SerializerProvider`는 적절한 `JsonSerializer`를 찾는다. ![jackson-serialize](./imgs/jackson-serialize4.jpg) --- class: center #### `JsonSerializer`는 `JsonGenerator`를 사용해서 Object를 JSON 으로 변환한다. ![jackson-serialize](./imgs/jackson-serialize5.jpg) --- class: center #### `ObjectMapper`는 JSON 문자열을 반환한다. ![jackson-serialize](./imgs/jackson-serialize6.jpg) --- class: middle #### `ObjectMapper`는 #### `JsonSerializer`로 generate 대상 및 방식을 결정하고, `(data-bind)` #### `JsonGenerator`로 write를 수행한다. `(streaming)`
![jackson-serialize-databind-streaming](./imgs/jackson-serialize-binding-streaming.jpg) --- class: center ### Jackson Deserialize ![jackson-deserialize](./imgs/jackson-deserialize.jpg) --- class: center #### `ObjectMapper`에게 JSON과 변환하고자 하는 Object Type을 넘긴다. ![jackson-deserialize](./imgs/jackson-deserialize2.jpg) --- class: center #### `JsonFactory`는 `JsonParser`를 생성한다. ![jackson-deserialize](./imgs/jackson-deserialize3.jpg) --- class: center #### `DeserializerContext`는 적절한 `JsonDeserializer`를 찾는다. ![jackson-deserialize](./imgs/jackson-deserialize4.jpg) --- class: center #### `JsonDeSerializer`는 Object를 생성하고, `JsonParser`를 사용해 Parsing 결과를 셋팅한다. ![jackson-deserialize](./imgs/jackson-deserialize5.jpg) --- class: center #### `ObjectMapper`는 Object를 반환한다. ![jackson-deserialize](./imgs/jackson-deserialize6.jpg) --- class: middle #### `ObjectMapper`는 #### `JsonDeserializer`로 parsing 대상 및 방식을 결정하고, `(data-bind)` #### `JsonParser`로 read를 수행한다. `(streaming)`
![jackson-deserialize-databind-streaming](./imgs/jackson-deserialize-databind-streaming.jpg) --- class: middle ### JSON Spec 으로 변경합니다.
#### jackson-databind -> JSON-B #### jackson-core(streaming) -> JSON-P --- class: middle ## JSON-B
### Jsonb, JsonbProvider (javax.json.bind) - ~~ObjectMapper~~ - Jsonb ``` java // ObjectMapper objectMapper = new ObjectMapper(); JsonbProvider provider = JsonbProvider.provider(); Jsonb jsonb = provider.create().build(); // serialize // String json = objectMapper.writeValueAsString(obj); String json = jsonb.toJson(obj); // deserialize // Object obj = objectMapper.readValue(json, type); Object obj = jsonb.fromJson(json, type); ``` --- class: middle ### SerializationContext (javax.json.bind.serializer) - ~~SerializerProvider~~ - SerializationContext ``` java // DefaultSerializerProvider provider = ...; // provider.defaultSerializeValue(obj, generator); SerializationContext serContext = ...; serContext.serialize(generator, obj); ```
### JsonbSerializer - ~~JsonSerializer~~ - JsonbSerializer ``` java // JsonSerializer ser = ...; // ser.serialize(obj, generator, provider); JsonbSerializer ser = ...; ser.serialize(obj, generator, serContext); ``` --- class: middle ### DeserializationContext (javax.json.bind.serializer) - ~~com.fasterxml.jackson.databind.DeserializationContext~~ - javax.json.bind.serializer.DeserializationContext ``` java // com.fasterxml.jackson.databind.DeserializationContext desrContext = ...; // desrContext.readValue(parser, type); javax.json.bind.serializer.DeserializationContext desrContext = ...; desrContext.deserialize(type, parser); ```
### JsonbDeserializer - ~~JsonDeserializer~~ - JsonbDeserializer ``` java // JsonDeserializer desr = ...; // desr.deserialize(parser, desrContext); JsonbDeserializer desr = ...; desr.deserialize(parser, desrContext, type); ``` --- class: middle ## JSON-P
### JsonProvider (javax.json.spi) - ~~JsonFactory~~ - JsonProvider ``` java // JsonFactory jsonFactory = new MappingJsonFactory(); JsonProvider provider = JsonProvider.provider(); // generator // JsonGenerator generator = jsonFactory.createGenerator(writer); JsonGenerator generator = provider.createGenerator(writer); JsonGeneratorFactory generatorFactory = provider.createGeneratorFactory(config); // parser // JsonParser parser = jsonFactory.createParser(reader); JsonParser parser = provider.createParser(reader); JsonParserFactory parserFactory = provider.createParserFactory(config); ``` --- class: middle ### JsonGenerator (javax.json.stream) - ~~com.fasterxml.jackson.core.JsonGenerator~~ - javax.json.stream.JsonGenerator ``` java // com.fasterxml.jackson.core.JsonGenerator generator = // jsonFactory.createGenerator(writer); // generator.writeStringField(key, value); javax.json.stream.JsonGenerator generator = provider.createGenerator(writer); generator.write(key, value); ```
### JsonParser (javax.json.stream) - ~~com.fasterxml.jackson.core.JsonParser~~ - javax.json.stream.JsonParser ``` java // com.fasterxml.jackson.core.JsonParser parser = // jsonFactory.createParser(reader); // String key = parser.getCurrentName(); // String value = parser.getValueAsString(); javax.json.stream.JsonParser parser = provider.createGenerator(writer); String value = parser.getString(); ``` --- class: center ### JSON-B, JSON-P serialize ![jsr-serialize](./imgs/jsr-serialize.jpg) --- class: center ### JSON-B, JSON-P deserialize ![jsr-deserialize](./imgs/jsr-deserialize.jpg) --- class: center, middle ![json-b](./imgs/json-b.jpg) ## JSON-B Specification #### comarison with other frameworks #### (Jackson, Gson, Genson) --- class: center, middle ## JSON-B Default Mapping --- class: middle ### Default Mapping - Optional (jackson with Jdk8Module)
- Optional.of("content") * JSON-B : "content" * Jackson : "content" * Gson : "content" * Genson : {"present":true}
- Optional.empty() * JSON-B : - * jackson : null * gson : - * genson : {"present":false} --- class: middle ### Default Mapping - Date
- new Date(); * JSON-B : "2017-11-22T13:31:48.501" * Jackson : 1511357508501 * Gson : "Nov 22, 2017 10:31:48 PM" * Genson : 1511357508501
- ZonedDateTime.now() (jackson with JavaTimeModule) * JSON-B : "2017-11-22T22:35:55.569+09:00[Asia/Seoul]" * jackson : 1511357755.569000000 * gson : {"dateTime":{"date":{"year":2017,"month":11, ...}} * genson : {"chronology":{},"dayOfMonth":22, ...} --- class: middle ### Default Mapping - Classes
- `public`, `protected` 접근자의 nested class, static nested class 지원 (MUST) - `Anonymous` 클래스는 serialization 만 지원한다 - deserialization 시 `public` 또는 `protected` 접근자의 no-argument 생성자가 존재해야 한다 - Enum 은 serialize 시 `name()` 을 사용하고, deserialize 시 `valueOf(String str)` 을 사용한다. - interface, abstract 같은 다형성을 요구하는 타입은 deserialization이 지원되지 않는다 (Collections, Number 는 지원) --- class: middle ### Default Mapping - Fields
- `final` 필드 serialize 지원 (MUST) - `final` 필드 deserialize 미지원 (MUST) - `static`, `transient` 필드 serialize / deserialize 미지원 (MUST) - serialize 시 `NULL` 값은 skip - deserialize 시 `NULL` 값은 null 셋팅 (단, Optional일 경우 empty() 셋팅) - deserialize 시 JSON 에 존재하지 않는 필드는 skip - serialize 시 속성 순서는 사전 순서 (단, 부모 클래스의 필드는 앞에 위치한다.)
### Default Mapping - Scope - public getter / setter - public fields with no getter / setter - public field and no-public getter ignored --- class: middle ### Scope and Field Access Strategy
- getters / setters 사용 * JSON-B, Jackson, Genson - public getter / setter 만 허용 * JSON-B - private field 사용 * Gson - Virtual fields 사용 * JSON-B, Jackson, Genson --- class: center, middle ## JSON-B Annotation --- class: middle ### Property Name - JSON-B : `@JsonbProperty` (FIELD, METHOD, PARAMETER) - Jackson : `@JsonProperty` (FIELD, METHOD, PARAMETER) - Gson : `@SerializedName` (FIELD, METHOD) - Genson : `@JsonProperty` (FIELD, METHOD, PARAMETER)
### Property Order - JSON-B : `@JsonbPropertyOrder` (LEXICOGRAPHICAL, ANY, REVERSE) - Jackson : `@JsonPropertyOrder` --- class: middle ### Ignore, Visibility - JSON-B > `@JsonbTransient` (FIELD, METHOD) > `@JsonbVisibility` (TYPE, PACKAGE) - Jackson > `@JsonIgnore` (FIELD, METHOD, CONSTRUCTOR) > `@JsonIgnoreProperties` (FIELD, METHOD, CONSTRUCTOR) > `@JsonAutoDetect` (TYPE) - Gson > `@Expose` (FIELD) - Genson > `@JsonIgnore` (FIELD, METHOD, CONSTRUCTOR) --- class: middle ### Null Handling - JSON-B > serializes null by default : No > hanling annotation : `@JsonNillable` - Jackson > serializes null by default : Yes > hadling annotation : `@JsonInclude(Include.NON_NULL)` - Gson > serializes null by default : No - Genson > serializes null by default : Yes --- class: middle ### Etc - `@JsonbCreator` - `@JsonbDateFormat` - `@JsonbNumberFormat` - `@JsonbTypeAdapter` - `@JsonbTypeSerializer` - `@JsonbTypeDeserializer` --- class: center, middle ![json-p](./imgs/json-p.jpg) ## JSON-P Specification --- class: middle ### JSR-353 (1.0)
- JsonValue - JsonObject - JsonString - JsonNumber - JsonArray - JsonStructure
- JsonObjectBuilder - JsonArrayBuilder
- JsonWriter - JsonReader --- class: middle ### Reading/Writing JsonObject ``` java // Create Json and print JsonObject json = Json.createObjectBuilder() .add("name", "Falco") .add("age", BigDecimal.valueOf(3)) .add("bitable", Boolean.FALSE).build(); String result = json.toString(); System.out.println(result); // output {"name":"Falco","age":3,"bitable":false} // Read back JsonReader jsonReader = Json.createReader(new StringReader(result)); JsonObject jobj = jsonReader.readObject(); System.out.println(jobj); // Output {"name":"Falco","age":3,"bitable":false} ``` --- class: middle ## What's new in JSON-P ##### javax.json-api 1.0 (JSR-353) -> 1.1 (JSR-374) - JsonPatch - JsonPointer - JsonMergePatch - JsonUtil - JsonCollectors --- class: middle #### HTTP PATCH - [RFC-5789](https://tools.ietf.org/html/rfc5789) (2010.03) - Apply partial modifications to a resource #### JSON Pointer - [RFC-6901](https://tools.ietf.org/html/rfc6901) (2013.04) #### JSON Patch - [RFC-6902](https://tools.ietf.org/html/rfc7386) (2013.04) - Content-Type: `application/json-patch+json` - http://jsonpatch.com/ #### JSON Merge Patch - [RFC-7396](https://tools.ietf.org/html/rfc7386) (2014.10) - Content-Type: `application/merge-patch+json` --- class: middle ### JSON Patch - The original document ``` json { "bibidi": "babidi", "foo": "bar" } ``` - The patch ``` json [ { "op": "add", "path": "/hello", "value": ["majin"] }, { "op": "replace", "path": "/bibidi", "value": "buu" }, { "op": "remove", "path": "/foo" } ] ``` - The result ``` json { "bibidi": "buu", "hello": ["majin"] } ``` --- class: middle ### JSON Patch : Operations
* add: `value`를 `path` 위치에 추가하거나, 배열에 insert 수행 * remove: `path` 위치의 값을 제거하거나, 배열에서 제거 * replace: `path` 위치의 값을 `value` 로 교체 * copy: `from` 위치의 값을 `path` 위치로 복사 * move: `from` 위치의 값을 `path` 위치로 이동 * test: `path` 위치의 값이 value 인지 테스트 --- class: middle ### JsonPatch : JSON-P * < T extends JsonStructure > T apply(T target); ``` java // String 의 문자열 "", escape 생략 JsonArray patchArray = Json.createReader(new StringReader( [{"op": "add", "path": "/tags/0", "value": "buu"}, {"op": "replace", "path": "/name", "value": "babidi"}] )) .readArray(); JsonPatch jsonPatch = Json.createPatch(patchArray); JsonObject target = Json.createReader(new StringReader( {"name": "bibidi", "description": "majin"} )) .readObject(); JsonObject result = jsonPatch.apply(target); System.out.println(result.toString()); {"name": "babidi", "description": "majin", "tags": ["buu"]} ``` --- class: middle ### JSON Pointer - JSON 문서 내의 특정 값을 식별하기 위한 문자열 형식 - JSON Patch 에서 작업을 수행할 문서의 부분을 지정하는데 사용한다. (path)
- JSON Pointer 는 string token 을 `/` 로 분리한다. - 빈 문자열은 JSON 문서의 `root` 를 가리킨다. - path `/` 는 root 를 가리키지 않으며, root 의 "" 키를 가리킨다. - key 의 `~` 또는 `/` 가 포함되어 있을때는, `~0`/ `~1` 를 사용한다. > `{ "foo/bar~": "baz" }` -> `/foo~1bar~0` - `-` 는 배열의 마지막을 가리킨다. > `/majins/-/name` : "buu" ``` json { "majins": [ { "name": "bibidi" }, { "name": "babidi"}, { "name": "buu"} ] } ``` --- class: middle ### JsonPointer : JSON-P * JsonValue getValue(JsonStructure target); ``` java // String 의 문자열 "", escape 생략 JsonObject target = Json.createReader(new StringReader( {"name": "buu", "tags": ["majin"]} )) .readObject(); JsonPointer pointer = Json.createPointer("/name"); JsonValue result = pointer.getValue(target); System.out.println(result.toString()); "buu" ``` --- class: middle ### JSON Merge Patch - The original document ``` json { "bibidi": "babidi", "foo": "bar", "satan": "angel" } ``` - The patch ``` json { "bibidi": "buu", "satan": null, "hello": ["majin"] } ``` - The result ``` json { "bibidi": "buu", "foo": "bar", "satan": null, "hello": ["majin"] } ``` --- class: middle ### JsonMergePatch : JSON-P * JsonValue apply(JsonValue target); ``` java // String 의 문자열 "", escape 생략 JsonObject mergePatch = Json.createReader(new StringReader( {"bibidi": "buu", "satan": null, "hello": ["majin"]} )) .readObject(); JsonMergePatch jsonMergePatch = Json.createMergePatch(mergePatch); JsonObject target = Json.createReader(new StringReader( {"bibidi": "babidi", "foo": "bar", "satan": "angel"} )) .readObject(); JsonValue result = jsonMergePatch.apply(target); System.out.println(result.toString()); { "bibidi": "buu", "foo": "bar", "hello": ["majin"]} ``` --- class: center, middle ## 우리가 원하는 PATCH
![want-patch](./imgs/want-patch.jpg) --- class: center, middle ## JSON-B, JSON-P 가 지원하는 PATCH
![real-patch](./imgs/real-patch.jpg) --- class: middle ## JsonPatch, JsonMergePatch ### Pros - Resource 에 대한 부분 수정이 가능하므로, API 갯수를 줄일 수 있다. - 부분 수정이 가능하면서, Version 충돌을 줄일 수 있다. - JsonPatch 는 기존 표현하기 어려웠던 동사(op)를 표현할 수 있다. - JsonMergePatch 는 이해하기 쉽고 Client 에서 간결하게 사용할 수 있다. ### Cons - API Provider 입장에서 구현이 쉽지 않음 - `JsonPatch` 는 client 입장에서 장황한 스펙 - `JsonMergePatch` 는 null 과 undefined 구분이 필요 - `JsonMergePatch` 는 nested 속성 patch 를 표현하기 어려운 한계 - Object의 serialize, deserialize 과정에 binding 설정에 따른 데이터 손실 및 의도하지 않은 변경이 발생할 수 있다. - 도메인 객체 속성 변경에 대한 도메인 로직이 개입하기 어렵다. --- class: middle ### JsonUtil.toJson()
- JSON String 을 JsonValue 로 변환해주는 메소드 ``` java String json = {"bibidi": "babidi"}; JsonValue jsonValue = JsonUtil.toJson(json); System.out.println(jsonValue.toString()); {"bibidi": "babidi"}; ``` --- class: middle ### JsonCollectors
- JSON-P 에서는 JsonObject 에 대한 stream collector 구현체를 제공한다.
- toJsonArray() : stream 을 `JsonArray` 로 collect 지원 - toJsonObject() : stream 을 `JsonObject` 로 collect 지원 - groupingBy() : stream 을 특정 key 에 대한 value 로 `grouping` 하는 collect 지원 --- class: middle ### JsonCollectors.toJsonArray()
``` java // String 의 문자열 "", escape 생략 // Given List
jsons = Arrays.asList( {"name":"project1","policy":"public","creator":{"name":"user1","age":30}}, {"name":"project2","policy":"private","creator":{"name":"user2","age":40}}, {"name":"project3","description":"desc","creator":{"name":"user3","age":50}}); // When JsonArray expected = jsons.stream() .map(JsonUtil::toJson) .collect(JsonCollectors.toJsonArray()); // Then assertEquals(jsons.get(0), expected.get(0).toString()); assertEquals(jsons.get(1), expected.get(1).toString()); assertEquals(jsons.get(2), expected.get(2).toString()); ``` --- class: middle ### JsonCollectors.toJsonObject()
``` java // Given Map
map = new HashMap<>(); map.put("name", "project"); map.put("description", "project desc"); map.put("policy", "public"); // When JsonObject expected = map.entrySet().stream() .map(e -> new SimpleEntry
)( e.getKey(), Json.createValue(e.getValue)))) .collect(JsonCollectors.toJsonObject()); // Then assertEquals(map.get("name"), expected.getString("name")); assertEquals(map.get("description"), expected.getString("description")); assertEquals(map.get("policy"), expected.getString("policy")); ``` --- class: middle ### JsonCollectors.groupingBy()
``` java // String 의 문자열 "", escape 생략 // Given List
jsons = Arrays.asList( {"name":"project1","policy":"public","creator":{"name":"user1","age":30}}, {"name":"project2","policy":"private","creator":{"name":"user2","age":40}}, {"name":"project3","description":"desc","creator":{"name":"user3","age":50}}); // When JsonArray expected = jsons.stream() .map(JsonUtil::toJson) .collect(JsonCollectors.groupingBy( jsonValue -> jsonValue.asJsonObject().getString("policy"))); // Then assertSame(2, expected.getJsonArray("public").size()); assertEquals(jsons.get(0), expected.getJsonArray("public").get(0).toString()); assertEquals(jsons.get(2), expected.getJsonArray("public").get(1).toString()); assertSame(1, expected.getJsonArray("private").size()); assertEquals(jsons.get(1), expected.getJsonArray("private").get(0).toString()); ``` --- class: center, middle ## JSON-B, JSON-P 구현체들
#### JSON-B, JSON-P 는 명세와 interface 를 제공하며, #### 실제 구현 및 동작은 구현체에 의존한다. --- class: middle ### JSON-P 구현체
- `org.glassfish:javax.json:1.1` (Default Provider) - `org.apache.johnzon:johnzon-core:1.1.4` - `com.owlike.genson:1.4` (JSR-353 only) * `com.fasterxml.jackson.datatype:jackson-datatype-jsr353:2.9.0` > not implemented, support JSR-353 --- class: middle ### JSON-B 구현체
- `org.eclipse:yasson:1.0.1` (Default Provider) - `org.apache.johnzon:johnzon-jsonb:1.1.4` --- class: middle ### Spring Boot 2.0 - M5
https://github.com/spring-projects/spring-boot/pull/9648 ![jhoeller](./imgs/jhoeller.jpg) --- class: middle ### Spring Boot 2.0 - JsonbAutoConfiguration
- Apache Johnzon 1.1.4 ![johnzon](./imgs/johnzon-dependency.jpg) ``` java package org.springframework.boot.autoconfigure.jsonb; @Configuration @ConditionalOnClass(Jsonb.class) public class JsonbAutoConfiguration { @Bean @ConditionalOnMissingBean public Jsonb jsonb() { return JsonbBuilder.create(); } } ``` --- class: middle ### Spring Boot 2.0 - JsonbTester ``` java @RunWith(SpringRunner.class) @JsonTest public class SpringBootJsonTests { @Autowired private JsonbTester
jsonb; private User object; private String json; @Before public void setUp() { this.object = new User("user", 30); this.json = "{\"name\" : \"user\", \"age\" : 30 }"; } @Test public void jasonbSerialize() throws IOException { JsonContent
expected = this.jsonb.write(this.object); assertThat(expected).extractingJsonPathStringValue("@.name") .isEqualTo(this.object.getName()); assertThat(expected).extractingJsonPathNumberValue("@.age") .isSameAs(this.object.getAge()); } @Test public void jasonbDeserialize() throws IOException { ObjectContent
expected = this.jsonb.parse(this.json); User user = expected.getObject(); assertThat(user.getName()).isEqualTo(this.object.getName()); assertThat(user.getAge()).isEqualTo(this.object.getAge()); } } ``` --- class: middle ### JSON-B, JSON-P 사용시 주의점
- `JsonbProvider`, `JsonProvider` 는 static 메소드로 적절한 구현체를 찾는다. - `ServiceLoader` 를 사용해 `META-INF.services` 에 명시된 구현체를 사용하기 때문에, 직접 구현체를 지정하는게 좋다. - 특히 `Jsonb` 는 `JsonProvider` 에 의존하고 `Jsonb` 구현체 내부에서 필요에 따라 `JsonProvider.provider()` 를 호출한다. - `META-INF.services` 에 사용 구현체를 지정해 놓지 않으면, dependency 가 추가되면서 사용되는 구현체가 변경될 수 있다. --- class: middle ### META-INF.services 에 구현체 지정
- `/resources/META-INF/services/javax.json.bind.spi.JsonbProvider` ``` java org.apache.johnzon.jsonb.JohnzonProvider # org.eclipse.yasson.JsonBindingProvider ```
- `/resources/META-INF/services/javax.json.spi.JsonProvider` ``` java org.glassfish.json.JsonProviderImpl # org.apache.johnzon.core.JsonProviderImpl # com.owlike.genson.ext.jsr353.GensonJsonProvider - not supported jsonb # com.github.pgelinas.jackson.javax.json.spi.JacksonProvider - not supported jsonb ``` --- class: middle ### JSON-B and Genson dependency issues
- `Genson` 의 경우 라이브러리 내부에서 `META-INF.services` 로 자신의 JsonProvider 구현체를 지정하고 있다. - META-INF 에 JsonProvider 구현체를 지정하지 않았다면, `Genson` 의 dependency 가 추가되는 것만으로, JsonProvider 구현체가 `Genson` 으로 변경될 수 있다. - `Genson` 은 JSR-374(1.1) 을 구현하지 않았기 때문에, `Jsonb` 에서 processing 중 에러를 만날 수 있다. > UnsupportedOperationException (JSR-374 에서 추가된 default method) - JSON-B 를 사용하는 프레임워크에서도 JsonbProvider 에 대해 비슷한 문제 발생 가능 --- class: center, middle ## Java JSON benchmark - https://github.com/fabienrenaud/java-json-benchmark - https://github.com/cowtowncoder/java-json-performance-benchmarks --- class: center, middle # Q & A ---