// 직렬화 - 객체를 json으로
Gson gson = new Gson();
gson.toJson(1); // ==> 1
gson.toJson("abcd"); // ==> "abcd"
gson.toJson(new Long(10)); // ==> 10
int[] values = { 1 };
gson.toJson(values); // ==> [1]
// 역 직렬화 - json을 객체로
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;//transient는 직렬화를 하지않도록하는 키워드
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
//value3는 직렬화되지 않는다.
// ==> json is {"value1":1,"value2":"abc"}
무한 재귀가 발생하기 때문에 순환 참조로 객체를 직렬화할 수 없음을 주의하세요.
// 역 직렬화
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
// ==> obj2 is just like obj
객체에 관해 신경써야하는 부분
private field를 사용하는 것이 훨씬 좋습니다.(추천합니다.)
직렬화 및 역직렬화 field가 포함되는 것을 나타내기위해 주석을 사용할 필요가 없습니다. 사용하는 클래스의 모든 필드(부모 클래스 포함)는 기본적으로 포함됩니다.
field에 transient라고 표시한다면, 기본적으로 직렬화 및 역직렬화 되지 않습니다.
이 구현은 null을 올바르게 처리합니다. 직렬화하는 동안, null field는 결과에서 생략됩니다. 역직렬화하는 동안, JSON 결과에서 없는 항목은 객체의 해당하는 필드를 기본값으로 설정합니다: 객체의 경우에는 null값, 숫자 유형은 0, boolean은 false로 설정됩니다.
필드가 synthetic인 경우, 직렬화 및 역직렬화되지 않습니다.
내부 클래스, 익명클래스 및 로컬 클래스에 해당하는 외부클래스의 필드는 직렬화 및 역직렬화되지 않습니다.
중첩(nested) 클래스 (내부 클래스를 포함하는)
Gson은 static 중첩 클래스를 쉽게 직렬화할 수 있습니다. Gson은 또한 static 중첩클래스를 역직렬화할 수 있습니다.
그러나, Gson은 자동으로 pure 내부 클래스를 역직렬화할 수 없습니다. 인자 없는 기본 생성자는 클래스 객체의 참조가 필요하기 때문입니다.
당신은 내부클래스를 static으로 만들거나 custom InstanceCreator를 제공해서 문제를 해결할 수 있습니다.
여기 예제가 있습니다:
public class A {
public String a;
class B {
public String b;
public B() {
// No args constructor for B
}
}
}
주의: 위의 클래스 B는 기본적으로 Gson으로 직렬화할 수 없습니다.
클래스 B는 내부클래스이기 때문에, Gson은 {"b":"abc"}를 B의 인스턴스로 역직렬화할 수 없습니다.
만약 이것이 static으로 정의되었더라면, Gson은 문자열을 역직렬화 할 수 있었을 것 입니다.
다른 해결법은 B를 위한 사용자 정의(custom) 인스턴스 생성자를 작성하는 것 입니다.
public class InstanceCreatorForB implements InstanceCreator<A.B> {
private final A a;
public InstanceCreatorForB(A a) {
this.a = a;
}
public A.B createInstance(Type type) {
return a.new B();
}
}
위 방법으로 해결이 가능하지만 추천하지는 않습니다.
배열 예제
Gson gson = new Gson();
int[] ints = {1, 2, 3, 4, 5};
String[] strings = {"abc", "def", "ghi"};
// 직렬화
gson.toJson(ints); // ==> [1,2,3,4,5]
gson.toJson(strings); // ==> ["abc", "def", "ghi"]
// 역직렬화
int[] ints2 = gson.fromJson("[1,2,3,4,5]", int[].class);
// ==> ints2 will be same as ints
Gson은 다차원 배열을 지원합니다. 배열의 원소가 어떤 복잡한 타입이더라도 지원합니다.
컬렉션 예제
Gson gson = new Gson();
Collection<Integer> ints = Lists.immutableList(1,2,3,4,5);
// 직렬화
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
정말 끔찍합니다, 우리가 컬렉션 타입을 어떻게 지정했는지 확인하세요. 안타깝지만, 자바에서 이 것을 해결할 방법은 없습니다.
컬렉션 제한사항
Gson은 어떠한 객체의 컬렉션도 직렬화할 수 있습니다. 하지만 해당하는 객체 컬렉션의 역직렬화는 불가능합니다. 왜냐햐면 역직렬화로 만들어지는 객체의 유형을 사용자에게 알려줄 방법이 없기 때문입니다. 대신에 역직렬화하는동안 컬렌션은 구체적인 generic 타입이어야합니다. 이는 이치에 맞고, 좋은 자바 코딩 습관을 가지고 있다면 거의 문제가 없습니다.
Generic Types 직렬화 및 역직렬화하기
당신이 toJson(obj)을 호출할 때, Gson은 직렬화하기 위한 field의 정보를 얻기위해 obj.getClass()를 호출합니다.
마찬가지로 MyClass.class 객체를 fromJson(json, MyClass.class) 메서드에서 전달할 수 있습니다. 이것은 객체가 non-generic type이라면 잘 작동합니다. 그러나, 만약 객체가 generic type이라면 자바 Type Erasure에 의해서 Generic Type의 정보는 사라집니다. 이것을 설명하는 예는 다음과 같습니다:
class Foo<T> {
T value;
}
Gson gson = new Gson();
Foo<Bar> foo = new Foo<Bar>();
gson.toJson(foo); // foo.value는 올바르게 직렬화되지 않습니다.
gson.fromJson(json, foo.getClass()); // foo.value를 Bar로 역직렬화하는데 실패합니다.
위의 코드는 값을 Bar type으로 해석하는데 실패합니다. 왜냐하면 Gson은 받은 객체의 클래스 정보를 얻기 위해서 foo.getClass()를 호출하는데, 이 메서드는 처리되지 않은 클래스인 Foo.class를 반환하기 때문입니다. 무슨말이냐면, Gson은 이 객체의 type이 그냥 일반 Foo가 아닌 Foo<Bar>임을 알 수가 없습니다.
당신은 당신의 generic type을 위한 올바른 매개변수용 type을 지정함으로 이 문제를 해결할 수 있습니다. 그러기 위해서 TypeToken 클래스를 이용할 수 있습니다.
Type fooType = new TypeToken<Foo<Bar>>() {}.getType();
gson.toJson(foo, fooType);
gson.fromJson(json, fooType);
실제로 fooType을 얻기위해 사용되는 idiom은 getType()메서드를 갖는 익명 로컬 inner 클래스를 정의합니다. getType()메서드는 완전한 매개변수화된 type을 반환합니다.
그러면 추가적인 작업 없이도 Gson으로 컬력션 직렬화가 가능합니다: toJson(collection)은 원하는 결과를 만들 것 입니다.
그러나 역직렬화 fromJson(json, Collection.class)는 작동하지 않습니다. Gson은 입력받은 JSON 값을 어떤 타입에 매핑해야하는지 알 수 없기 때문입니다. Gson은 fromJson() 호출 시 일반화된 버전의 컬렉션 type이 필요합니다.
그러기 위한 3가지 방법이 있습니다:
배열 원소를 파싱하기 위해 Gson의 parser API를 사용하세요. (로우레벨 스트리밍 parser 또는 DOM parser JsonParser) 그리고 각각의 배열 원소에 Gson.fromJson()을 사용하세요. 이 방법을 추천합니다. 이 예제를 참고하세요.
각각의 배열 원소를 확인하고 적절한 객체에 매핑하는 Collection.class용 타입 어댑터를 등록하세요. 이 방법은 Gson이 다른 컬렉션 타입을 역 직렬화할 때 문제가 생기는 것이 단점입니다.
MyCollectionMemberType용 어댑터를 등록하세요. 그리고 fromJson()에 Collection<MyCollectionMemberType>을 사용하세요.
이 방법은 배열이 최상위 원소인 경우 또는 컬렉션을 가진 field 타입을 Collection<MyCollectionmemberType>으로 바꿀 수 있는 경우 작동합니다.
내장 시리얼라이저(Serializers)와 내장 디시리얼라이저(Deserializers)
Gson은 일반적으로 사용되는 (기본 representation이 부적적할 수 있는) 클래스를 위한 내장 시리얼라이저와 디시리얼라이저를 가지고 있습니다. 이러한 클래스의 목록이 여기 있습니다:
"https://github.com/google/gson/"과 같은 문자열을 위한 java.net.URL 클래스
"/google/gson/"과 같은 문자열을 위한 java.net.URI 클래스
또한, 일반적으로 사용되는 클래스(JodaTime같은 클래스)를 위한 소스 코드를 이 페이지에서 찾을 수 있습니다.
커스텀 직렬화 및 역직렬화
가끔 기본 표현이 당신이 원하는 바와 다를 수 있습니다. 라이브러리 클래스(DateTime, etc)를 다루는 경우 종종 이런 일이 발생합니다. 원하는 표현을 위해 당신은 커스텀 시리얼라이저와 디시리얼라이저를 등록할 수 있습니다. 두 파트를 정의하면 등록이 완료됩니다:
Json 시리얼라이저: 객체에 대한 커스텀 직렬화를 정의해야합니다.
Json 디시리얼라이저: 유형에 대한 커스텀 역직렬화를 정의해야합니다.
Instance Creators: 인자 없는 생성자를 사용할 수 있거나 디시리얼라이저가 등록이 되어있다면 필요하지 않습니다.
GsonBuilder gson = new GsonBuilder();
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 호출은 타입 어댑터가 이러한 인터페이스 중 하나 이상을 구현했는지 확인하고 모든 인터페이스에 등록합니다._
시리얼라이저 작성하기
여기 JodaTime의 DateTime 클래스용 커스텀 시리얼라이저를 어떻게 작성하는지에 대한 예가 있습니다.
private class DateTimeSerializer implements JsonSerializer<DateTime> {
public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
Gson은 직렬화하는 동안 DateTime 객체를 만나면 serialize()를 호출합니다.
디시리얼라이저 작성하기
여기 JodaTime의 DateTime 클래스를위한 커스텀 디시리얼라이저 작성법에 대한 예가 있습니다.
private class DateTimeDeserializer implements JsonDeserializer<DateTime> {
public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new DateTime(json.getAsJsonPrimitive().getAsString());
}
}
Gson이 JSON문자열을 DateTime 객체로 역직렬화할 때 Gson은 deserialize를 호출합니다.
시리얼라이저와 디시리얼라이저에 대해 신경써야할 부분
종종 따로 처리하지 않은 모든 generic 타입에 대한 단일 핸들러를 등록하기 원할 수 있습니다.
예를들어, (id 직렬화/역직렬화를 위한) Id 클래스를 가지고 있다고 가정해봅시다. (즉, 내부 vs 외부 표현)
모든 generic 타입에 대해 같은 직렬화를 갖는 Id<T> 타입 꼭 id 값을 쓰세요.
역직렬화는 비슷하지만 완전히 같지는 않습니다. Id<T> 인스턴스를 반환하는 new Id(Class<T>, String) 호출이 필요합니다.
Gson은 이를 위한 단일 핸들러 등록을 지원합니다. 당신은 또한 특정 generic 타입을 위한 특정 핸들러를 등록할 수 있습니다 (예를들면 특별한 처리가 필요한 Id<RequiresSpecialHandling>). toJson()과 fromJson()을 위한 타입 매개변수는 generic 타입 정보를 가집니다. 이 제네릭 타입 정보는 처리되지 않은 동일한 유형에 해당하는 모든 generic 타입을 위한 단일 핸들러 작성을 돕습니다.
인스턴스 Creator 작성하기
객체를 역직렬화하는 동안, Gson은 클래스의 기본 인스턴스를 생성해야합니다. 직렬화 및 역직렬화를 위한 올바른 클래스는 인자 없는 생성자를 가져야합니다.
public인지 private인지는 상관이 없습니다.
보통, 인자없는 생성자가 정의되지 않은 라이브러리 클래스를 다루는 경우 인스턴스 Creator가 필요합니다.
인스턴스 Creator 예제
private class MoneyInstanceCreator implements InstanceCreator<Money> {
public Money createInstance(Type type) {
return new Money("1000000", CurrencyCode.USD);
}
}
타입은 generic 타입일 수 있습니다.
구체적인 generic 타입 정보가 필요한 생성자를 호출하는데 유용합니다.
예를들어, Id클래스가 Id가 생성되는 클래스를 저장하는 경우.
매개변수화된 타입을 위한 인스턴스 Creator
인스턴스화하려는 타입이 매개변수화된 타입인 경우가 있습니다. 일반적으로 이것은 문제가 없습니다. 실제 인스턴스는 raw 타입이기 때문입니다. 여기 예제가 있습니다:
class MyList<T> extends ArrayList<T> {
}
class MyListInstanceCreator implements InstanceCreator<MyList<?>> {
@SuppressWarnings("unchecked")
public MyList<?> createInstance(Type type) {
// No need to use a parameterized list since the actual instance will have the raw type anyway.
//실제 인스턴스는 raw 타입을 가지기 때문에 매개변수화된 리스트를 사용할 필요가 없습니다.
return new MyList();
}
}
그러나, 실제 매개변수화 된 타입으로 인스턴스를 만들어야 하는 경우가 있습니다. 이런 경우에는, createInstance 메서드로 전달되는 타입 파라미터를 사용할 수 있습니다. 여기 그 예제가 있습니다:
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<Foo>에 바인딩 되어야하는) Id<Foo>의 자바 매개변수화된 타입 표현입니다._ Id 클래스는 하나의 매개변수화된 타입 파라미터 T가 있으므로, 이 경우에는 (Foo.class를 가질) getActualTypeArgument()로 부터 반환받는 타입 배열의 0번째 원소를 사용합니다.
촘촘한 vs 보기좋은, JSON 결과 포맷 출력하기
Gson이 기본으로 제공하는 JSON 출력은 촘촘한 JSON 포맷입니다. 이것은 출력된 JSON 구조에 어떠한 공백도 없다는 의미입니다. 그러므로, JSON 출력에 이름(키)과 값(객체, 배열) 사이에 공간이 없습니다. 게다가 "null" field는 결과에서 무시됩니다.(주의:null 값이 컬렉션, 열 객체에는 남아있을 것 입니다.) 모든 null 값을 출력하도록 하는 방법에 대한 정보는Null Object Support를 참고하세요.
보기좋은 출력 기능을 사용하고 싶다면, GsonBuilder를 사용해서 Gson 인스턴스를 설정하세요. JsonFormatter는 Public API가 아니므로, 사용자는 JSON출력에 대한 기본 출력 설정/여백을 설정할 수 없습니다. 현재로서는, 기본 라인 길이가 80자, 2칸 인덴트(들여쓰기) 및 4칸 오른쪽 여백인 기본 JsonPrintFormatter만 제공하고 있습니다.
다음은 기본 JsonPrintFormatter로 Gson 인스턴스를 설정하는 방법을 보여주는 예제입니다.(JsonCompactFormatter 대신)
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jsonOutput = gson.toJson(someObject);
Null 객체 지원
Gson에서 구현된 기본 동작은 null 객체 필드를 무시하는 것 입니다. 그래서 더 촘촘한(compact) 출력 형식을 만들어 줍니다; 그러나, 사용자는 이러한 필드에 대한 기본값을 반드시 정의해야합니다. JSON 포맷이 다시 Java 형식으로 변환되기 때문입니다.
null값을 출력하도록 Gson 인스턴스를 설정하는 방법은 다음과 같습니다:
Gson gson = new GsonBuilder().serializeNulls().create();
주목: null을 Gson으로 직렬화할 때, Gson은 JsonNull 원소를 JsonElement 구조에 추가합니다. 그러므로, 이 객체는 커스텀 직렬화/역직렬화에서 사용될 수 있습니다.
여기 예제가 있습니다:
public class Foo {
private final String s;
private final int i;
public Foo() {
this(null, 5);
}
public Foo(String s, int i) {
this.s = s;
this.i = i;
}
}
Gson gson = new GsonBuilder().serializeNulls().create();
Foo foo = new Foo();
String json = gson.toJson(foo);
System.out.println(json);
json = gson.toJson(null);
System.out.println(json);
이것의 결과:
{"s":null,"i":5}
null
버전관리 지원
@Since annotation을 사용하면 같은 객체에 여러가지 버전을 사용하는 것이 가능합니다. 이 어노테이션은 클래스와 필드에서 사용할 수 있고, 향후 릴리즈를 통해 메서드에서도 사용이 가능할 것 입니다. 이 기능을 사용하기 위해서, Gson 인스턴스가 특정 버전보다 높은 필드/객체를 무시하도록 설정해야합니다. Gson 인스턴스에 버전을 설정하지 않으면, 모든 필드와 클래스를 버전 상관없이 직렬화하고 역직렬화합니다.
public class VersionedClass {
@Since(1.1) private final String newerField;
@Since(1.0) private final String newField;
private final String field;
public VersionedClass() {
this.newerField = "newer";
this.newField = "new";
this.field = "old";
}
}
VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput);
System.out.println();
gson = new Gson();
jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput);
Gson은 상위 레벨 클래스와 필드 타입을 제외하기위한 여러가지 방법을 지원합니다. 다음은 필드와 클래스를 제외하도록 하는 pluggable한 방법입니다. 아래 방법 중에 어느 것도 원하는 요구를 충족시키지 못한다면, 커스텀 시리얼라이저 및 디시리얼라이저를 사용할 수 있습니다.
자바 Modifier로 제외하기
기본적으로, 필드를 transient로 표시한다면, 그것은 제외됩니다. 또한, 필드를 static으로 표시한다면, 기본적으로 그것은 외됩니다. 어떤 transient 필드를 포함하길 원한다면, 당신은 다음과 같이 하면 됩니다:
import java.lang.reflect.Modifier;
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC)
.create();
참고: excludeFieldsWithModifiers() 메서드에 Modifier 상수를 줄 수 있습니다.
예를 들어:
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
Gson의 @Expose
이 기능은 객체의 특정 필드를 표시하는 방법을 제공합니다. JSON의 직렬화 및 역직렬화를 고려하여 제외할 필드를 표시할 때 사용됩니다.
이 어노테이션을 사용하기 위해서는, new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()를 사용해서 Gson을 생성해야합니다. 생성된 Gson 인스턴스는 @Expose로 표시되지 않은 클래스의 모든 필드를 제외합니다.
사용자 정의 제외 방법(User Defined Exclusion Strategies)
위의 방법들이 원하는대로 작동하지 않는다면, 언제든지 당신의 Exclusion Strategy를 작성하여 Gson에 연결(plug)할 수 있습니다. 더 많은 정보를 원하시면 ExclusionStrategy 자바 문서를 확인하세요.
다음의 예제는@Foo로 표기된 필드를 제외하는 방법과 String 클래스의 최상위 타입(또는 선언된 필드 타입)을 제외하는 방법을 보여줍니다:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Foo {
// Field tag only annotation
}
public class SampleObjectForTest {
@Foo private final int annotatedField;
private final String stringField;
private final long longField;
private final Class<?> clazzField;
public SampleObjectForTest() {
annotatedField = 5;
stringField = "someDefaultValue";
longField = 1234;
}
}
public class MyExclusionStrategy implements ExclusionStrategy {
private final Class<?> typeToSkip;
private MyExclusionStrategy(Class<?> typeToSkip) {
this.typeToSkip = typeToSkip;
}
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz == typeToSkip);
}
public boolean shouldSkipField(FieldAttributes f) {
return f.getAnnotation(Foo.class) != null;
}
}
public static void main(String[] args) {
Gson gson = new GsonBuilder()
.setExclusionStrategies(new MyExclusionStrategy(String.class))
.serializeNulls()
.create();
SampleObjectForTest src = new SampleObjectForTest();
String json = gson.toJson(src);
System.out.println(json);
}
결과는 다음과 같습니다:
{"longField":1234}
JSON 필드 네이밍(Naming) 지원
Gson은 표준 자바 필드 이름(즉, 소문자로 시작하는 카멜케이스 표기법 --- sampleFieldNameInJava)을 Json 필드 이름(즉, sample_field_name_in_java 혹은 SampleFieldNameInJava)로 변환하기 위한 pre-defined 필드 naming 정책을 지원합니다. pre-defined naming 정책에 대한 정보는 FieldNamingPolicy 클래스에서 확인하세요
사용자가 필드별로 커스텀 이름을 정의할 수 있도록하는 annotation기반의 strategy도 있습니다. 참고로, annotation 기반의 straegy는 field name validation이 있습니다. 이 field name validation의 경우 유효하지 않은 이름이 annotation 값으로 주어지면 런타임 exception이 발생합니다.
다음은 두개의 Gson 네이밍 정책 기능을 사용하는 방법의 예 입니다:
private class SomeObject {
@SerializedName("custom_naming") private final String someField;
private final String someOtherField;
public SomeObject(String a, String b) {
this.someField = a;
this.someOtherField = b;
}
}
SomeObject someObject = new SomeObject("first", "second");
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);