web-dev-qa-db-ja.com

Jacksonを使用してオブジェクトに生のJSONを含めるにはどうすればよいですか?

Jacksonを使用してオブジェクトを(デ)シリアル化するときに、Javaオブジェクト内に生のJSONを含めようとしています。この機能をテストするために、次のテストを作成しました。

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

@Test
public void test() throws JsonGenerationException, JsonMappingException, IOException {

    String foo = "one";
    String bar = "{\"A\":false}";

    Pojo pojo = new Pojo();
    pojo.foo = foo;
    pojo.bar = bar;

    String json = "{\"foo\":\"" + foo + "\",\"bar\":" + bar + "}";

    ObjectMapper objectMapper = new ObjectMapper();
    String output = objectMapper.writeValueAsString(pojo);
    System.out.println(output);
    assertEquals(json, output);

    Pojo deserialized = objectMapper.readValue(output, Pojo.class);
    assertEquals(foo, deserialized.foo);
    assertEquals(bar, deserialized.bar);
}

コードは次の行を出力します。

{"foo":"one","bar":{"A":false}}

JSONはまさに私が物事を見て欲しい方法です。残念ながら、JSONをオブジェクトに読み戻そうとすると、コードは例外で失敗します。例外は次のとおりです。

org.codehaus.jackson.map.JsonMappingException:[ソース:Java.io.StringReader@d70d7a;のSTART_OBJECTトークンからJava.lang.Stringのインスタンスをデシリアライズできません。行:1、列:13](参照チェーン経由:com.tnal.prism.cobalt.gather.testing.Pojo ["bar"])

ジャクソンが一方向ではうまく機能するのに、反対方向に進むと失敗するのはなぜですか?再び独自の出力を入力として受け取ることができるようです。私がやろうとしていることは非正統的であることは知っています(一般的なアドバイスは、barという名前のプロパティを持つAの内部オブジェクトを作成することです)が、やりたくありませんこのJSON。私のコードはこのコードのパススルーとして機能しています-JSONを変更し、コードを変更する必要がないため、何も変更せずにこのJSONを取得して再度送信する必要があります。

アドバイスをありがとう。

編集:Pojoを静的クラスにしたため、別のエラーが発生していました。

94
bhilstrom

@JsonRawValueは、逆方向の処理が少し難しいため、シリアル化側のみを対象としています。実際には、事前にエンコードされたコンテンツを挿入できるように追加されました。

逆のサポートを追加することは可能だと思いますが、それは非常に厄介です:コンテンツを解析し、「生の」形式に書き戻す必要があります。異なる場合があります)。これは一般的なケースです。ただし、問題の一部のサブセットでは意味があるかもしれません。

しかし、特定の場合の回避策は、タイプを「Java.lang.Object」として指定することです地図。実際には、別のゲッター/セッターが必要な場合があります。 getterは、シリアル化のためにStringを返します(@JsonRawValueが必要です)。セッターは、MapまたはObjectを受け取ります。理にかなっている場合は、文字列に再エンコードできます。

55
StaxMan

@ StaxMan answerに続いて、私は次の作品を魅力のように作りました:

public class Pojo {
  Object json;

  @JsonRawValue
  public String getJson() {
    // default raw value: null or "[]"
    return json == null ? null : json.toString();
  }

  public void setJson(JsonNode node) {
    this.json = node;
  }
}

そして、最初の質問に忠実であるために、ここに動作テストがあります:

public class PojoTest {
  ObjectMapper mapper = new ObjectMapper();

  @Test
  public void test() throws IOException {
    Pojo pojo = new Pojo("{\"foo\":18}");

    String output = mapper.writeValueAsString(pojo);
    assertThat(output).isEqualTo("{\"json\":{\"foo\":18}}");

    Pojo deserialized = mapper.readValue(output, Pojo.class);
    assertThat(deserialized.json.toString()).isEqualTo("{\"foo\":18}");
    // deserialized.json == {"foo":18}
  }
}
48
yves amsellem

カスタムデシリアライザーでこれを行うことができました( here からカットアンドペースト)

package etc;

import Java.io.IOException;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

/**
 * Keeps json value as json, does not try to deserialize it
 * @author roytruelove
 *
 */
public class KeepAsJsonDeserialzier extends JsonDeserializer<String> {

    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        TreeNode tree = jp.getCodec().readTree(jp);
        return tree.toString();
    }
}
32
Roy Truelove

@JsonSetterが役立つ場合があります。私のサンプルを参照してください(「データ」には未解析のJSONが含まれているはずです):

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

    @JsonSetter("data")
    void setData(JsonNode data)
    {
        this.data = data.toString();
    }
}
15
anagaf

これはJPAエンティティでも機能します。

private String json;

@JsonRawValue
public String getJson() {
    return json;
}

public void setJson(final String json) {
    this.json = json;
}

@JsonProperty(value = "json")
public void setJsonRaw(JsonNode jsonNode) {
    // this leads to non-standard json, see discussion: 
    // setJson(jsonNode.toString());

    StringWriter stringWriter = new StringWriter();
    ObjectMapper objectMapper = new ObjectMapper();
    JsonGenerator generator = 
      new JsonFactory(objectMapper).createGenerator(stringWriter);
    generator.writeTree(n);
    setJson(stringWriter.toString());
}

理想的には、ObjectMapperおよびJsonFactoryさえもコンテキストからのものであり、JSONを正しく処理するように構成されています(標準または「Infinity」フロートなどの非標準値を使用)。

3
Georg

これは、内部クラスの問題です。 Pojoクラスは、テストクラスの 非静的内部クラス であり、ジャクソンはそのクラスをインスタンス化できません。したがって、シリアライズできますが、デシリアライズできません。

次のようにクラスを再定義します。

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

staticが追加されていることに注意してください

3
skaffman

Roy Truelove の素晴らしい answer に追加して、これが注入方法です@JsonRawValueの出現に応じたカスタムデシリアライザー:

import com.fasterxml.jackson.databind.Module;

@Component
public class ModuleImpl extends Module {

    @Override
    public void setupModule(SetupContext context) {
        context.addBeanDeserializerModifier(new BeanDeserializerModifierImpl());
    }
}

import Java.util.Iterator; import com.fasterxml.jackson.annotation.JsonRawValue; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.SettableBeanProperty; public class BeanDeserializerModifierImpl extends BeanDeserializerModifier { @Override public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) { Iterator<SettableBeanProperty> it = builder.getProperties(); while (it.hasNext()) { SettableBeanProperty p = it.next(); if (p.getAnnotation(JsonRawValue.class) != null) { builder.addOrReplaceProperty(p.withValueDeserializer(KeepAsJsonDeserialzier.INSTANCE), true); } } return builder; } }
3
Amir Abiri

この簡単な解決策は私のために働いた:

public class MyObject {
    private Object rawJsonValue;

    public Object getRawJsonValue() {
        return rawJsonValue;
    }

    public void setRawJsonValue(Object rawJsonValue) {
        this.rawJsonValue = rawJsonValue;
    }
}

そのため、JSONの生の値をrawJsonValue変数に格納でき、他のフィールドで(オブジェクトとして)デシリアライズしてJSONに戻し、REST経由で送信しても問題ありませんでした。 @JsonRawValueを使用しても、保存されたJSONがオブジェクトとしてではなく文字列としてデシリアライズされ、それが私が望んでいたものではなかったため、役に立たなかった。

3
Pavol Golias

オブジェクトの使用はどちらの方法でも正常に機能します...このメソッドには、生の値を2回で逆シリアル化するためのオーバーヘッドが少しあります。

ObjectMapper mapper = new ObjectMapper();
RawJsonValue value = new RawJsonValue();
value.setRawValue(new RawHello(){{this.data = "universe...";}});
String json = mapper.writeValueAsString(value);
System.out.println(json);
RawJsonValue result = mapper.readValue(json, RawJsonValue.class);
json = mapper.writeValueAsString(result.getRawValue());
System.out.println(json);
RawHello hello = mapper.readValue(json, RawHello.class);
System.out.println(hello.data);

RawHello.Java

public class RawHello {

    public String data;
}

RawJsonValue.Java

public class RawJsonValue {

    private Object rawValue;

    public Object getRawValue() {
        return rawValue;
    }

    public void setRawValue(Object value) {
        this.rawValue = value;
    }
}
1
Vozzie

私はまったく同じ問題を抱えていました。この投稿で解決策を見つけました: Jacksonまたはその代替を使用してJSONツリーをプレーンクラスに解析します

最後の答えをご覧ください。 JsonNodeをパラメーターとして受け取り、jsonNodeでtoStringメソッドを呼び出してStringプロパティを設定するプロパティのカスタムセッターを定義することで、すべてが機能します。

1
Alex Kubity

私は同様の問題を抱えていましたが、多くのJSON項目(List<String>)を含むリストを使用していました。

public class Errors {
    private Integer status;
    private List<String> jsons;
}

@JsonRawValueアノテーションを使用してシリアル化を管理しました。しかし、逆シリアル化のために、Royの提案に基づいてカスタムdeserializerを作成する必要がありました。

public class Errors {

    private Integer status;

    @JsonRawValue
    @JsonDeserialize(using = JsonListPassThroughDeserialzier.class)
    private List<String> jsons;

}

以下に、私の「リスト」デシリアライザを見ることができます。

public class JsonListPassThroughDeserializer extends JsonDeserializer<List<String>> {

    @Override
    public List<String> deserialize(JsonParser jp, DeserializationContext cxt) throws IOException, JsonProcessingException {
        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
            final List<String> list = new ArrayList<>();
            while (jp.nextToken() != JsonToken.END_ARRAY) {
                list.add(jp.getCodec().readTree(jp).toString());
            }
            return list;
        }
        throw cxt.instantiationException(List.class, "Expected Json list");
    }
}
1
Biga

ジャクソンモジュールを使用して@JsonRawValueは両方の方法で動作します(シリアル化と逆シリアル化):

public class JsonRawValueDeserializerModule extends SimpleModule {

    public JsonRawValueDeserializerModule() {
        setDeserializerModifier(new JsonRawValueDeserializerModifier());
    }

    private static class JsonRawValueDeserializerModifier extends BeanDeserializerModifier {
        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
            builder.getProperties().forEachRemaining(property -> {
                if (property.getAnnotation(JsonRawValue.class) != null) {
                    builder.addOrReplaceProperty(property.withValueDeserializer(JsonRawValueDeserializer.INSTANCE), true);
                }
            });
            return builder;
        }
    }

    private static class JsonRawValueDeserializer extends JsonDeserializer<String> {
        private static final JsonDeserializer<String> INSTANCE = new JsonRawValueDeserializer();

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            return p.readValueAsTree().toString();
        }
    }
}

ObjectMapperを作成した後、モジュールを登録できます:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonRawValueDeserializerModule());

String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
1
Helder Pereira