Jackson:アノテーション無しのカスタムシリアライゼーション・デシリアライゼーション

2019年11月21日

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()); } } }