Android/Json

JSON 라이브러리 Gson과 Jackson - Gson편

donghune 2019. 6. 18. 13:38

앞서 jackson을 어떻게 사용해야 하는가에 대해서 탐구해 보았는데
이번은 gson에 대해서 탐구를 해보겠다

Gson
Site : https://github.com/google/gson
Gson은 Java Object를 JSON 표현으로 변환하는 데 사용할 수있는 Java 라이브러리
또한 JSON 문자열을 동등한 Java 객체로 변환하는 데 사용할 수 있다.
 Gson은 소스 코드가없는 기존 오브젝트를 포함하여
임의의 Java 오브젝트에 대해 작업 할 수 있다.


Gradle Dependency


implementation 'com.google.code.gson:gson:2.8.5'


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 
) 

Jackson과 동일한 객체를 사용하였다.


2. Java Object -> Json

fun main() { 
    val person = Person(Name("홍", "길동"), 20, Gender.Male) 
    val json = Gson().toJson(person) 
    println(json) 
} 

 

[ 결과 ]
{"age":20,"gender":"Male","name":{"firstName":"홍","lastName":"길동"}}


기본적으로 Gson객체를 생성후 toJson 메서드를 사용하면된다.
Jackson과 다르게 field를 정렬된 상태로 출력을 시킨다.
 

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

val gson = GsonBuilder().setPrettyPrinting().create()
gson.toJson(person)
 

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


3. Json -> Java Object

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


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

Jackson과 다르게 기본값을 주지 않았지만 잘 작동한다.


4. Annotation

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

★ @Expose


Serialize/Deserialize 시 제외시킬 프로퍼티를 지정 Jackson과 간단하게 아래와 같이 어노테이션을 붙혀주면 된다.

data class Person( 
    @Expose(serialize = false) 
    val name: Name, 
    val age: Int, 
    val gender: Gender 
) 

위의 코드로 예상을 하면 name의 직렬화를 false로 하였으니
역직렬화를 할 경우 name이 null이 나와야 할 것이다.

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

음..? 적용이 되지 않는다... 또 Jackson처럼 코틀린 때문에 그런가 싶었더니
Gson에 옵션을 추가 시켜주어야 한다고 한다.

val gson = GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()

gson 객체를 생성하고 excludeFieldsWithoutExposeAnnotation를 해주어야 한다.
다시 빌드를 해보면 

Serialize : {} 
DeSerialize : Person(name=null, age=0, gender=null) 

이번엔 모든 프로퍼티가 직렬화가 되지 않았다..
알고보니 이녀석은 좀 까다로운놈!
아래의 표를 참고하자

Code Serialize Deserialize
@Expose(deserialize = false) 
val name: Name 
YES  NO 
@Expose 
val name: Name 
YES  YES 
@Expose(serialize = false) 
val name: Name 
NO  YES 
@Expose(serialize = false, deserialize = false) 
val name: Name 
NO  NO 

val name: Name

NO  NO 

 
[ 결과 ]

{"age":20,"gender":"Male"} 
Person(name=null, age=20, gender=Male) 

보다시피 이름과 나이가 기본값인 공백과 0으로 설정된 것을 볼 수 있다.
상황에 따라서 잘 설정하면 될것 같다.

 
★ SerializedName

Jackson의 JsonProperty와 같이
getter/setter 의 이름을 property 와 다른 이름을 사용할 수 있도록 설정
Database 를 자바 클래스로 매핑하는데 DB의 컬럼명이 알기 어려울 경우등에 유용하게 사용

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


작동을 시켜보지만 별다른 변경점이 없다...
코틀린 DataClass 때문인데.. 별도의 모듈을 적용시켜 주어야 한다.
아래와 같이 코드를 변경!

val json = Gson().toJson(person) 
println(json)


[ 결과 ]
( 변경 전 ) : {"age":20,"gender":"Male","name":{"firstName":"홍","lastName":"길동"}}
( 변경 후 ) : {"age":20,"gender":"Male","fullName":{"firstName":"홍","lastName":"길동"}}

 
5. Custom Serializer/Deserialize

JsonSerializer/Deserializer 

사실상 Json을 쉽게 작성하고자 만들어 진것이 Gson!
굳이 내가 만든 객체의 프로퍼티가 Primitive라면 커스텀을 만들 필요는 없다...

여기서는 Date라는 클래스에 대해서만 작성되었고
Date는 Parsing을 어떻게 하냐에 따라서 2019-06-18도 Date 2019.06.18도 Date이다
이러한 경우에만 커스텀을 만들어 주면 될 것 같다. 이하 Jackson 동일

직렬화

class DateSerializer : JsonSerializer { 

    @SuppressLint("SimpleDateFormat") 
    val dateFormat = SimpleDateFormat("yyyy-MM-dd") 

    override fun serialize(src: Date, typeOfSrc: Type?, 
                           context: JsonSerializationContext?): JsonElement { 
        return JsonPrimitive(dateFormat.format(src)) 
    } 

} 

역 직렬화 

class DateDeserializer : JsonDeserializer { 

    @SuppressLint("SimpleDateFormat") 
    val dateFormat = SimpleDateFormat("yyyy-MM-dd") 

    override fun deserialize(json: JsonElement, typeOfT: Type?,
                             context: JsonDeserializationContext?): Date { 
        return dateFormat.parse(json.asString) 
    } 

} 

등록 

val gson = GsonBuilder() 
     .registerTypeAdapter(Date::class.java, DateSerializer()) 
     .registerTypeAdapter(Date::class.java, DateDeserializer()) 
     .create() 

Person 코드 변경 

data class Person( 
    val name: Name, 
    val age: Int, 
    val gender: Gender, 
    val birth: Date 
) 

실행

val birth = SimpleDateFormat("yyyy-MM-dd").parse("2019-06-18") 
val person = Person(Name("홍", "길동"), 20, Gender.Male, birth) 

val json = gson.toJson(person) 
val result = gson.fromJson(json, Person::class.java) 

println(json) 
println(result) 

 

[ 결과 ] 

D/gson: {"age":20,"birth":"2019-06-18","gender":"Male","name":{"firstName":"홍","lastName":"길동"}} 
D/gson: Person(name=Name(firstName=홍, lastName=길동), age=20, gender=Male, 
birth=Tue Jun 18 00:00:00 GMT+09:00 2019) 

정상적으로 Date로 파싱되는 것을 볼 수 있다.


이로써 Gson도 끝났다.. 상황에 따라 Jackson을 쓰던 gson을 쓰면 될 것 같다.