엄범

https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations

https://www.tutorialspoint.com/jackson_annotations/jackson_annotations_jsonanysetter.htm

 

ObjectMapper 는 생성 비용이 꽤 비싸기 때문에 멤버변수 / bean / static으로 처리 !!!

  • ObjectMapper는 Thread Safe하기 때문에 굳이 매번 생성해서 따로 쓸 필요가 없음.
  • 그리고 생성 비용이 비싼 편이라 매번 생성하도록 하는 경우 138배까지 성능 차이가 발생할 수 있음. (마스크 대란)
  • 그래서 reuse를 권장하고 있음.
  • 멤버 변수도 괜찮고 static 도 괜찮지만, Bean으로 DI 받아 쓰는 것은 다음과 같은 장점을 가지고 있음.

 

 

POJO <> json String

```java

import com.fasterxml.jackson.databind.ObjectMapper;

 

// POJO -> JSON String

String jsonStr = objectMapper.writeValueAsString(new BillingKey("example_key"))

//JSON String -> POJO

BillingKey pojo = objectMapper.readValue(jsonStr, BillingKey.class);

```

*** readValue는 json String으로부터. convertValue는 Object로부터.

 

Map <> POJO

```java

Map<String, String> salesRequestJson = ...;

Payment payment = objectMapper.convertValue(salesRequestJson, Payment.class);

 

private static final TypeReference<Map<String, String>> MAP_TYPE_REFERENCE =
    new TypeReference<Map<String, String>>() {};

 

Map<String, String> requestMap = objectMapper.convertValue(request, MAP_TYPE_REFERENCE);

```

 

Annotation

이름 변환 JSON <> POJO <> DB

```java

@JsonProperty("error_code")

private String errorCode;

```

  • 물론 DB의 경우 `` as``를 쓰는 방법도 있지만... as를 쓰는 경우 sql마다 다 as를 적어줘야하고, 혹시라도 이름이 변경되면 이들을 찾아다니면서 다 변경해주어야 해서 좀 더 번거롭다.
  • 하지만 as를 쓰면 WHERE 문에서 as 뒤의 변수명을 사용할 수 있어 이게 더 나을 수도 있음.

 

이름 변환 2

deserialize 시에만 이름을 바꿔서 받고 싶다면?

jackson은 deserialize 시 setter를 보므로

```java

private String originName;

public void setParamName(String paramName) { originName = paramName; }

```

또는

```java

private String originName;

@JsonSetter("paramName")

public void setOriginName(String originName) { originName = originName; }

@JsonGetter("originName")  // 이걸 안하면 왜인지 serialize 시에도 paramName이 되어 버린다.

public String getOriginName() { return originName; }

 

+++ @AllArgsConstructor는 없어야 하고 @NoArgsConstructor 는 있어야 함. 이렇게 안하면 no suitable HttpMessageConverter ... Error남.

```

  • 왜 @JsonGetter("originName")까지 해줘야 되는지는 다음을 보면 나와있음. (@JsonSetter도 마찬가지.)
  • https://github.com/FasterXML/jackson-databind/issues/1519
  • 그래서 결국 한쪽만 필요하더라도 set/get 양쪽 다 붙여줘야 하기 때문에... @JsonGetter/@JsonSetter 대신 @JsonProperty를 get/set에 붙여주어도 똑같다.
  • 근데 이렇게 까지 해야하는 상황이면 보통은 받는 쪽에서 @JsonAlias 써서 처리하는게 나을 때가 많다

 

serialize 시에만 이름을 다르게 바꿔서 json으로 만들고 싶다면?

jackson은 serialize 시 getter를 보므로

```java

private String originName;

public String getNewName() { return originName; }

```

또는

```java

private String originName;

@JsonGetter("newName")

public String getOriginName() { return originName; }

@JsonSetter("originName")  // 이걸 안하면 못받는다. 이유는 위 설명 참조.

public void setOriginName(String originName) { originName = originName; }

 

+++ @AllArgsConstructor는 없어야 하고 @NoArgsConstructor 는 있어야 함. 이렇게 안하면 no suitable HttpMessageConverter ... Error남.

```

 

클라이언트가 보내주는 JSON data 중 특정 key에 대해서는 Mapping을 제외

클라이언트가 임의로 이런 속성을 만들어서 보내게 되었을 때 이상한 값이 들어가는 것 방지, 

또는 로직 상 클라이언트에서 이 key:value를 보내지 않을 때.

```java

@JsonIgnore

private String method;

```

 

요청 받는 Dto와 응답 내려주는 Dto를 구분하는 경우 이런 식으로 아예 Ignore해버려도 상관 없지만,

하나의 Dto로 요청도 받고 응답도 받는 경우 요청에서는 무시하고 싶은데 응답에서는 JSON으로 내려주고 싶을 때

```java

@JsonProperty(access = JsonProperty.Access.READ_ONLY)

private String salesTime;

```

 

Null인 필드에 대해서는 key-value쌍을 만들지 않도록 함

```java

@JsonInclude(JsonInclude.Include.NON_NULL)

public class aaaa...

```

 

JSON string이 아니라, byte로 serialize하고 싶다면?

  • Serializable 인터페이스 구현.
  • 포함하고 싶지 않은 필드는 `` transient`` 키워드를 붙여준다. (Key=null 로 처리됨)

 

jackson ObjectMapper의 json -> data class 매핑 룰

`` @AllArgsConstructor``로 하면 생성자 통해 매핑되는데, 직접 정의한 all args constructor로 하면 400에러가 발생한다?!

=> Bytecode 확인해보니 `` @AllArgsConstructor``를 사용하면 `` @ConstructorProperties``가 자동으로 붙는다

그래서 확인해봤다.

 

```java

public class TestCls {

  @Setter @Getter

  private String pa;

  @Getter

  private String pa2;

 

  @ConstructorProperties({"pa"})

  public TestCls(String pa) {

    this.pa = "constructor init";

    this.pa2 = "constructor init";

}

```

```

요청 파라미터 : { "pa"="param init", "pa2"="param init" }

실행 결과 : pa=constructor init, pa2=param init

```

  1. @ConstructorProperties가 있다면, 이게 붙은 생성자 먼저 실행하여 초기화한다.
  2. 1에서 초기화 되지 않은 필드 중 식별 가능한 필드를 초기화한다.
    • jackson은 기본적으로 Getter/Setter에서 get/set prefix 잘라내고 맨앞 소문자로 만드는 것으로 필드를 식별한다.
      • 그래서 pa2가 @Getter만 붙어 있는데도 param init으로 초기화 된 것
      • Getter/Setter 둘 다 없다면 그 필드는 식별되지 않아 초기화 X
    • 식별한 필드 목록에 대해서, Setter 있으면 setter 호출해서 넣어주고 없다면 reflection으로 넣어준다.

 

*** @ConstructorProperties는 하나만 존재해야 한다. 여러개 있으면 conflict error 발생

 

기타

[Java] Enum to Json / Enum to Object

[Spring] RestTemplate은 어떻게 response Object를 DataType <T>로 변환하는가