본문 바로가기

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);

     

    반응형