ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JSON 라이브러리 Gson과 Jackson - Jackson 편
    Android/Json 2019. 6. 16. 23:48

    앞서 Json 어떤 라이브러리를 사용해야 하는가에 대해서 탐구해보았는데

    gson와 jackson이 저/대용량에서 좋은 성능을 보여 어떤식으로 사용해야 하는지

    간단하게 알아보도록 하자

     

    IntelliJ에서 Kotlin으로 테스트를 해보았다.

     

    Jackson

    Site : https://github.com/FasterXML/jackson

    Json 뿐만 아니라 XML/YAML/CSV 등 다양한 형식의 데이타를 지원하는 data-processing 툴

    스트림 방식이므로 속도가 빠르며 유연하며 다양한 third party 데이타 타입을 지원한다.


    Gradle Dependency

     

    implementation "com.fasterxml.jackson.core:jackson-core:2.9.9"
    implementation "com.fasterxml.jackson.core:jackson-annotations:2.9.9"
    implementation "com.fasterxml.jackson.core:jackson-databind:2.9.9"
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9"


    1. POJO ( Plain Old Java Object ) 생성

    enum class Gender {
        Male,
        Female;
    }
    
    data class Person(
        val name: Name,
        val age: Int,
        val gender: Gender
    )
    
    data class Name(
        val firstName: String,
        val lastName: String
    )

    가장 기본적인 Person 객체를 생성 하였고 reference 객체와 enum 객체도 섞었다.


    2. Java Object -> Json

    fun main() {
        val person = Person(Name("홍", "길동"), 20, Gender.Male)
        val objectMapper = ObjectMapper()
        val result = objectMapper.writeValueAsString(person)
        println(result)
    }

    [ 결과 ]

    {"name":{"firstName":"홍","lastName":"길동"},"age":20,"gender":"Male"}

     

    ObjectMapper : Jackson 라이브러리의 주요 액터 클래스로서 기본 POJO와의 JSON, 일반 JSON 트리 모델 (JsonNode) 간의 JSON 읽기 및 쓰기 기능 및 변환 수행 관련 기능을 제공

     

    writeValueAsString : 객체를 받아 String으로 반환

     

    [ Tip ] Json을 이쁘게 출력시키는 방법은 아래와 같이 코드를 작성

    objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(person)

     

    {
      "name" : {
        "firstName" : "홍",
        "lastName" : "길동"
      },
      "age" : 20,
      "gender" : "Male"
    }


    3. Json -> Java Object

    fun main() {
        val json = "{\"name\":{\"firstName\":\"홍\",\"lastName\":\"길동\"},\"age\":20,\"gender\":\"Male\"}"
        val objectMapper = ObjectMapper()
        val result = objectMapper.readValue(json, Person::class.java)
        println(result)
    }

    [ 결과 ] 

    cannot deserialize from Object value

     

    음? 역직렬화가 되지 않는다.

    이유는

     

    [ 수정 ]

    data class Person(
        val name: Name = Name(),
        val age: Int = 0,
        val gender: Gender = Gender.Male
    )
    
    data class Name(
        val firstName: String = "",
        val lastName: String = ""
    )

    파라미터 대한 기본값이 필요하다.

    아마도 값이 존재하지 않을때 엄청난 크래쉬를 안고가기 싫어서 그런가 부다...

     

    [ 결과 ]

    Person(name=Name(firstName=홍, lastName=길동), age=20, gender=Male)

     

    잘나온다 ㅎㅎ


    4. Annotation

     

    속성을 서포트 해주는 어노테이션! 총 3가지가 있다.

     

    ★ JsonIgnoreProperties / JsonIgnore 

    Serializer/Deserialize 시 제외시킬 프로퍼티를 지정

     

    [ 코드 ]

    @JsonIgnoreProperties("name", "age")
    data class Person(
        val name: Name = Name(),
        val age: Int = 0,
        val gender: Gender = Gender.Male
    )

     

    [ 결과 ]

    Person(name=Name(firstName=, lastName=), age=0, gender=Male)

     

    보다시피 이름과 나이가 기본값인 공백과 0으로 설정된 것을 볼 수 있다.

    JsonIgnoreProperties : 여러개를 한꺼번에

    JsonIgnore : 한개만 

     

    상황에 따라서 잘 설정하면 될것 같다.

     

    ★ JsonProperty

    getter/setter 의 이름을 property 와 다른 이름을 사용할 수 있도록 설정

    Database 를 자바 클래스로 매핑하는데 DB의 컬럼명이 알기 어려울 경우등에 유용하게 사용

     

    [ 코드 ]

    data class Person(
        @JsonProperty("realName")
        val name: Name = Name(),
        val age: Int = 0,
        val gender: Gender = Gender.Male
    )

     

    작동을 시켜보지만 별다른 변경점이 없다...

    코틀린 DataClass 때문인데.. 별도의 모듈을 적용시켜 주어야 한다.

    아래와 같이 코드를 변경!


    val objectMapper = ObjectMapper()
    objectMapper.registerModules(KotlinModule())

     

    [ 결과 ]

    ( 변경 전 ) : {"name":{"firstName":"홍","lastName":"길동"},"age":20,"gender":"Male"}

    ( 변경 후 ) : {"realName":{"firstName":"홍","lastName":"길동"},"age":20,"gender":"Male"}

     

    ★ JsonInclude

    Serialize 시 동작을 지정한다. 기본적으로 잭슨은 값의 유무와 상관없이 무조건 serialize 하게 되지만

    다음과 같이 설정할 경우 not null 이거나 none empty 일 경우에만 serialize 된다.

    data class Person(
        @JsonInclude
        val name: Name?,
        val age: Int = 0,
        val gender: Gender = Gender.Male
    )

     

    음.. 코틀린 상에서는 테스트가 되지를 않는다.. 아무래도 null에 민감한 아이여서 그런지 잘 작동하지는 않는다.


    5. Custom Serializer/Deserialize

     

    StdSerializer 와 StdDeserializer

    두개모두 JsonSerializer/Deserializer 을 상속 받고 있다.

    두개의 차이점은 Method의 차이가 아닌가 싶다.

     

    자세한 Method는 아래 주소를 참조

    https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/ser/std/StdSerializer.html

     

    직렬화

    class PersonSerializer(t: Class<Person>) : StdSerializer<Person>(t) {
    
        override fun serialize(value: Person, gen: JsonGenerator, provider: SerializerProvider) {
    
            gen.writeStartObject()
    
            gen.writeObjectField("name", value.name)
            gen.writeNumberField("age", value.age)
            gen.writeStringField("gender", value.gender.toString())
    
            gen.writeEndObject()
    
        }
    
    }

    StdSerializer를 상속 후 객체를 Class 파라미터로 받아 넣어주고 알맞게 직렬화 코드를 짜주면 된다.

    Name도 마찬가지로 Serializer 코드를 짜주었다

     

    직렬화

    class PersonDeserializer(t: Class<Person>) : StdDeserializer<Person>(t) {
    
        private val objectMapper: ObjectMapper = ObjectMapper()
    
        init {
            val simpleModule = SimpleModule()
            simpleModule.addDeserializer(Name::class.java, NameDeserializer(Name::class.java))
            this.objectMapper.registerModule(simpleModule)
        }
    
        override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Person {
    
            val objectCodec = p.codec
            val jsonNode = objectCodec.readTree<TreeNode>(p)
    
            val name = objectMapper.convertValue<Name>(jsonNode.get("name").toString(), Name::class.java)
            val age = jsonNode.get("age").toString().toInt()
            val gender = Gender.valueOf(jsonNode.get("gender").toString())
    
            return Person(name, age, gender)
    
        }
    
    }

    그리고 모듈을 하나 만들어 ObjectManager에 registerModule 시켜주면 된다

    fun main() {
    
        val person = Person(Name("홍", "길동"), 20, Gender.Male)
    
        val module = SimpleModule()
        module.addSerializer(PersonSerializer(Person::class.java))
        module.addSerializer(NameSerializer(Name::class.java))
        module.addDeserializer(PersonDeserializer(Person::class.java))
        module.addDeserializer(NameDeserializer(Name::class.java))
    
        val objectMapper = ObjectMapper()
        objectMapper.registerModule(KotlinModule())
        objectMapper.registerModule(module)
    
        val result = objectMapper.writeValueAsString(person)
        println(result)
    
    }

    코드 짜는데 너무 힘들었습니다.. 1. 코틀린이여서 2. 정보가 옛날이여서... ㅋㅋㅋㅋ

    코드는 나중에 리펙토링 한번 하곘습니다.

     

    ObjectManager를 생성한 이유!

    Person에는 Name이라는 레퍼런스가 있기때문에 Name의 역직렬화가 있어야 함으로

    ObjectManager를 만들어서 Module로 넣어줍니다.

     

    {"name":{"firstName":"홍","lastName":"길동"},"age":20,"gender":"Male"}

     

    정상적으로 잘 작동 됩니다...

     

    이렇게 해서 Jackson에 대해서 한번 알아보았구요...

    나머지 관련은 추가적으로 포스팅 하겠습니다. ㅃ2~

    'Android > Json' 카테고리의 다른 글

    JSON 라이브러리 Gson과 Jackson - Gson편  (0) 2019.06.18
    JSON 어떤 라이브러리를 사용해야 할까?  (0) 2019.06.16

    댓글

Designed by Tistory.