Gson 이란?
2014년에 2.3.1 까지 나오는 지속성 있는 자바 라이브러리로, 자바 객체를 JSON 문자열로 손쉽게 바꾸어 주며 그 반대도 가능하다.
http://code.google.com/p/google-gson.
직렬화 : 직렬화란 자료 구조나 객체 상태(멤버 변수)를 (네트워크 연결 링크로 전송될 파일이나 메모리 버퍼 등 으로)저장 가능한 형식으로 변환하는 과정이다. 그리고 이후에 동일하거나 혹은 또 다른 컴퓨터 환경에서 재구조화 된다.1
역직렬화 : 데이터의 원형이 되는 단위의 흐름인 스트림을 읽어서 다시 원본 객체나 자료 구조로 변환하는 과정.
Gson 사용법1
Gson 은 객체를 생성하는 Gson() 과 다양한 설정과 함께 생성할 수 있는 GsonBuilder() 를 생성자로 가진다.
원시형(Primitives) 변수 Json 화 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
(직렬화) : json 화 하기
Gson gson = new Gson();
gson.toJson(1); ==> prints 1
gson.toJson("abcd"); ==> prints "abcd"
gson.toJson(new Long(10)); ==> prints 10
int[] values = { 1 };
gson.toJson(values); ==> prints [1]
(역직렬화) : 자바 객체로 바꾸기
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson(""abc"", String.class);
String anotherStr = gson.fromJson("["abc"]", String.class);
|
클래스(내부에 멤버 변수를 가지고 있는)
|
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
(직렬화) : json 화 하기
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
==> json is {"value1":1,"value2":"abc"}
Note that you can not serialize objects with circular references since that will result in infinite recursion.
(역직렬화) : 자바 객체로 바꾸기
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);<br />
==> obj2 is just like obj
|
객체(object) 변환 포인트
- private 필드 사용권장
- 직렬화와 역직렬화에 필드를 추가시키기 위한 지시자나 어노테이션은 필요 없습니다. 현재 클래스(와 수퍼 클래스로부터 온) 모든 필드는 기본으로 포함됩니다.
- 필드가 transient 로 표시되면, (기본적으로) Json 직렬화나 역직렬화에서 제외 됩니다.
- 이식은 null 을 정확하기 처리합니다.
- 직렬화 중에는, null 필드는 출력에서 제외됩니다.
- 역직렬화 중에는, 설정에 JSON 결과값 에 빠진 기록 값(entry)은 객체 안에 대응되는 필드를 null 로 만듭니다.
- 필드가 synthtic, 이라면 JSON 에 포함되지 않고 무시됩니다.
- 필드가 이너 클래스, 익명 클리스, 지역 클래스에 담겨 있는 외부 클래스와 대응된다면, 직렬화나 역직렬화에 포함시키지 않고 무시합니다.
배열
1
2
3
4
5
6
7
8
9
10
11
12
|
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
(직렬화) : json 화 하기
gson.toJson(ints); ==> prints [1,2,3,4,5]
gson.toJson(strings); ==> prints ["abc", "def", "ghi"]
(역직렬화) : 자바 객체로 바꾸기
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
==> ints2 will be same as ints
|
다양한 요소 타입을 가지는 다차원 배열도 된다.
널 객체 지원
null 객체 필드에 대해 Gson에 이식된 기본 행동은 무시다. 이 것은 축소 출력 포맷일 때 더 허용된다: 그러나, 클라이언트는 이 필드를 위한 기본 값을 자바로 되돌리기 위한 JSON 포맷으로 정의해야 한다,
역자 주 :
여기에 null 을 출력하기 위한 Gson 인스턴스를 설정하는 방법이 있다.
Gson gson = new GsonBuilder().serializeNulls().create();
노트 : null 을 Gson 으로 직렬화 할 때, 그것은 JsonNull 요소를 JsonElemet 구조에 추가합니다. 그리고 나면, 객체는 사용자정의 직렬화/역질렬화를 사용됩니다.
여기에 예제가 있네요:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<br />// Foo 클래스 정의
public class Foo {
// 문자열 s 정수 i 를 선언하고 상속을 방지(final)
private final String s;
private final int i;
// Foo 클래스 생성자 정의
public Foo() {
// 아래의 오버라이드 메소드를 호출하면서 문자열 null , 정수 5 를 넘겨준다.
this(null, 5);
}
// Foo 생성자 오버라이드 호출 후 Foo 의 멤버 변수에 인자로 넘겨준 문자열과 정수를 설정(세터역할)
public Foo(String s, int i) {
this.s = s;
this.i = i;
}
}
// Gson 객체 생성, 옵션으로 Null 도 직렬화 하라고 지정하고 생성.
Gson gson = new GsonBuilder().serializeNulls().create();
// Foo 생성자 호출해서 null 과 5 설정
Foo foo = new Foo();
// json 변수에 생성된 foo 인스턴스를 Json 화해서 저장.
String json = gson.toJson(foo);
// json으로 직렬화 된 객체 상태(멤버 변수)를 출력.
System.out.println(json);
// null 을 직렬화 한 후 출력
json = gson.toJson(null);
System.out.println(json);
======== 출력 ========
// null 값이 있는 상태(멤버 변수)가 무시되지 않고 json 으로 변환됐다.
{"s":null,"i":5}
null
|
콜렉션
|
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
(직렬화) : json 화 하기
String json = gson.toJson(ints); ==> json is [1,2,3,4,5]
(역직렬화) : 자바 객체로 바꾸기
Type collectionType = new TypeToken<Collection<Integer>>(){}.getType();
Collection<Integer> ints2 = gson.fromJson(json, collectionType);
ints2 is same as ints
|
콜렉션 제한
- 많은 객체들을 직렬화 할 수 있다. 그러나 그 것을 역직렬화 할 수는 없다.
- 왜냐면, 결과 객체에 타입을 사용자가 알려줄 수 있는 방법이 없기 때문이다.
> **역자 주 ** : 컬렉션은 object 타입으로 다른 객체를 담는다. 담아 있는 것을 꺼낼 때는 다시 해당 객체 타입으로 형변환(캐스팅)을 해줘야 함.
- 역직렬화 과정에서, 콜렉션에 꼭 제너릭 타입을 지정해줘야 한다.
제너릭 타입을 직렬화, 역 직렬화하기
toJson(obj) 메소드 호출할 때, Gson 은 obj.getClass() 메소드로 직렬화 할 필드의 정보르 가져온다. 유사하게, 당신은 fromJson(json, MyClass.class) 으로 MyClass.class 를 통과시킬 수 있다. 이것은 객체가 비-제너릭 타입일 경우에 잘 작동한다. 하지만 객체가 제너릭 타입이라면, 자바 타입 삭제 때문에 제너릭 정보가 지워진다. 여기서 포인트를 보자.
|
// T 제너릭 타입을 상태(멤버변수, 요소)로 가지는 Foo 컬렉션 정의
class Foo<T> {
T value;
}
Gson gson = new Gson();
// Bar 제너릭 타입을 가지는 Foo 컬렉션 인스턴스를 foo 로 생성
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // foo.value 를 직렬화 하지 못한다.
|
위에 코드는 Bar 타입의로 값을 해석하는 데 실패한다. 왜냐면 Gson 이 클래스 정보를 가져오려고 list.getClass() 를 호출(invokes) 해도, raw 타입인 Foo.class 만 반환받기 때문인데, 이 말은 Gson 이 그냥 Foo 클래스가 아니라 Foo 타입인지 알아낼 방법이 없다는 말이다.
이 문제는 타입을 인수로 넘겨주면 된다. 바로 타입토큰 클래스를 사용하면 된다.
|
// 주입할 타입을 타입토큰 클래스로 생성
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
// 생성된 타입 객체를 인자로 넘긴다.
gson.toJson(foo, fooType);
/// 이제 gson 에는 해당 제너릭 타입이 들어가 있으므로, 역직렬화가 잘 된다.
gson.fromJson(json, fooType);
|
무작위 타입의 객체를 가지는 콜렉션을 직렬화 및 역직렬화하기
종종 타입이 섞인 JSON 을 다루고 싶을 때가 있다. 예를 들자면.
[‘hello’,5,{name:’GREETINGS’,source:’guest’}]
역자 주 : 위를 보면 세번째 요소인 {name:’GREETINGS’,source:’guest’} 에 아무런 타입을 가리키는 값이 없는 것을 알 수 있다. 즉, 위 json 값을 역직렬화 해서 객체로 만들더라도, 어떤 타입으로 만들 것인지 Gson 은 알 수가 없다는 말이다.
이 것은 다음을 포함하는 컬렉션과 같다.
|
Collection collection = new ArrayList();
collection.add("hello");
collection.add(5);
collection.add(new Event("GREETINGS", "guest"));
|
Event 클래스는 다음처럼 정의가 되어 있다.
|
class Event {
private String name;
private String source;
private Event(String name, String source) {
this.name = name;
this.source = source;
}
}
|
당신은 어떤 것도 지정하지 않고 Gson 으로 컬렉션을 직렬화 할 수 있다. toJson(컬렉션) 메소드가 원하는 출력물을 작성할 것이다.
그러나, fromJson(json,컬렉션.class) 는 작동하지 않을 것이다, Gson 은 입력값을 타입으로 연결(map) 하는 방법을 알아낼 수가 없기 때문이다. Gson 은 fromJson 내부에 컬렉션 타입의 genericised 버전을 제공하기를 요구한다. 그래서, 다음과 같이 세 개의 선택지가 있다:
선택 1 : Gson 의 분석기(parser) API(저수준 스트리밍 분석기나 DOM 분석기 JSON분석기 )를 사용해서 배열의 각 요소에 Gson.fromJson() 을 해석한다. 이 것이 선호되는 접근법이다.
선택 2 : 각 배열 멤버를 찾는 Collection.class 를 위한 어뎁터 종류를 등록하고 그들을 적절한 객체에 연결(map)한다. 이 접근법의 단점은 Gson 에서 다른 콜렉션 타입의 역직렬화를 망쳐버리게 될 것이란 것이다.
선택 3 : MyCollectionMemberType 용 타입 어뎁터를 등록하고 fromJson 메소드에 Collection를 같이 써준다. 이 접근법은 배열이 최상단 요소이거나 당신이 필드 타입이 Collection 로 고정되게 변경했을 경우에만 통하는 접근법이다.
사용자 정의 직렬화와 역직렬화
종종 기본 설정은 당신이 원하는 것이 아닙니다. 이 것은 자주 일어나는데 라이브러리 클래스를 다룰 때 그럽니다(DateTime, etc).
Gson 당신 자신의 사용자 정의 직렬화, 역직렬화 등록을 허용합니다. 이 것은 두 부분을 정의함으로서 완료됩니다.
- Json 직렬화기 : 객체를 위한 사용자 정의 직렬화 정의 필요.
- Json 역직렬화기 : 타입을 위한 사용자 정의 역직렬화 정의 필요.
- 인스턴스 생성기 : 인자-없는 생성자가 가능하거나 역직렬화화가 등록되어 있다면 필요하지 않다.
|
// GsonBauilder 인스턴스 생성
GsonBuilder gson = new GsonBuilder();
// 빌더 인스턴르에 registerTypeAdapter() 메서드로, 특정 클래스에 사용할, 타입 어댑터, 직렬화, 역직렬화, 인스턴스 생성기 등록.
gson.registerTypeAdapter(MyType2.class, new MyTypeAdapter());
gson.registerTypeAdapter(MyType.class, new MySerializer());
gson.registerTypeAdapter(MyType.class, new MyDeserializer());
gson.registerTypeAdapter(MyType.class, new MyInstanceCreator());
|
registerTypeAdapter 호출은 만약 타입 어댑터가 이 인터페이스들을 하나 이상 구현(implements) 하는지 확인하고 그것들 모두를 등록한다.
직렬화기 작성
JodaTime DateTime 클래스 사용자 정의 직렬화를 작성하는 예시다.
|
// DateTimeSerializer 를 직렬화할 객체의 타입을 구현 제너릭으로 하는 JsonSerializer 을 구현(implemtnst) 하게 한다.
private class DateTimeSerializer implements JsonSerializer<DateTime> {
// JsonElement 을 반환하는 seriallize(변환할 객체, 타입, Json 역직렬화문맥) 메소드를 오버라이드 한다.
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
// 호출하면 JsonPrimitive(직렬화할 객체.toString); 을 돌려준다.
return new JsonPrimitive(src.toString());
}
}
|
Gson 은 직렬화 과정 중에 DateTime 객체가 들어오면 toJson() 메소드를 호출한다.
역직렬화기 작성
|
<br />// DateTimeSerializer 를 직렬화할 객체의 타입을 구현 제너릭으로 하는 JsonSerializer 을 구현(implemtnst) 하게 한다.
private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
// DateTIme 을 반환하는 deserialize(json 요소, 타입, Json 역직렬화문맥) 오버라이드한다.
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
// DateTIme 객체를 반환한다.
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}
|
Gson은 JSON 문자열 조각을 DateTime 객체로 역직렬화할 필요가 있을 때 fromJson() 메소드를 호출한다.
직렬화기와 역직렬화기에 중요 포인트
raw 타입과 대응되는 모든 제너릭 타입을 위한 단일 핸드러를 등록하길 원할 것이다.
- 예를 들자면, 당신이 Id 를 위한 “Id” 클래스가 있다고 가정하자
- 모든 제너릭 타입 용 같은 직렬화를 가진 Id 타입
- 역직렬화도 비슷하지만 똑같지는 않다.
- Id 인스턴스를 반환하는 “new Id(Class, 문자열)” 을 호출한다.
Gson 은 이 것을 위한 단일 핸들러 등록을 지원한다. 당신은 또한 특정 제너릭 타입에 특정 핸들러를 등록할 수 있다.(Id 은 특별한 핸들링을 필요로 한다.)
toJson 과 fromJson 메소드를 위한 Type 인자는 당신이 같은 raw 타입에 대응하는 모든 제너릭 타입용 단일 핸들러를 작성하는데 도움을 주는 제너릭 타입 정보를 포함한다.
인스턴스 생성기 작성하기
객체를 역직렬화 하는 동안, Gson 은 클래스의 기본 인스턴를 생성할 필요가 있다.
잘-행동하는 클래스, 직렬화와 역직렬화를 잘 수행하는 클래스는 인자-없는 생성자를 가진다.
- 생성자가 public 인지 private 인지는 상관없다.
전형적으로, 인스턴스 생성기는 당신이 인자-없는 생성자를 정지 하지 않는 라이브러리 클래스를 다룰 때 필요하다.
인스턴스 생성기 예제
|
<br />// InstanceCreator<Money> 를 구현하는 MoneyInstanceCreator 클래스를 정의한다.
private class MoneyInstanceCreator implements InstanceCreator<Money> {
// Money 타입을 반환하는 createInstance(타입) 메소드를 오버라이드
public Money createInstance(Type type) {
// Money() 를 생성하는데 100000 정수와 USD 의 통화 코드를 인자로 넘겨서 생성한다.
return new Money("1000000", CurrencyCode.USD);
}
}
|
타입은 대응하는 제너릭 타입이 될 수 있다.
- 특정 제너릭 타입 정보를 필요로 하는 생성자를 호출하는데 매우 유용하다.
- 예를 들자면, 만약 Id 클래스가 어떤 Id 가 막 생성된 Id 를 저장한다면.
인자화된 타입을 위한 인스턴스생성기
종종 당신이 인스턴스화 하려고 시도하는 타입은 인자화된 타입이다.
일반적으로, 이것은 실제 인스턴스가 raw 타입이면 문제가 되지 않는다. 여기에 예제가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<br />// ArryList<T> 를 확장하는 MyList<T> 클래스
class MyList<T> extends ArrayList<T> {
}
// InstanceCreator<Mylist<?>> 를 구현하는 MyListInstanceCreator 클래스
class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
@SuppressWarnings("unchecked")
public MyList<?> createInstance(Type type) {
// 인지화된 list 를 사용할 필요가 없다. 왜냐면 실제 인스턴스가 raw type 으로 생성될 것이기 때문이다.
return new MyList();
}
}
|
하지만, 종종 당시능ㄴ 실제 인자화된 타입에 기반한 인스턴스를 생성할 필요가 있을 것이다. 이 경우에서, 당신은 타입 인자를 createInstance 메소드에 통과시켜 사용할 것이다. 여기에 예가 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Id<T> {
private final Class<T> classOfId;
private final long value;
public Id(Class<T> classOfId, long value) {
this.classOfId = classOfId;
this.value = value;
}
}
class IdInstanceCreator implements InstanceCreator<Id<?>> {
public Id<?> createInstance(Type type) {
Type[] typeParameters = ((ParameterizedType)type).getActualTypeArguments();
Type idType = typeParameters[0]; // Id has only one parameterized type T
return Id.get((Class)idType, 0L);
}
}
|
위에 예제에서, Id 클래스의 인스턴스는 인자화된 타입을 위한 실제 타입 내부를 실제로 통과하지 않고는 생성할 수 없다. 우리는 이런 문제를 통과된 메소드 인자(타입)를 사용함으로써 해결한다. 이 경우에 타입 객체는 실제 인스턴스가 Id 에 묶여야 하 Id의 자바 인자화된 타입 표현이다. Id 클래스가 T 라는 인자화된 매개변수를 가지고, 우리는 이 경우에 Foo 에 위치할 getActualTypeArgument() 메소드에 위해 반환받을 타입 배열에 0번째 요소를 사용할 것이다.
JSON 출력 양식용 컴팩트 vs 이쁜 인쇄
Gson 에 의해서 기본 제공되는 JSON 출력은 컴팩트 JSON 양식이다. 이것은 출력된 JSON 구조에 어떤 화이트 스페이스도 없다는 거다. 어찌 됐건 JSON 배역 내부에 있는 필드 이름과 값, 객체 필드, 객체 사이에 화이트 스페이스가 없다. null 필드도 출력에서 무시된다.
만약 당신이 이쁜 인쇄 기능을 사용하길 원하면, 당신은 반드시 Gson 인스턴스가 GsonBuilder 를 사용하도록 설정해야 한다. JsonFormatter 는 우리의 공개 API 를 통해 노출되지 않고, 클라이언트는 기본 인쇄 설정/마진 설정에 접근할 수 없다. 지금은, 우리는 오직 기본 JsonPrintFormatter 로 한 줄에 80글자, 들여쓰기 2글자, 오른쪽 마진 4글자를 준다.
다음의 예제는 어떻게 Gson 인스턴스가 기본 JsonPrintFormatter 를 JsonCompactFormatter 대신에 사용하게 설정하는 지를 보여준다.
|
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
|
버저닝 지원
@Since 어노테이션으로 같은 객체의 여러 버전을 관리할 수 있다. 자세한 것은 공식 문서[^공식문서]를 참조!!!
직렬화 역직렬화 과정에서 필드를 제외하기
공식 문서[^공식문서] 참조!
JSON 필드 이름규칙 지원
공식 문서[^공식문서] 참조!!!!
[출처] http://blog.mezeet.com/2015/04/gson-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%BC%EB%B6%80-%EB%B2%88%EC%97%AD/