Jackson:JSON文字列をそのままオブジェクトに出し入れする

2020年1月7日

問題

言い方が難しいのだが、やりたいことはこういうことだ。

以下のように、フィールドyにはJSON文字列を格納する。

  public class Foo {
    String x = "a";
    String y = "{\"b\": 123}";
  }

このFooオブジェクトをJacksonで普通にJSON化すると以下になる。

{"x":"a","y":"{\"b\": 123}"}

これは当然だ、yに格納されている文字列は「単なる文字列」であって、JSONとはみなされず、全体がダブルクォートで囲まれ、中にあったダブルクォートはエスケープされてしまう。「”{\”b\”: 123}”」の二つの「\”」の部分だ。

しかし、これを以下のようにシリアライズしたい。

{"x":"a","y":{"b": 123}}

さらに、デシリアライズ時には元のyのJSON文字列を取得したい。

※シリアライズ側に細工して上記のようなJSON文字列が得られても、そのままではデシリアライズできない。なぜなら、yフィールドは文字列であり、そこに{“b”:123}をデシリアライズすることはできないからだ。

理由

これがやりたい理由というのはこうだ。

別の場所でJSON化されたオブジェクトAがあり、それをこちらのオブジェクトBに格納してJSON化した場合、先のようにただの文字列になってしまうのだが、これではPretty-Printした場合にもAは文字列として扱われてしまい、A部分はPretty-Printされず、構造がわかりにくい。

つまり、BをPretty-Printした時に、Aも同時にPrettyになって欲しいのである。

解決

以下で解決することができた。

  // JSONをそのまま格納するクラスを作成
  public class JsonAsIs {

    public final String json;    

    public JsonAsIs(String json) {
      this.json = json;
    }

    @Override
    public String toString() {
      return json;
    }
  }

  ...............

  // JsonAsIsクラスのシリアライゼーション・デシリアライゼーションを指定するモジュール  
  SimpleModule module = new SimpleModule()
      .addSerializer(JsonAsIs.class, new StdSerializer<JsonAsIs>(JsonAsIs.class) {
        @Override
        public void serialize(
            JsonAsIs value, JsonGenerator jgen, SerializerProvider provider) 
          throws IOException, JsonProcessingException {        
          jgen.writeRawValue(value.toString());
        }
      })
      .addDeserializer(JsonAsIs.class, new StdDeserializer<JsonAsIs>(JsonAsIs.class) {
      @Override
      public JsonAsIs deserialize(JsonParser jp, DeserializationContext ctxt) 
        throws IOException, JsonProcessingException {  
        TreeNode tree = jp.getCodec().readTree(jp);
        return new JsonAsIs( tree.toString());        
      }
    });

  // モジュールを指定してObjectmapperを作成
  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)
      ;

これを以下のように使う。

  public class Foo {
    String x = "abc";
    // StringではなくJsonAsIsに格納
    JsonAsIs y = new JsonAsIs("{\"b\": 123}");
  }

....
    String serialized = mapper.writeValueAsString(new Foo());
    System.out.println(serialized);
    Foo deserialized = mapper.readValue(serialized, Foo.class);
    System.out.println(deserialized.y);

出力結果は以下だ。

{"x":"abc","y":{"b": 123}}
{"b":123}

参考

以下を参考にした。