본문 바로가기

TIL

TIL) 객체의 커스텀 직렬화

1. 객체의 커스텀 직렬화

직렬화 관련 TIL) 을 세번쨰 연속 쓰고 있다.. 뭔가 겹치는 부분도 있고 해서 좀 찝찝하지만.. 새로운 부분이 있어서 그냥 적는다..

 

이번에 해본 내용은 객체의 커스텀한 직렬화이다.

 

 

일단 클래스를 하나 만든다.

 

class Family(
    val name: String,
    val number: Int,
    val people: List<FamilyPeople>
)

class FamilyPeople(
    val name: String,
    val age: Int
)

 

 

 

1-1 객체의 커스텀 Serializer/Deserializer

 

 

[직렬화]

class FamilySerializer: JsonSerializer<Family>() {
    override fun serialize(value: Family, generator: JsonGenerator, provider: SerializerProvider) {
        generator.writeStartObject();

        generator.writeFieldName("[name]")
        generator.writeString(value.name + "Family")

        generator.writeFieldName("[number]")
        generator.writeString(value.number.toString() + "people")

        generator.writeFieldName("people")
        generator.writeObject(value.people)

        generator.writeEndObject()
    }
}

 

인자로 들어오는 JsonGenerator 타입의 객체에는 Json 을 만들 수 있는 기능이 있다.

 

generator.writeStartObject() : json 의 { 와 같다.

generator.writeFieldName() : json 의 "name" 과 같은 키값을 적는다.

generator.writeString() : json 에 String 값을 적는다. 벨류값을 적을 때 사용한다.

generator.writeEndObject() : json 의 } 와 같다.

 

 

 

[역직렬화]

class FamilyDeserializer : JsonDeserializer<Family>() {

    private val objectMapper: ObjectMapper = ObjectMapper()

    override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Family {
        val objectCodec = p.codec
        val jsonNode: JsonNode = objectCodec.readTree(p)

        val name = jsonNode["name"].asText() + " Family"
        val number = jsonNode["number"].asInt() + 1000
        val people = objectMapper.convertValue(jsonNode["people"], List::class.java)
        return Family(name, number, people as List<FamilyPeople>)
    }

    init {
        val simpleModule = SimpleModule().apply {
//        addDeserializer()
        }
        objectMapper.registerModule(simpleModule)
    }
}

역직렬화에는, json 을 객체로 만들어야 하기에 ObjectMapper 가 필요하다.

 

클래스의 내부에 다른 중첩 객체가 있고, 그 객체의 직렬화 로직이 커스텀하다면, ObjectMapper 를 생성할때,

Kotlin 의 init() 안에서 등록한다.

 

이후 JsonParser 의 codec 값을 읽어서 객체를 만든다.

 

 

 

 

1-2.  만든 Serializer/Deserializer 를 적용하는 방법에는 3개가 있다.

 

  • @JsonSerializer / @JsonDeserializer.
  • 코드 상에서 직접 ObjectMapper 를 등록해서 사용하는 방법.
  • WebConfigurer 의 extendMessageConverter() 에서 ObjectMapper 를 등록한 MappingJackson2HttpMessageConverter 를 등록해서 사용.

 

3번째 방법은 이전 글에서 다루었기 때문에, 넘어가고

1번째 방법은 객체에 에너테이션만 붙이면 된다.

 

이번에는 두번째 코드 상에서 직접 ObjectMapper 를 등록해서 사용하는 방법을 적어본다.

 

class SerializeTest {

    private lateinit var objectMapper: ObjectMapper

    @BeforeEach
    fun register() {
        val simpleModule = SimpleModule().apply {
            addSerializer(Family::class.java, FamilySerializer())
            addDeserializer(Family::class.java, FamilyDeserializer())
        }

        objectMapper = ObjectMapper().apply {
            registerModule(simpleModule)
        }
    }

    @Test
    fun serialize() {
        val p1 = FamilyPeople("tomas", 27)
        val p2 = FamilyPeople("eil", 47)
        val p3 = FamilyPeople("json", 22)
        val myFamily = Family("Yang", 4, listOf(p1, p2, p3))

        Assertions.assertThat(objectMapper.writeValueAsString(myFamily))
            .isEqualTo("{\"[name]\":\"YangFamily\",\"[number]\":\"4people\",\"people\":[{\"name\":\"tomas\",\"age\":27},{\"name\":\"eil\",\"age\":47},{\"name\":\"json\",\"age\":22}]}")
    }

    @Test
    fun deserialize() {
        val json = "{\n" +
                "    \"name\": \"fam\",\n" +
                "    \"number\": 3,\n" +
                "    \"people\": [\n" +
                "        {\n" +
                "            \"name\": \"tomas\",\n" +
                "            \"age\": 27\n" +
                "        },\n" +
                "        {\n" +
                "            \"name\": \"eilen\",\n" +
                "            \"age\": 11\n" +
                "        }\n" +
                "    ]\n" +
                "}"

        val family = objectMapper.readValue(json, Family::class.java)

        Assertions.assertThat(family.name).isEqualTo("fam Family")
        Assertions.assertThat(family.number).isEqualTo(1003)
    }
}