Jackson annotations

Circlee7
19 min readAug 9, 2018

json관련 jackson 에서 지원하는 유용한 어노테이션 기능들을 살펴보자

jacskon annotation 에 대해 정리된 원문을 요약한다. + 추가

Read + Write Annotations

@JsonIgnore

-> Jackson이 해당 프로퍼티를 무시하도록 하는 역할을 한다.public class Test {

@JsonIgnore
public long id = 0;

public String name = null;
}
<-->{"name":"circlee"}

@JsonIgnoreProperties

@JsonIgnoreProperties

-> @JsonIgnore 와 같은 기능, 해당 클래스의 여러 필드리스트를 무시하기 위해 사용한다.@JsonIgnoreProperties({"firstName", "lastName"})
public class PersonIgnoreProperties {

public long personId = 0;

public String firstName = null;
public String lastName = null;

}
<-->{"firstName":"circlee7","lastName":"eldie"}

@JsonIgnoreType

-> JsonIgnoreType 은 해당타입이 사용되는 모든곳에서 무시되도록 한다.

@JsonBackReference

-> 부모자식 관계의 자식쪽에 작성함으로써 순환참조관계의 serialize 시 직렬화/역직렬화
시 참조하여 동작한다.
참고 : https://stackoverflow.com/questions/37392733/difference-between-jsonignore-and-jsonbackreference-jsonmanagedreference

@JsonManagedReference

-> 부모자식 관계의 부모쪽에 작성함으로써 순환참조관계의 serialize 시 직렬화/역직렬화
시 참조하여 동작한다.
참고 : https://stackoverflow.com/questions/37392733/difference-between-jsonignore-and-jsonbackreference-jsonmanagedreference

@JsonAutoDetect

@JsonAutoDetect

-> JsonAutoDetect는 특정접근제한자의 필드들을 추가하라고 Jackson에게 알려주는 역할을 한다.@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )
public class PersonAutoDetect {

private long personId = 123;
public String name = null;

}
JsonAutoDetect.Visibility 는 접근레벨에 맞는 상수가 정의되어있다.
ANY
, DEFAULT
, NON_PRIVATE
, NONE
, PROTECTED_AND_PRIVATE
, PUBLIC_ONLY

@JsonView


JsonView 는 어노테이션 인자로 넘겨진 View 에 해당하는 클래스로 구분되어 직렬화/역직렬화 시 설정된 JsonView 에 따라 매칭되는 프로퍼티들이 역/직렬화 대상에 포함된다.

@AllArgsConstructor
public @Data class Person {
private String name;

@JsonView(PersonView.NORMAL.class)
private String gender;

@JsonView(PersonView.PRIVATE.class)
private int age;

@JsonView(PersonView.ADDITIONAL.class)
private String address;

@JsonView({PersonView.ADDITIONAL.class, PersonView.HOBBIES.class})
private String hobbies;



public static class PersonView {

interface NORMAL{}

interface ADDITIONAL extends NORMAL{}

interface PRIVATE extends NORMAL{}

interface HOBBIES extends NORMAL{}
}
}

실행: ObjectMapper writer/reader를 사용시 원하는 View를 지정한다.
ObjectMapper om = new ObjectMapper();
Person p = new Person("eldi", "male", 10, "seoul", "basketball");
System.out.println(om.writeValueAsString(p));
System.out.println(om.writerWithView(PersonView.NORMAL.class).writeValueAsString(p));
System.out.println(om.writerWithView(PersonView.ADDITIONAL.class).writeValueAsString(p));
System.out.println(om.writerWithView(PersonView.PRIVATE.class).writeValueAsString(p));
System.out.println(om.writerWithView(PersonView.HOBBIES.class).writeValueAsString(p));

출력:
{"name":"eldi","gender":"male","age":10,"address":"seoul","hobbies":"basketball"} <-- view 지정 하지 않는경우 전체 프로퍼티가 직렬화된다.{"name":"eldi","gender":"male"}
<-- NORMAL 로 직렬화시에는 JsonView를 지정하지 않은 name과 view 와 매칭되는 gender 만 노출된다.
{"name":"eldi","gender":"male","address":"seoul","hobbies":"basketball"} <-- ADDITIONAL 로 직렬화 시에는 NORMAL 을 상속받았기 때문에 NORMAL, ADDITIONAL 해당하는 전체를 포함한다.{"name":"eldi","gender":"male","age":10}
<-- 위와 마찬가지로 PRIVATE 와 부모인 NORMAL 에 해당하는 프로퍼티를 포함한다.

{"name":"eldi","hobbies":"basketball"}
<-- hobbies 프로퍼티는 ADDITIONAL 과 HOBBIES VIEW 가 복수개로 정의되어 있어서 두개의 VIEW 모두에 포함된다.

Read Annotations (Json Object to Java Object : Deserialize)

@JsonSetter

-> Jackson은 Json Data와 Class 의 프로퍼티 이름이 일치해야 하지만 
JsonSetter 를 사용하여, Json Data의 특정 프로퍼티 이름으로 Class의 Setter 메소드에 지정할수있다.
{
"id" : 1234,
"name" : "John"
}
<-->public class Bag {

private Map<String, Object> properties = new HashMap<>();

@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}

public Object get(String fieldName){
return this.properties.get(fieldName);
}
}

@JsonAnySetter

-> JSON Object 의 모든 setter 메소드를 인지할수 없는 필드에 대해 
JsonAnySetter 어노테이션을 통해 Map<String, Object> 변수로 담을 수 있습니다.
{
"id" : 1234,
"name" : "John"
}
<-->public class Bag {

private Map<String, Object> properties = new HashMap<>();

@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}

public Object get(String fieldName){
return this.properties.get(fieldName);
}
}

@JsonCreator

-> JsonCreater 는 생성자메소드에 지정되어 JSON Object 의 필드 와 메소드의 파라미터 매칭을 통해 Java Object를 생성가능하게 한다.
@JsonSetter를 사용할수 없거나, 불변객체이기 때문에 setter 메소드가 존재하지 않을때
초기화 데이터 주입을 위해 유용하다.
{
"id" : 1234,
"name" : "John"
}
<-->public class PersonImmutable {

private long id = 0;
private String name = null;

@JsonCreator
public PersonImmutable(
@JsonProperty("id") long id,
@JsonProperty("name") String name ) {

this.id = id;
this.name = name;
}

public long getId() {
return id;
}

public String getName() {
return name;
}

}

@JacksonInject

-> JacksonInject 어노테이션은 Jackson 에의해 역직렬화된 Java Object에 공통정인 값을 주입할 수 있는 java Object의 프로퍼티를 지정한다.
예를들어 여러 소스에서 Person Json Object를 파싱할때에 , 이 Json Object 의 소스가 어디인지 주입을 통해 저장할수 있다.
public class PersonInject {

public long id = 0;
public String name = null;

@JacksonInject
public String source = null;

}
--- InjectableValues inject = new InjectableValues.Std().addValue(String.class, "jenkov.com");
PersonInject personInject = new ObjectMapper().reader(inject)
.forType(PersonInject.class)
.readValue(new File("data/person.json"));
inject 리더가 지정된 ObjectMapper 를 이용하여 파싱하게 되면 jacksonInject 어노테이션을 확인하여 필드의 타입기반 매칭으로 값을 주입한다.

@JsonDeserialize

-> JsonDeserialize 는 필드가 커스텀한 Deserializer class를 사용할수 있도록 한다. public class PersonDeserialize {

public long id = 0;
public String name = null;

@JsonDeserialize(using = OptimizedBooleanDeserializer.class)
public boolean enabled = false;
}
---
public class OptimizedBooleanDeserializer
extends JsonDeserializer<Boolean> {

@Override
public Boolean deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {

String text = jsonParser.getText();
if("0".equals(text)) return false;
return true;
}
}
JsonDeserializer<T t> 를 상속을 통해 구현할때 필드에 맞는 genericType을 지정하여 상속받고, desirialze 메소드를 작성한다.

Write Annotations (Java Object to Json Object : Serialize)

@JsonInclude

-> JsonInclude 는 특정한 상황? 상태? 의 필드만 Json Object 변경시 포함되도록 지정할수 있다. @JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {

public long personId = 0;
public String name = null;

}
JsonInclude.Include 는 아래와 같은 include 상수들이 지정되어 있고
NON_EMPTY의 경우 not null과 not empty를 의미한다.
java8 Optional 타입의 isAbsent 도 체크한다.
ALWAYS
, NON_NULL
, NON_ABSENT
, NON_EMPTY
, NON_DEFAULT
, USE_DEFAULTS

@JsonGetter

-> @JsonSetter 와 반대로 setter메소드에 지정되며 해당하는 이름으로 Json Object 의 data field 가 생성된다.public class PersonGetter {

private long personId = 0;

@JsonGetter("id")
public long personId() { return this.personId; }

@JsonSetter("id")
public void personId(long personId) { this.personId = personId; }

}

@JsonAnyGetter

-> @JsonAnySetter 와 반대 역할로써 Key, Value 형식의 Map을 리턴하는 메소드에 지정함으로써  Json Object의 프로퍼티로 작성되게끔 한다.public class PersonAnyGetter {

private Map<String, Object> properties = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
}
<-->
{
"map.key1" : "map.key1.value"
,"map.key2" : map.key2.value
...
}

@JsonPropertyOrder

->  JsonPropertyOrder는 Java Object가 Json 으로 직렬화 될때 필드의 순서를 지정할 수 있다.
(일반적으로 Jackson은 class 에서 필드를 발견하는 순서대록 직렬화 해버림)
@JsonPropertyOrder({"name", "personId"})
public class PersonPropertyOrder {

public long personId = 0;
public String name = null;

}

@JsonRawValue

-> JsonRawValue 는 특정필드에 지정되어, 해당 필드의 값이 raw하게 Json Output이 되도록 할 수 있습니다.public class PersonRawValue {

public long personId = 0;

public String address = "$#";
}
일반적으로 문자열은 Json Output 시에 쌍따옴표로 감싸진체 출려됩니다.{"personId":0,"address":"$#"}--- public class PersonRawValue {

public long personId = 0;

@JsonRawValue
public String address = "$#";
}
@JsonRawValue를 사용하면 쌍따움표 없이 그대로 출력됩니다.{"personId":0,"address":$#}하지만, 이것은 잘못된 Json형식입니다.---@JsonRawValue 는 Raw Json 문자열을 String 타입변수에 담아 출력할때 사용될수 있습니다.public class PersonRawValue {

public long personId = 0;

@JsonRawValue
public String address =
"{ \"street\" : \"Wall Street\", \"no\":1}";

}
{"personId":0,"address":{ "street" : "Wall Street", "no":1}}

@JsonValue

-> @JsonValue 를 지정함으로써 Jackson이 객체자체를 직렬화하지 않고 JsonValue 가 지정된 메소드를 호출하는것으로 대체할 수 있습니다.public class PersonValue {

public long personId = 0;
public String name = null;

@JsonValue
public String toJson(){
return this.personId + "," + this.name;
}

}
출력 :
"0,null"
쌍따옴표는 Jackson에 의해 붙여지게 됩니다.
반환된 문자열내의 쌍따옴표는 모두 이스케이프 처리됩니다.

@JsonSerialize

-> JsonSerialize를 이용하여 특정필드에 커스텀 serialzer를 지정할 수 있다.public class PersonSerializer {

public long personId = 0;
public String name = "John";

@JsonSerialize(using = OptimizedBooleanSerializer.class)
public boolean enabled = false;
}
public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> {

@Override
public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {

if(aBoolean){
jsonGenerator.writeNumber(1);
} else {
jsonGenerator.writeNumber(0);
}
}
}

Jackson 에서 다양한 어노테이션을 지원하기 때문에
적재적소에 잘 사용한다면 보다 간결한 코딩이 가능할것으로 생각된다.

물론, 무분별한 가독성을 해치거나 복잡도를 증가시키는 사용은 피해야겠다.

JsonSerialize/JsonDeserialize 어노테이션을 살펴보니 Type 도 target으로 설정되어있다.
해당 날짜타입의 클래스도 Jackson 레벨에서 특정패턴의 날짜형식으로 처리가 가능할것으로 보인다.
(Spring Converter와 별개로 확인을 위해)


출력:
{"name":"test","timeYmd":"2018-08-09 13:45:07"}
2018-08-09T13:45:07.551
2018-08-09T13:45:07 <- 파싱후의 LocalDateTime 객체이므로 이미 직렬화시 패턴에 의해 밀리세컨드는 절삭됨.

추가

@JsonIdentityReference

@JsonAppend

@JsonNaming

@JsonNaming 어노테이션은 프로퍼티 들의 직렬화 네이밍 전략을 선택하는데 사용되어진다.
이미 정의된 프로퍼티 네이밍 전략 들 또는 커스텀으로 작성된 전략을 사용할 수 있다.
default는 LOWER_CAMEL_CASE 이고.Jackson 라이브러리는 4가지의 프로퍼티 네이밍 전략들을 내장하고 있다.KEBAB_CASE: 이름 요소 들은 하이픈 기호로 구분되어진다.
LOWER_CASE: 모든 문자는 구분자 없는 소문자로 이루어진다.
SNAKE_CASE: 모든 문자는 소문자로, 이름 요소들 사이에 underscore 로 구분된다.
UPPER_CAMEL_CASE: 첫번째 문자를 포함한 모든 이름요소들은 대문자로 시작하고 소문자로 이어지며, 구분자는 없다.
ex>@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)|
public class NamingBean {
private int id;
private String beanName;
}

@JsonPropertyDescription

@JsonPOJOBuilder

@JsonTypeId

@JsonTypeIdResolver

@JacksonAnnotationsInside

아래 참고

https://www.baeldung.com/jackson-annotations

--

--