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を静的クラスにしたため、別のエラーが発生していました。
@JsonRawValueは、逆方向の処理が少し難しいため、シリアル化側のみを対象としています。実際には、事前にエンコードされたコンテンツを挿入できるように追加されました。
逆のサポートを追加することは可能だと思いますが、それは非常に厄介です:コンテンツを解析し、「生の」形式に書き戻す必要があります。異なる場合があります)。これは一般的なケースです。ただし、問題の一部のサブセットでは意味があるかもしれません。
しかし、特定の場合の回避策は、タイプを「Java.lang.Object」として指定することです地図。実際には、別のゲッター/セッターが必要な場合があります。 getterは、シリアル化のためにStringを返します(@JsonRawValueが必要です)。セッターは、MapまたはObjectを受け取ります。理にかなっている場合は、文字列に再エンコードできます。
@ 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}
}
}
カスタムデシリアライザーでこれを行うことができました( 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();
}
}
@JsonSetterが役立つ場合があります。私のサンプルを参照してください(「データ」には未解析のJSONが含まれているはずです):
class Purchase
{
String data;
@JsonProperty("signature")
String signature;
@JsonSetter("data")
void setData(JsonNode data)
{
this.data = data.toString();
}
}
これは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」フロートなどの非標準値を使用)。
これは、内部クラスの問題です。 Pojo
クラスは、テストクラスの 非静的内部クラス であり、ジャクソンはそのクラスをインスタンス化できません。したがって、シリアライズできますが、デシリアライズできません。
次のようにクラスを再定義します。
public static class Pojo {
public String foo;
@JsonRawValue
public String bar;
}
static
が追加されていることに注意してください
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;
}
}
この簡単な解決策は私のために働いた:
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がオブジェクトとしてではなく文字列としてデシリアライズされ、それが私が望んでいたものではなかったため、役に立たなかった。
オブジェクトの使用はどちらの方法でも正常に機能します...このメソッドには、生の値を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;
}
}
私はまったく同じ問題を抱えていました。この投稿で解決策を見つけました: Jacksonまたはその代替を使用してJSONツリーをプレーンクラスに解析します
最後の答えをご覧ください。 JsonNodeをパラメーターとして受け取り、jsonNodeでtoStringメソッドを呼び出してStringプロパティを設定するプロパティのカスタムセッターを定義することで、すべてが機能します。
私は同様の問題を抱えていましたが、多くの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");
}
}
ジャクソンモジュールを使用して@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);