如何使用 Jackson 将原始 JSON 包含在对象中?

发布于 2024-10-14 03:51:03 字数 1463 浏览 13 评论 0原文

当使用 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:无法从 START_OBJECT 令牌中反序列化 java.lang.String 的实例 在[来源:java.io.StringReader@d70d7a;行:1,列:13](通过参考链:com.tnal.prism.cobalt.gather.testing.Pojo[“bar”])

为什么Jackson在一个方向上工作得很好,但在另一个方向上却失败了?看起来它应该能够再次将自己的输出作为输入。我知道我想做的事情是非正统的(一般建议是为 bar 创建一个具有名为 A 属性的内部对象),但我不这样做想要与这个 JSON 进行交互。我的代码充当此代码的传递 - 我想接收此 JSON 并再次将其发送回来而不触及任何东西,因为当 JSON 更改时,我不希望我的代码需要修改。

谢谢你的建议。

编辑:使 Pojo 成为静态类,这导致了不同的错误。

I am trying to include raw JSON inside a Java object when the object is (de)serialized using Jackson. In order to test this functionality, I wrote the following test:

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

The code outputs the following line:

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

The JSON is exactly how I want things to look. Unfortunately, the code fails with an exception when attempting to read the JSON back in to the object. Here is the exception:

org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader@d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])

Why does Jackson function just fine in one direction but fail when going the other direction? It seems like it should be able to take its own output as input again. I know what I'm trying to do is unorthodox (the general advice is to create an inner object for bar that has a property named A), but I don't want to interact with this JSON at all. My code is acting as a pass-through for this code -- I want to take in this JSON and send it back out again without touching a thing, because when the JSON changes I don't want my code to need modifications.

Thanks for the advice.

EDIT: Made Pojo a static class, which was causing a different error.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

瞳孔里扚悲伤 2024-10-21 03:51:03

@JsonRawValue 仅适用于序列化端,因为反向处理起来有点棘手。实际上,添加它是为了允许注入预编码内容。

我想可以添加对反向的支持,尽管这会很尴尬:必须解析内容,然后重新写回“原始”形式,这可能相同也可能不同(因为字符引用可能有所不同)。
这适用于一般情况。但也许这对于某些问题来说是有意义的。

但我认为针对您的具体情况的解决方法是将类型指定为“java.lang.Object”,因为这应该可以正常工作:对于序列化,字符串将按原样输出,对于反序列化,它将被反序列化为一张地图。实际上,如果是这样,您可能希望有单独的 getter/setter; getter 将返回 String 进行序列化(并且需要 @JsonRawValue); setter 将采用 Map 或 Object。如果有意义的话,您可以将其重新编码为字符串。

@JsonRawValue is intended for serialization-side only, since the reverse direction is a bit trickier to handle. In effect it was added to allow injecting pre-encoded content.

I guess it would be possible to add support for reverse, although that would be quite awkward: content will have to be parsed, and then re-written back to "raw" form, which may or may not be the same (since character quoting may differ).
This for general case. But perhaps it would make sense for some subset of problems.

But I think a work-around for your specific case would be to specify type as 'java.lang.Object', since this should work ok: for serialization, String will be output as is, and for deserialization, it will be deserialized as a Map. Actually you might want to have separate getter/setter if so; getter would return String for serialization (and needs @JsonRawValue); and setter would take either Map or Object. You could re-encode it to a String if that makes sense.

许久 2024-10-21 03:51:03

根据 @StaxMan 的回答,我制作了以下作品,就像一个魅力:

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}
  }
}

Following @StaxMan answer, I've made the following works like a charm:

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

And, to be faithful to the initial question, here is the working test:

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}
  }
}
追风人 2024-10-21 03:51:03

我可以使用自定义反序列化器来完成此操作(从此处剪切并粘贴)

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 KeepAsJsonDeserializer extends JsonDeserializer<String> {
    
    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException {
        
        TreeNode tree = jp.getCodec().readTree(jp);
        return tree.toString();
    }
}

通过注释所需的成员来使用它,如下所示:

@JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;

I was able to do this with a custom deserializer (cut and pasted from 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 KeepAsJsonDeserializer extends JsonDeserializer<String> {
    
    @Override
    public String deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException {
        
        TreeNode tree = jp.getCodec().readTree(jp);
        return tree.toString();
    }
}

Use it by annotating the desired member like this:

@JsonDeserialize(using = KeepAsJsonDeserializer.class)
private String value;
十六岁半 2024-10-21 03:51:03

@JsonSetter 可能会有所帮助。请参阅我的示例(“数据”应该包含未解析的 JSON):

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

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

@JsonSetter may help. See my sample ('data' is supposed to contain unparsed JSON):

class Purchase
{
    String data;

    @JsonProperty("signature")
    String signature;

    @JsonSetter("data")
    void setData(JsonNode data)
    {
        this.data = data.toString();
    }
}
送你一个梦 2024-10-21 03:51:03

这是你的内部类的问题。 Pojo 类是一个 非静态内部类 你的测试类,杰克逊无法实例化该类。所以它可以序列化,但不能反序列化。

像这样重新定义你的类:

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

注意添加 static

This is a problem with your inner classes. The Pojo class is a non-static inner class of your test class, and Jackson cannot instantiate that class. So it can serialize, but not deserialize.

Redefine your class like this:

public static class Pojo {
    public String foo;

    @JsonRawValue
    public String bar;
}

Note the addition of static

自在安然 2024-10-21 03:51:03

添加到 Roy Truelove 的伟大答案,这是如何注入自定义反序列化器以响应 @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;
    }
}

Adding to Roy Truelove's great answer, this is how to inject the custom deserialiser in response to appearance of @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;
    }
}
々眼睛长脚气 2024-10-21 03:51:03

这个简单的解决方案对我有用:

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 被反序列化为字符串,而不是对象,而这不是我想要的。

This easy solution worked for me:

public class MyObject {
    private Object rawJsonValue;

    public Object getRawJsonValue() {
        return rawJsonValue;
    }

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

So I was able to store raw value of JSON in rawJsonValue variable and then it was no problem to deserialize it (as object) with other fields back to JSON and send via my REST. Using @JsonRawValue didnt helped me because stored JSON was deserialized as String, not as object, and that was not what I wanted.

无人接听 2024-10-21 03:51:03

这甚至适用于 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”浮点数)。

This even works in a JPA entity:

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

Ideally the ObjectMapper and even JsonFactory are from the context and are configured so as to handle your JSON correctly (standard or with non-standard values like 'Infinity' floats for example).

一个人的旅程 2024-10-21 03:51:03

以下是如何使用 Jackson 模块使 @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);

Here is a full working example of how to use Jackson modules to make @JsonRawValue work both ways (serialization and deserialization):

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

Then you can register the module after creating the ObjectMapper:

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

String json = "{\"foo\":\"one\",\"bar\":{\"A\":false}}";
Pojo deserialized = objectMapper.readValue(json, Pojo.class);
人间☆小暴躁 2024-10-21 03:51:03

我有完全相同的问题。
我在这篇文章中找到了解决方案:
使用 Jackson 或其类将 JSON 树解析为普通类替代方案

查看最后一个答案。
通过为属性定义一个自定义设置器,该属性采用 JsonNode 作为参数,并调用 jsonNode 上的 toString 方法来设置 String 属性,这一切都可以解决。

I had the exact same issue.
I found the solution in this post :
Parse JSON tree to plain class using Jackson or its alternatives

Check out the last answer.
By defining a custom setter for the property that takes a JsonNode as parameter and calls the toString method on the jsonNode to set the String property, it all works out.

oО清风挽发oО 2024-10-21 03:51:03

使用对象两种方式都可以正常工作...此方法在两次反序列化原始值时会产生一些开销。

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

Using an object works fine both ways... This method has a bit of overhead deserializing the raw value in two times.

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;
    }
}
南城旧梦 2024-10-21 03:51:03

我遇到了类似的问题,但使用了包含大量 JSON itens 的列表 (List)。

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

我使用 @JsonRawValue 注释管理序列化。但对于反序列化,我必须根据 Roy 的建议创建一个自定义的反序列化器

public class Errors {

    private Integer status;

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

}

下面您可以看到我的“List”解串器。

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

I had a similar problem, but using a list with a lot of JSON itens (List<String>).

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

I managed the serialization using the @JsonRawValue annotation. But for deserialization I had to create a custom deserializer based on Roy's suggestion.

public class Errors {

    private Integer status;

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

}

Below you can see my "List" deserializer.

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");
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文