본문 바로가기

IT/getting started

Jackson 라이브러리 Annotation 총정리

반응형
 

Jackson Annotation Examples | Baeldung

The core of Jackson is basically a set of annotations - make sure you understand these well.

www.baeldung.com

 

GitHub - vljh246v/TIL: Today I learned

Today I learned. Contribute to vljh246v/TIL development by creating an account on GitHub.

github.com

0. 목차

1. Jackson 직렬화 Annotations

  • 직렬화 주석

1.1 @JsonAnyGetter

  • Map 필드를 사용할 때 유용하게 사용할 수 있음
  • Map에 있는 값들을 일반 필드 속성처럼 표시할 수 있음 
@Test
fun jsonAnyGetterTest() {

    class ExtendableBean(val name: String) {
        @get:JsonAnyGetter
        val properties: MutableMap<String, String> = mutableMapOf()
    }

    val bean = ExtendableBean("My bean")
    bean.properties["attr1"] = "val1"
    bean.properties["attr2"] = "val2"

    val result = ObjectMapper().writeValueAsString(bean)
    print(result)
    assertThat(result).doesNotContain("properties")
    assertThat(result).contains("attr1")
    assertThat(result).contains("attr2")
}
  • 출력 값을 아래와 같다.
  • @JsonAnyGetter를 사용하였을 때
{"name":"My bean","attr1":"val1","attr2":"val2"}
  • @JsonAnyGetter를 사용하지 않았을 때
{"name":"My bean","properties":{"attr1":"val1","attr2":"val2"}}
  • @JsonAnyGetter(enabled = false)와 같은 형태로 비활성화도 가능

 

1.2 @JsonGetter

  • 지정한 메소드를 getter메서드로 사용하기 위해 사용
  • @JsonProperty 주석의 대안
@Test
fun jsonGetterTest() {

    class MyBean(val name: String) {

        @JsonGetter("name")
        fun getMyName(): String {
            return "My name is $name"
        }
    }

    val bean = MyBean("My bean")

    val result = ObjectMapper().writeValueAsString(bean)
    print(result)
    assertThat(result).contains("My name is")
}
  • 출력 결과
{"name":"My name is My bean"}

 

1.3 @JsonPropertyOrder

  • 직렬화 시 속성의 순서를 지정할 수 있음
@Test
fun jsonPropertyOrderTest() {

    @JsonPropertyOrder("country", "name", "age")
    class MyBean(val name: String, val age: Int, val country: String)

    val bean = MyBean("My bean", 32, "KR")

    val result = ObjectMapper().writeValueAsString(bean)
    print(result)
    assertThat(result).contains("My bean")
    assertThat(result).contains(32.toString())
    assertThat(result).contains("KR")
}
  • 출력 결과
{"country":"KR","name":"My bean","age":32}
  • @JsonPropertyOrder(alphabetic = true)를 사용해 알파벳 순으로도 정렬할 수 있음
{"age":32,"country":"KR","name":"My bean"}

 

 

1.4 @JsonRawValue

  • 속성을 그대로 직렬화 하여 JSON 형태로 사용할 수 있다.
@Test
fun jsonRawValueTest() {

    class MyBean(val name: String, @JsonRawValue val json: String)

    val bean = MyBean("My bean",  "{\"attr\":false}")

    val result = ObjectMapper().writeValueAsString(bean)
    print(result)
    assertThat(result).contains("{\"attr\":false}")
}
  • 출력 결과 (사용)
{"name":"My bean","json":{"attr":false}}
  • 출력 결과 (미사용)
{"name":"My bean","json":"{\"attr\":false}"}

 

1.5 @JsonValue

  • 전체 인스턴스를 직렬 화하는 데 사용할 단일 메서드를 지정

1.5.1 일반 클래스에서 사용

@Test
fun jsonValueTest_class() {

    class MyBean(val name: String, val age: Int, val country: String) {

        @JsonValue
        fun getMyBean(): String {
            return "MyBean(name='$name', age=$age, country='$country')"
        }
    }

    val bean = MyBean("My bean", 32, "KR")

    val result = ObjectMapper().writeValueAsString(bean)
    print(result)
    assertThat(result).contains("My bean")
    assertThat(result).contains(32.toString())
    assertThat(result).contains("KR")
}
  • 출력 결과 (사용)
"MyBean(name='My bean', age=32, country='KR')"
  • 출력 결과 (미사용)
{"name":"My bean","age":32,"country":"KR","myBean":"MyBean(name='My bean', age=32, country='KR')"}

 

1.5.2. 열거형 타입에서 사용

@Test
fun jsonValueTest_enum() {
    val result = ObjectMapper().writeValueAsString(Type.TYPE_A)
    print(result)
    assertThat(result).contains("Type A")
}

enum class Type(val id: Int, private val typeName: String) {
    TYPE_A(1, "Type A"),
    TYPE_B(1, "Type B");

    @JsonValue
    fun getName(): String {
        return typeName
    }
}
  • 출력 결과 (사용)
"Type A"
  • 출력 결과 (미사용)
"TYPE_A"

 

1.6 @JsonRootName

  • Json을 한번 더 감싸는 root wrapper 이름을 설정할 수 있음
  • ObjectMapper에 SerializationFeature.WRAP_ROOT_VALUE 를 설정해주어야 한다.
@Test
fun jsonRootNameTest() {

    @JsonRootName(value = "bean")
    class MyBean(val name: String, val age: Int, val country: String)

    val bean = MyBean("My bean", 32, "KR")

    val mapper = ObjectMapper()
    mapper.enable(SerializationFeature.WRAP_ROOT_VALUE)
    val result = mapper.writeValueAsString(bean)

    print(result)
    assertThat(result).contains("bean")
    assertThat(result).contains("My bean")
    assertThat(result).contains(32.toString())
    assertThat(result).contains("KR")
}
  • 출력 결과
{"bean":{"name":"My bean","age":32,"country":"KR"}}
  • 주의할점은 SerializationFeature.WRAP_ROOT_VALUE 설정 후에 @JsonRootName을 설정하지 않으면 기본적으로 클래스의 이름으로 root 이름이 정의가 된다.
// @JsonRootName 사용, SerializationFeature.WRAP_ROOT_VALUE 적용
{"bean":{"name":"My bean","age":32,"country":"KR"}}

// @JsonRootName 미사용, SerializationFeature.WRAP_ROOT_VALUE 적용
{"MyBean":{"name":"My bean","age":32,"country":"KR"}}

 

1.7 @JsonSerialize

  • 엔티티를 마샬링 할 때 사용할 사용자 custom serialzer를 지정할 수 있게 해 줌
  • StdSerializer를 상속받아 custom serialzer를 작성한다.

 

  • 사용자 custom serialzer
class CustomDateSerializer(t: Class<Date>? = null) : StdSerializer<Date>(t) {
    companion object {
        private val formatter = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
    }
    @Throws(IOException::class, JsonProcessingException::class)
    override fun serialize(
        value: Date,
        generator: JsonGenerator,
        provider: SerializerProvider
    ) {
        generator.writeString(formatter.format(value))
    }
}
  • 사용 예제
@Test
fun jsonSerializeTest() {

    class MyBean(
        val name: String,
        @get:JsonSerialize(using = CustomDateSerializer::class) val date: Date)

    val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
    val toParse = "03-08-2022 12:18:00"
    val date  = df.parse(toParse)

    val bean = MyBean("My bean", date)

    val mapper = ObjectMapper()
    val result = mapper.writeValueAsString(bean)

    print(result)
    assertThat(result).contains(toParse)
}
  • 출력 결과
{"name":"My bean","date":"03-08-2022 12:18:00"}

 

 

2. Jackson 역직렬화 Annotations

  • 역직렬화 주석

 

2.1 @JsonCreator

  • 역직렬화시에 entity는 변경하지 않고 언마샬링 할 수가 있음

 

  • 역직렬화 대상 Json
{
    "id":1,
    "theName":"My bean"
}

 

  • 사용 예제
@Test
fun jsonCreatorTest() {
    class BeanWithCreator @JsonCreator constructor(
        @JsonProperty("id")  val id: Int,
        @JsonProperty("theName") val name: String
    ) {
        override fun toString(): String {
            return "BeanWithCreator(id=$id, name='$name')"
        }
    }

    val json = "{\"id\":1,\"theName\":\"My bean\"}"

    val mapper = ObjectMapper()
    val result = mapper.readerFor(BeanWithCreator::class.java)
        .readValue<BeanWithCreator>(json)

    print(result)
    assertThat(result.name).isEqualTo("My bean")
}

 

2.2 @JacksonInject

  • 어노테이션을 붙인 필드는 json 역직렬화를 통한 데이터가 아닌 inject를 해서 값을 가져올 때 사용

 

  • 사용 예제
@Test
fun jacksonInjectTest() {
    class BeanWithInject () {

        @JacksonInject("id1")
        val id1: Int? = null

        @JacksonInject("id2")
        val id2: Long? = null

        val name: String? = null
        override fun toString(): String {
            return "BeanWithInject(id1=$id1, id2=$id2, name=$name)"
        }
    }

    val json = "{\"name\":\"My bean\"}"

    val mapper = ObjectMapper()
    val inject = InjectableValues.Std()
        .addValue("id1", 1)
        .addValue("id2", 2L)

    val result = mapper.reader(inject)
        .forType(BeanWithInject::class.java)
        .readValue<BeanWithInject>(json)

    print(result)
    assertThat(result.name).isEqualTo("My bean")
    assertThat(result.id1).isEqualTo(1)
    assertThat(result.id2).isEqualTo(2L)
}
  • 보통 인터넷 예제들을 보면 addValue(Class<?> classKey, Object value) 메서드를 사용해 inject 하는 걸 볼 수 있음
  • 개인적으로 필드 이름을 명시적으로 적어서 inject 하는 게 더 좋다고 생각

 

2.3 @JsonAnySetter

  • JsonAnyGetter와 반대
  • 역직렬화시 정해지지 않은 json 필드들을 Map의 값으로 사용 가능

 

  • 역직렬화 대상 Json
{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

 

  • 사용 예제
@Test
fun jsonAnySetterTest() {
    class ExtendableBean {
        val name: String? = null
        val properties: MutableMap<String, String> = mutableMapOf()

        @JsonAnySetter
        fun add(key: String, value: String) {
            properties[key] = value
        }

        override fun toString(): String {
            return "ExtendableBean(name=$name, properties=$properties)"
        }
    }

    val json = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}"

    val mapper = ObjectMapper()
    val result = mapper.readerFor(ExtendableBean::class.java)
        .readValue<ExtendableBean>(json)

    print(result)
    assertThat(result.name).isEqualTo("My bean")
    assertThat(result.properties).containsEntry("attr2", "val2")
    assertThat(result.properties).containsEntry("attr1", "val1")
}

 

2.4 @JsonSetter

  • 어노테이션이 붙은 메서드를 필드의 setter 메서드로 사용
  • @JsonProperty 대안으로 사용 가능

 

  • 사용 예제
@Test
fun jsonSetterTest() {
    class MyBean {
        var myName: String? = null
        val id: Int? = null

        @JsonSetter("name")
        fun setTheName(theName: String){
            this.myName = theName
        }

        override fun toString(): String {
            return "MyBean(myName=$myName, id=$id)"
        }
    }

    val json = "{\"id\":1,\"name\":\"My bean\"}"

    val mapper = ObjectMapper()
    val result = mapper.readerFor(MyBean::class.java)
        .readValue<MyBean>(json)

    print(result)
    assertThat(result.myName).isEqualTo("My bean")
    assertThat(result.id).isEqualTo(1)
}

 

2.5 @JsonDeserialize

  • custome deserializer를 작성한다.
  • Date 타입 필드를 역직렬화 하는 경우가 있다고 가정

 

  • 사용 예제 (custome deserializer)
class CustomDateDeserializer(t: Class<Date>? = null): StdDeserializer<Date>(t) {
    companion object {
        private val formatter = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
    }

    @Throws(IOException::class, ParseException::class)
    override fun deserialize(jsonParser: JsonParser, context: DeserializationContext): Date {
        val date = jsonParser.text

        return formatter.parse(date)
    }
}

 

  • 사용 예제
@Test
fun jsonDeserializeTest() {
    class EventWithSerializer {
        var name: String? = null

        @JsonDeserialize(using = CustomDateDeserializer::class)
        var eventDate: Date? = Date()
    }

    val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
    val json = "{\"name\":\"demo\",\"eventDate\":\"20-12-2014 02:30:00\"}"

    val mapper = ObjectMapper()
    val result = mapper.readerFor(EventWithSerializer::class.java)
        .readValue<EventWithSerializer>(json)

    print(result)
    assertThat(result.name).isEqualTo("demo")
    assertThat(df.format(result.eventDate)).isEqualTo("20-12-2014 02:30:00")
}

 

2.6 @JsonAlias

  • 역직렬화시 속성에 대한 하나 이상의 대체 이름을 지정함

 

  • 사용 예제
@Test
fun jsonAliasTest() {
    class AliasBean {
        @JsonAlias(value = ["fName", "f_name"])
        var firstName: String? = null
        var lastName: String? = null

        override fun toString(): String {
            return "AliasBean(firstName=$firstName, lastName=$lastName)"
        }
    }

    val json = "{\"fName\": \"version\", \"lastName\": \"demo\"}"

    val mapper = ObjectMapper()
    val result = mapper.readerFor(AliasBean::class.java)
        .readValue<AliasBean>(json)

    print(result)
    assertThat(result.firstName).isEqualTo("version")
}

 

 

3. Jackson 속성 관련 Annotations

 

3.1 @JsonIgnoreProperties

  • 무시할 속성 또는 속성 목록을 표시하는 클래스 단위 어노테이션

 

  • 사용 예제
@Test
fun jsonIgnorePropertiesTest() {
    @JsonIgnoreProperties(value = ["firstName"])
    class BeanWithIgnore(
        var firstName: String? = null,
        var lastName: String? = null
    ) {
        override fun toString(): String {
            return "AliasBean(firstName=$firstName, lastName=$lastName)"
        }
    }

    val bean = BeanWithIgnore("jaehyun", "lim")

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("lim")
    assertThat(result).doesNotContain("jaehyun")
}

 

3.2 @JsonIgnore

  • 필드 수준에서 무시할 속성을 지정

 

  • 사용 예제
@Test
fun jsonIgnoreTest() {
    class BeanWithIgnore(
        @JsonIgnore
        var firstName: String? = null,
        var lastName: String? = null
    ) {
        override fun toString(): String {
            return "AliasBean(firstName=$firstName, lastName=$lastName)"
        }
    }

    val bean = BeanWithIgnore("jaehyun", "lim")

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("lim")
    assertThat(result).doesNotContain("jaehyun")
}

 

3.3 @JsonIgnoreType

  • 어노테이션을 적용한 클래스의 모든 속성을 무시

 

  • 사용 예제
@Test
fun jsonIgnoreTypeTest() {

    @JsonIgnoreType
    class Name(
        var firstName: String? = null,
        var lastName: String? = null
    ) {
        override fun toString(): String {
            return "Name(firstName=$firstName, lastName=$lastName)"
        }

    }

    class User(
        var id: String,
        var name: Name
    ) {
        override fun toString(): String {
            return "User(id='$id', name=$name)"
        }
    }

    val bean = User("1", Name("jaehyun", "lim"))

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("1")
    assertThat(result).doesNotContain("jaehyun")
    assertThat(result).doesNotContain("lim")
}

 

3.4 @JsonInclude

  • empty / null / default값이 있는 속성을 제외할 수 있음
  • 기본 사용법과 custom을 설정해서 사용 가능

 

  • 사용 예제 (NON_NULL)
@Test
fun jsonIncludeTest_NON_NULL() {
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    class MyBean(
        var firstName: String? = null,
        var lastName: String? = null
    ) {
        override fun toString(): String {
            return "MyBean(firstName=$firstName, lastName=$lastName)"
        }
    }

    val bean = MyBean("jaehyun", null)

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("jaehyun")
    assertThat(result).doesNotContain("lim")
}

 

  • 사용 예제(CUSTOM)
@Test
fun jsonIncludeTest_CUSTOM() {
    @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
    class MyBean(
        var firstName: String? = null,
        var lastName: String? = null,
        @get:JsonInclude(content = JsonInclude.Include.CUSTOM, contentFilter = PhoneNumberFilter::class)
        var privateInfo: Map<String, String> = mutableMapOf(),
        @get:JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = CompanyNameFilter::class)
        var companyName: String? = null,
    ) {
        override fun toString(): String {
            return "MyBean(firstName=$firstName, lastName=$lastName, privateInfo=$privateInfo, companyName=$companyName)"
        }
    }

    val bean = MyBean(
        "jaehyun",
        "",
        mapOf("phoneNumber" to "010-1111-2222", "address" to "seoul"),
        "NAVER"
    )

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("firstName")
    assertThat(result).contains("phoneNumber")
    assertThat(result).doesNotContain("lastName")
    assertThat(result).doesNotContain("companyName")
}

class PhoneNumberFilter {
    private val phoneNumberPattern: Pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}")

    override fun equals(other: Any?): Boolean {

        if (other == null || other !is String) {
            return false
        }
        return !phoneNumberPattern.matcher(other.toString()).matches()
    }

    override fun hashCode(): Int {
        return phoneNumberPattern.hashCode()
    }
}

class CompanyNameFilter {

    override fun equals(other: Any?): Boolean {
        if (other == null || other !is String) {
            return false
        }

        return !other.contentEquals("LINE")
    }

    override fun hashCode(): Int {
        return javaClass.hashCode()
    }
}
  • 결과
{"firstName":"jaehyun","privateInfo":{"phoneNumber":"010-1111-2222"}}

 

3.5 @JsonAutoDetect

  • 속성의 표시 여부에 관한 전반적인 정의를 할 수 있다.
  • @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)로 지정한다면 접근 지정자 레벨이 private이라도 표시
  • JsonAutoDetect.Visibility.ANY 외에도 PUBLIC_ONLY, PROTECTED_AND_PUBLIC등을 설정할 수 있다.

 

  • 사용 예제
@Test
fun jsonAutoDetectTest() {

    @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
    class MyBean(
        private var firstName: String? = null,
        var lastName: String? = null
    ) {
        fun getFullName(): String {
            return "$firstName $lastName"
        }

        override fun toString(): String {
            return "MyBean(firstName=$firstName, lastName=$lastName)"
        }
    }

    val bean = MyBean("jaehyun", "lim")

    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("firstName")
    assertThat(result).contains("lastName")
}

 

 

4. Jackson 다형성 Type 관련 Annotations

  • 다형성 처리를 위한 annotation
  • @JsonTypeInfo – 실제 클래스가 무엇인지 부가 정보 표시를 위한 annotation
  • @JsonSubTypes – 서브 클래스들을 모두 등록해주어야 사용 가능
  • @JsonTypeName – annotation이 달린 클래스에 사용할 이름을 정의

 

  • 사용 class
@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes(
    JsonSubTypes.Type(Dog::class, name = "dog"),
    JsonSubTypes.Type(Cat::class, name = "cat")
)
open class Animal {
    var name: String? = null
}

@JsonTypeName("dog")
class Dog : Animal() {
    var barkVolume = 0L
    override fun toString(): String {
        return "Dog(name='$name', barkVolume=$barkVolume)"
    }
}

@JsonTypeName("cat")
class Cat : Animal() {
    var isNeutering = true
    override fun toString(): String {
        return "Cat(name='$name', isNeutering=$isNeutering)"
    }
}

 

  • 사용 예제 (serialization)
@Test
fun jsonPolymorphicTest_Serialization() {

    val dog = Dog().apply {
        name = "dog"
        barkVolume = 10L
    }
    val cat = Cat().apply {
        name = "cat"
        isNeutering = true
    }

    var result = ObjectMapper().writeValueAsString(dog)
    print(result)
    assertThat(result).contains("type")
    assertThat(result).contains("dog")

    result = ObjectMapper().writeValueAsString(cat)
    print(result)
    assertThat(result).contains("type")
    assertThat(result).contains("cat")
}
  • JsonTypeInfo.Id.CLASS를 사용하면 실제 {pacakge 이름}.{class 이름} 형태로 사용할 수 있다.
  • property에는 사용할 type을 지정할 수 있다.
  • JsonTypeInfo.As.EXISTING_PROPERTY 를 사용해서 이미 가지고 있는 프로퍼티에 type을 표시할 수 있다.

 

  • 결과
{"type":"dog","name":"dog","barkVolume":10}{"type":"cat","name":"cat","neutering":true}

 

  • 사용 예제 (deserialization)
@Test
fun jsonPolymorphicTest_Deserialization() {

    val json = "{\"name\":\"lacy\",\"type\":\"dog\",\"barkVolume\":10}"
    val result = ObjectMapper().readValue(json, Animal::class.java)

    print(result)
    assertThat(result).isInstanceOf(Dog::class.java)
}

 

  • 결과
Dog(name='lacy', barkVolume=10)

 

 

5. 기타 일반적인 Jackson Annotations

 

5.1 @JsonProperty

  • json 속성 이름을 나타냄
  • getXXX, setXXX 외 지정하고 싶은 gettter, setter에 사용 가능

 

  • 사용 예제
@Test
fun jsonPropertyTest() {

    class MyBean{
        var id: String? = null
        var name: String? = null

        @JsonProperty("name")
        fun setTheName(theName: String){
            this.name = theName
        }

        @JsonProperty("name")
        fun getTheName(): String? {
            return this.name
        }

        override fun toString(): String {
            return "MyBean(id='$id', name='$name')"
        }
    }

    var bean = MyBean().apply {
        id = "1"
        name = "demo"
    }
    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("id")
    assertThat(result).contains("1")
    assertThat(result).contains("name")
    assertThat(result).contains("demo")

    bean = ObjectMapper().readValue(result, MyBean::class.java)
    print(result)
    assertThat(bean.id).contains("1")
    assertThat(bean.name).contains("demo")
}

 

5.2. @JsonFormat

  •  날짜/시간 값을 직렬화할 때 형식을 지정

 

  • 사용 예제
@Test
fun jsonFormatTest() {
    class MyBean(
        var name: String,
        @get:JsonFormat(
            shape = JsonFormat.Shape.STRING,
            pattern = "dd-MM-yyyy hh:mm:ss"
        )
        var date: Date
    ) {
        override fun toString(): String {
            return "MyBean(name='$name', date=$date)"
        }
    }

    val df = SimpleDateFormat("dd-MM-yyyy hh:mm:ss")
    df.timeZone = TimeZone.getTimeZone("UTC")

    val toParse = "15-08-2022 01:00:00"
    val parse = df.parse(toParse)
    val bean = MyBean("party", parse)
    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains(toParse)
}

 

5.3 @JsonUnwrapped

  • 직렬화 / 역직렬화시에 unwrapped/flatten 되어야 하는 값을 정의

 

  • 사용 예제
class UnwrappedUser(
    val id: Int,
    @get:JsonUnwrapped
    var name: Name
) {
    override fun toString(): String {
        return "UnwrappedUser(id=$id, name=$name)"
    }
}

class Name(
    val firstName: String,
    val lastName: String
) {
    override fun toString(): String {
        return "Name(firstName='$firstName', lastName='$lastName')"
    }
}

@Test
fun jsonUnwrappedTest() {

    val bean = UnwrappedUser(1, Name("demo", "lim"))
    val result = ObjectMapper().writeValueAsString(bean)

    print(result)
    assertThat(result).contains("demo")
    assertThat(result).contains("1")
    assertThat(result).doesNotContain("name")
}

 

5.4 @JsonView

  • 동일한 객체에서도 표현할 프로퍼티를 여러 조합으로 선택할 수 있게 한다.
  • 예를 들어서 ID와 PW가 존재하는 객체에서 일반 User 와 Admin에게 보여줘야 할 데이터는 다를 수 있다.

 

  • 사용 예제
class View {
    class User
    class Admin
}

class User {
    @JsonProperty("user_id")
    @JsonView(View.User::class, View.Admin::class)
    var userId: Long? = null

    @JsonProperty("password")
    @JsonView(View.Admin::class)
    var password: String? = null
}

@Test
fun jsonViewTest() {

    val bean = User()
        .apply {
            this.userId = 10L
            this.password = "1234"
        }

    var result = ObjectMapper()
        .writerWithView(View.User::class.java)
        .writeValueAsString(bean)

    print(result)
    assertThat(result).contains("user_id")
    assertThat(result).doesNotContain("password")

    result = ObjectMapper()
        .writerWithView(View.Admin::class.java)
        .writeValueAsString(bean)

    print(result)
    assertThat(result).contains("user_id")
    assertThat(result).contains("password")
}

 

5.4 @JsonManagedReference, @JsonBackReference

  • 객체와 객체가 1:N 과 같이 관계를 가지고 있고, json 변환 시 순환 참조가 문제가 될 때 사용

 

  • 사용 예제
class Parent {
    var id: Int? = null

    @JsonManagedReference
    var children: MutableList<Child> = mutableListOf()
    override fun toString(): String {
        return "Parent(id=$id, children=$children)"
    }
}

class Child {
    var id: Int? = null

    @JsonBackReference
    var parent: Parent? = null
    override fun toString(): String {
        return "Child(id=$id, parent=$parent)"
    }
}

@Test
fun jsonReferenceTest() {


    val parent = Parent().apply {
        this.id = 1

        val child1 = Child().apply {
            this.id = 1
        }

        val child2 = Child().apply {
            this.id = 2
        }

        this.children.add(child1)
        this.children.add(child2)

        child1.parent = this
        child2.parent = this
    }

    val result = ObjectMapper()
        .writeValueAsString(parent)

    print(result)
    assertThat(result).contains("children")
}

 

  • 해당 어노테이션없이 사용한다면 아래와 같은 순환 참조 에러를 볼 수 있다.
com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain...

 

5.5 @JsonIdentityInfo

  • @JsonManagedReference, @JsonBackReference와 유사하게 직렬화/역직렬화시 순환 참조를 막을 수 있다.
  • ID를 통해서 순환참조 방지

 

  • 사용 예제
@JsonIdentityInfo(generator = PropertyGenerator::class, property = "id")
class ItemWithIdentity {
    var id = 0
    var itemName: String? = null
    var owner: UserWithIdentity? = null
    override fun toString(): String {
        return "ItemWithIdentity(id=$id, itemName=$itemName, owner=$owner)"
    }
}

@JsonIdentityInfo(generator = PropertyGenerator::class, property = "id")
class UserWithIdentity {
    fun addItem(item: ItemWithIdentity) {
        userItems.add(item)
    }

    override fun toString(): String {
        return "UserWithIdentity(id=$id, name=$name, userItems=$userItems)"
    }

    var id = 0
    var name: String? = null
    var userItems: MutableList<ItemWithIdentity> = mutableListOf()
}

@Test
@Throws(JsonProcessingException::class)
fun jsonIdentityInfoTest() {
    val user = UserWithIdentity().apply {
        id = 1
        name = "demo"
    }
    val item = ItemWithIdentity().apply {
        id = 2
        itemName = "book"
        owner = user
    }
    user.addItem(item)


    val result = ObjectMapper().writeValueAsString(user)
    print(result)
    assertThat(result).contains("userItems")
    assertThat(result).contains("owner")
}

 

  • 결과
{"id":1,"name":"demo","userItems":[{"id":2,"itemName":"book","owner":1}]}

 

5.6 @JsonFilter

  • 직렬화 중 사용할 필터를 지정

 

  • 사용 예제
@Test
fun jsonFilterTest() {
    @JsonFilter("myFilter")
    class BeanWithFilter(
        var id: Int,
        var name: String?
    ) {
        override fun toString(): String {
            return "BeanWithFilter(id=$id, name=$name)"
        }
    }

    val bean = BeanWithFilter(1, "demo")

    val filters = SimpleFilterProvider().addFilter(
        "myFilter",
        SimpleBeanPropertyFilter.filterOutAllExcept("name")
    )

    val result = ObjectMapper()
        .writer(filters)
        .writeValueAsString(bean)

    print(result)
    assertThat(result).contains("name")
    assertThat(result).doesNotContain("id")
}

 

 

6. Custom Jackson Annotation

  • @JacksonAnnotationsInside annotation을 사용해서 Custom 한 jackson annotation을 만들 수 있음

 

  • 사용 예제
// CustomAnnotation.java


import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({"name", "id", "dateCreated"})
public @interface CustomAnnotation {
}
@Test
fun jacksonAnnotationsInsideTest() {

    @CustomAnnotation
    class BeanWithCustomAnnotation(
        var id: Int,
        var name: String?,
        var dateCreated: Date?
    ) {
        override fun toString(): String {
            return "BeanWithCustomAnnotation(id=$id, name=$name, dateCreated=$dateCreated)"
        }
    }

    val bean = BeanWithCustomAnnotation(1, "demo", null)


    val result = ObjectMapper()
        .writeValueAsString(bean)

    print(result)
    assertThat(result).doesNotContain("dateCreated")
    assertThat(result).contains("id")
    assertThat(result).contains("name")
}
  • null 값을 제외하는 @JsonInclude / 프로퍼티 순서를 정하는 @JsonPropertyOrder를 사용

 

  • 결과
// 미사용
{"id":1,"name":"demo","dateCreated":null}

// 사용
{"name":"demo","id":1}

 

 

7.Jackson MixIn Annotations

  • 특정 타입의 속성을 무시할 때 MixIn annotation을 사용한다.

 

  • 사용 예제
class Employee(
    val name: String,
    val age: Int,
    val address: Address
)

class Address(
    val address1: String,
    val address2: String
)

@JsonIgnoreType
class MyMixInForIgnoreType {}

@Test
fun mixInTest() {


    val bean = Employee("demo", 32, Address("add1", "add2"))

    var result = ObjectMapper()
        .writeValueAsString(bean)

    print(result)
    assertThat(result).contains("name")
    assertThat(result).contains("age")
    assertThat(result).contains("address")

    val mapper = ObjectMapper()
    mapper.addMixIn(Address::class.java, MyMixInForIgnoreType::class.java)

    result = mapper.writeValueAsString(bean)
    assertThat(result).contains("name")
    assertThat(result).contains("age")
    assertThat(result).doesNotContain("address")
}

 

 

8.Disable Jackson Annotation

  • MapperFeature.USE_ANNOTATIONS을 사용해서 모든 jackson annotaion을 비활성화시킬 수 있음
ObjectMapper mapper = new ObjectMapper(); 
mapper.disable(MapperFeature.USE_ANNOTATIONS);

 

반응형