如何使用 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 读回到对象时,代码失败,并出现异常。这里有一个例外:

Exception: 无法反序列化 java.lang 的实例 at [Source: java.io.StringReader@d70d7a; line: 1, column: 13] (through reference chain: com.tnal.prism.cobalt.gather.testing.Pojo["bar"])

为什么杰克逊在一个方向上运转良好,但在另一个方向上却失败了?它似乎应该能够再次将自己的输出作为输入。我知道我正在尝试做的是非传统的(一般的建议是为 bar创建一个内部对象,它有一个名为 A的属性) ,但是我根本不想与这个 JSON 交互。我的代码作为这段代码的传递者——我希望接收这段 JSON,然后在不接触任何内容的情况下再次发送出去,因为当 JSON 发生变化时,我不希望我的代码需要修改。

谢谢你的建议。

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

111065 次浏览

这是你的内部类的问题。Pojo类是测试类的 non-static inner class,Jackson 无法实例化该类。因此它可以序列化,但不能反序列化。

像这样重新定义你的类:

public static class Pojo {
public String foo;


@JsonRawValue
public String bar;
}

请注意 static的添加

@ JsonRawValue 仅用于序列化端,因为反方向处理起来要复杂一些。实际上,添加它是为了允许注入预编码的内容。

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

但是我认为对于您的特定情况的解决方案是将类型指定为‘ java.lang。对象’,因为这应该可以正常工作: 对于序列化,String 将按原样输出,对于反序列化,它将反序列化为 Map。实际上,如果是这样的话,您可能需要单独的 getter/setter; getter 将返回用于序列化的 String (并且需要@JsonRawValue) ; setter 将接受 Map 或 Object。如果有意义的话,您可以将其重新编码为 String。

按照 @ 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}
}
}

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

看看最后一个答案。 通过为以 JsonNode 作为参数并调用 jsonNode 上的 toString 方法来设置 String 属性的属性定义一个自定义 setter,所有这些都可以实现。

I was able to do this with a custom deserializer (cut and pasted from 给你)

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;

使用一个对象在两种情况下都可以很好地工作... ... 这种方法在两次反序列化原始值时会有一点开销。

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

@ JsonSetter 可能会有帮助,参见我的示例(‘ data’应该包含未解析的 JSON) :

class Purchase
{
String data;


@JsonProperty("signature")
String signature;


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

为了增加 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;
}
}

这个简单的解决办法对我很有效:

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.

这甚至可以在 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());
}

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).

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

然后,您可以在创建 ObjectMapper之后注册模块:

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


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

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

我使用 @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");
}
}