ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kotlin - jackson 을 이용하여 객체를 json 으로 변환할 때 주의점
    Kotiln 2021. 3. 24. 20:19
    import com.fasterxml.jackson.databind.ObjectMapper
    import com.fasterxml.jackson.module.kotlin.KotlinModule
    
    fun main(args: Array<String>) {
        val testData = TestData("name", "code", false)
        println(testData)
        val objectMapper = ObjectMapper().registerModule(KotlinModule())
        val res = objectMapper.writeValueAsString(testData)
        println(res)
        val data = objectMapper.readValue(res, TestData::class.java)
        println(data)
    }
    
    data class TestData(
        val name: String,
        val aCode: String,
        val isUse: Boolean
    )

    위의 코드를 실행하면 다음과 같은 에러가 발생합니다.

    TestData(name=name, aCode=code, isUse=false)
    {"name":"name","acode":"code","use":false}
    Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `TestData` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
     at [Source: (String)"{"name":"name","acode":"code","use":false}"; line: 1, column: 2]
    	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1589)
    	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1055)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
    	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
    	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
    	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
    	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
    	at MainKt.main(Main.kt:9)
    
    Process finished with exit code 1

    출력된 결과를 확인해 보면 TestData 의 데이터는 잘 들어갔지만 json으로 변환하는 과정에서 뭔가 문제가 생긴 것을 확인할 수 있습니다.

    TestData(name=name, aCode=code, isUse=false)
    {"name":"name","acode":"code","use":false}

    aCode 는 acode 로, isUse 는 use 로 바뀌어서 변환되었습니다.

    코틀린 코드를 자바 코드로 변환해보면

    위와 같은 메소드를 생성하여 값을 반환하게 되어 있습니다.

    jackson 의 코드를 살펴보면

    properties 를 _props 의 값으로 주게되는데 이때

    getACode, isUse 메소드로 값을 가져온 결과가 acode, use 로 보여집니다.

    aCode 의 경우

    jackson 은 자바빈 네이밍 컨벤션을 따르며 그에 따라 객체 프로퍼티의 값을 가져옵니다.

    그러므로 프로퍼티의 처음 두자리는 대문자로 시작하지 않는 것이 좋습니다

    http://futuretask.blogspot.com/2005/01/java-tip-6-dont-capitalize-first-two.html

    https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/

    isUse 의 경우

    메소드에서 get, set, is 의 뒤에 오는 것을 json 의 키로 사용합니다.

    따라서 isUse 가 use 로 변경된 것입니다.

    그대로 사용하려면?

    위의 두 경우에서 그대로 key 값이 들어가길 원한다면 @JsonProperty 를 사용합니다.

    data class TestData(
        val name: String,
        @get:JsonProperty("aCode")
        val aCode: String,
        @get:JsonProperty("isUse")
        val isUse: Boolean
    )

    getter@JsonProperty 를 추가하고 사용할 이름을 입력합니다.

    자바 코드에서 @JsonProperty 가 잘 적용된 것을 확인할 수 있습니다.

    이제 다시 실행시키면 원하던 결과가 출력됩니다.

    출처

    반응형

    댓글 4

    • hjlee 2021.09.15 01:12

      aCode 는 왜 두번째 글자부터 정상으로 나오는건가요?
      aaCode 로 하니까 aaCode로 시리얼라이즈가 되네요.
      이 부분 이해가 안가 여쭤봅니다.

      • 블린더르 2021.09.15 10:28 신고

        @JsonProperty 를 쓰지 않으면 jackson 은 프로퍼티를 Getter 를 기준으로 유추합니다.
        즉 getACode() 를 가지고 프로퍼티 이름을 유추하게 되며 ACode 라는 프로퍼티 이름을 유추하게 됩니다.

        본문에는 자바빈 네이밍 컨벤션을 따른다고 써놨는 데 자료를 찾아보니 기본적으로 jackson 이 자바빈 네이밍 컨벤션을 따르긴 하지만 약간 다른 부분이 있습니다.
        자바빈 네이밍 컨벤션은 기본적으로 맨 앞 자리는 소문자로 변환하지만 1, 2 번째 글자가 모두 대문자인 경우는 유지를 합니다.
        반면 jackson 은 1, 2 번째 글자가 대문자인 경우 모두 소문자로 변경하는 규칙을 가지고 있습니다. (https://bcp0109.tistory.com/309)
        따라서 ACode -> acode 로 변환된 것입니다.

        댓글에 남겨주신 aaCode 는 getAaCode 가 getter 로 생성되고 이것으로 유추된 프로퍼티 이름은 AaCode 인데 맨 앞 글자를 소문자로 변환하여 aaCode 로 변환됩니다.

        참고로 objectMapper.configure(MapperFeature.USE_STD_BEAN_NAMING, true)
        를 추가하면 jackson 에서 완전하게 자바 빈 컨벤션을 따르게 되며 그럴 경우 변환되는 값은 "ACode":"code" 가 됩니다.

      • hjlee 2021.09.15 14:48

        친절한 설명 정말 감사합니다.
        도움이 많이 되었습니다!!

    • 2021.09.27 14:24

      비밀댓글입니다

Designed by Tistory.