Jackson:アノテーション無しのカスタムシリアライゼーション・デシリアライゼーション
Jacksonにおいて、カスタムなシリアライゼーション・デシリアライゼーションを行いたいことがある。例えば、以下のオブジェクトについて、
class Foo {
int a;
int b;
}
...
Foo foo = new Foo();
foo.a = 123;
foo.b = 456;
通常であれば、以下のようにシリアライズされる。
{"a":123,"b":456}
しかし時には、普通でないシリアライゼーション・デシリアライゼーションを行いたい。例えば、
"123:456"
という単一の文字列としてシリアライゼーション・デシリアライゼーションしたいものとする。
しかし、巷の例では「FooクラスにアノテーションをつけてJacksonに指示する」という解決法がほとんどだ。しかし以下のような状況もある。
- アノテーションをつけたくない。
- アノテーションをつけられない。
この場合にどうするかを示したのが以下である。もちろんこれはあくまでも例だ。
ちなみにObjectMapperのコンフィギュレーションについては、Jackson:ObjectMapperのコンフィギュレーションを参照のこと。
// gradleの依存
dependencies {
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.9.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
import static org.junit.Assert.*;
import java.io.*;
import java.lang.reflect.*;
import org.junit.*;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonAutoDetect.*;
import com.fasterxml.jackson.annotation.JsonInclude.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.std.*;
import com.fasterxml.jackson.databind.module.*;
import com.fasterxml.jackson.databind.node.*;
import com.fasterxml.jackson.databind.ser.std.*;
public class JacksonSample {
@Test
public void test() throws Exception {
Foo foo = new Foo(123, 456);
String json = mapper.writeValueAsString(foo);
assertEquals("\"123:456\"", json);
Foo deserialized = mapper.readValue(json, Foo.class);
assertEquals(123, deserialized.a);
assertEquals(456, deserialized.b);
}
static class Foo {
int a;
int b;
public Foo(String string) {
String[]splited = string.split(":");
a = Integer.parseInt(splited[0]);
b = Integer.parseInt(splited[1]);
}
public Foo(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public String toString() {
return a + ":" + b;
}
}
SimpleModule module = new SimpleModule()
.addSerializer(new ToStringSerializer<Foo>(Foo.class))
.addDeserializer(Foo.class, new ConstructorDeserializer<Foo>(Foo.class))
;
ObjectMapper mapper = new ObjectMapper()
.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
.setSerializationInclusion(Include.NON_NULL)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.registerModule(module);
;
static class ConstructorDeserializer<T> extends StdDeserializer<T> {
/** 単一の文字列を受け入れるコンストラクタ */
final Constructor<T>ctr;
/**
* クラス指定は必須。
* @param vc 対象クラス
*/
public ConstructorDeserializer(Class<T> vc) {
super(vc);
try {
ctr = vc.getConstructor(String.class);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* デシリアライズする
*/
@Override
public T deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
TextNode node = (TextNode)jp.getCodec().readTree(jp);
try {
return ctr.newInstance(node.asText());
} catch (Exception ex) {
throw new IOException(ex);
}
}
}
static class ToStringSerializer<T> extends StdSerializer<T> {
public ToStringSerializer() {
this(null);
}
public ToStringSerializer(Class<T> t) {
super(t);
}
/** 値オブジェクトのtoString()を呼び出してJSON値とする。 */
@Override
public void serialize(
T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(value.toString());
}
}
}