杰克逊JSON反序列化,忽略JSON中的根元素

47

如何忽略JSON中的父标签?

以下是我的JSON:

String str = "{\"parent\": {\"a\":{\"id\": 10, \"name\":\"Foo\"}}}";

这里是从json映射的类。

public class RootWrapper {
  private List<Foo> foos;

  public List<Foo> getFoos() {
    return foos;
  }

  @JsonProperty("a")
  public void setFoos(List<Foo> foos) {
    this.foos = foos;
  }
 }

以下是测试代码:

public class JacksonTest {
   // code here
}
@Test
public void wrapRootValue() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE, true);
    mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    String str = "{\"parent\": {\"a\":{\"id\": 10, \"name\":\"Foo\"}}}";

    RootWrapper root = mapper.readValue(str, RootWrapper.class);

    Assert.assertNotNull(root);
}

我遇到了这个错误:

 org.codehaus.jackson.map.JsonMappingException: Root name 'parent' does not match expected ('RootWrapper') for type [simple type, class MavenProjectGroup.mavenProjectArtifact.RootWrapper]

我发现了Jackson注解提供的解决方案:

  (a) Annotate you class as below

  @JsonRootName(value = "parent")
  public class RootWrapper {

  (b) It will only work if and only if ObjectMapper is asked to wrap.
    ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE, true);

任务完成!

Jackson反序列化方式又出了问题 :(

如果配置了'DeserializationConfig.Feature.UNWRAP_ROOT_VALUE',它将解包所有的JSONs,即使我的类没有用@JsonRootName(value =“rootTagInJson”)注释,这不奇怪吗。

我希望只有当类使用@JsonRootName进行注释时才解包根标记,否则不要解包。

因此,以下是解包根标记的用例。

  ###########################################################
     Unwrap only if the class is annotated with @JsonRootName.
  ############################################################

我在Jackson源代码的ObjectMapper中做了一个小改动,并创建了一个新版本的jar。 1. 将此方法放置在ObjectMapper中

// Ash:: Wrap json if the class being deserialized, are annotated
// with @JsonRootName else do not wrap.
private boolean hasJsonRootName(JavaType valueType) {
    if (valueType.getRawClass() == null)
        return false;

    Annotation rootAnnotation =  valueType.getRawClass().getAnnotation(JsonRootName.class);
    return rootAnnotation != null;
}


    2. Edit ObjectMapper method :: 
    Replace 
       cfg.isEnabled(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE)
    with
       hasJsonRootName(valueType)

    3. Build your jar file and use it.

4
这并不是一个问题。你被鼓励提出并回答自己的问题,但我建议你将其作为一个问题和答案对来进行。我会翻译成中文:这不是真正的问题。您可以自己提问并回答,但建议您将其设置为问题和答案的形式。 - James Wiseman
2
除了自动包装/解包之外,我发现简单的单属性包装器类非常有效;或者绑定到 Map<String,WrappedType> 然后获取唯一条目的值。 - StaxMan
我喜欢这个答案!!我只有一个问题?如果我想这样做怎么办:TypeReference ref = new TypeReference<List<MyObj>>() {} );然后在这里进行一些解包。我的问题是JsonRootName在这里期望'List',但我的对象有不同的东西。我无法弄清楚如何向ref变量添加注释? - benathon
1
@StaxMan所建议的实际上非常有用,也是在您可能正在编写JerseyTest并意识到@Ash的解决方案尚未成为apache源代码的一部分时实现目标的最短路径。我花了几分钟的时间挣扎和解决如何在Java代码中绑定通用类型:myClientResponse.getEntity(new GenericType<Map<String, WrappedType>>(){}); - pulkitsinghal
5个回答

40
https://github.com/FasterXML/jackson-databind中的文件中提取的示例可能会给出更好的方法。具体而言,可以使用来实现这一点。
private ObjectMapper rootMapper()
{
    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
    mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
    return mapper;
}

public void testRootUsingExplicitConfig() throws Exception
{
    ObjectMapper mapper = new ObjectMapper();
    ObjectWriter writer = mapper.writer().withRootName("wrapper");
    String json = writer.writeValueAsString(new Bean());
    assertEquals("{\"wrapper\":{\"a\":3}}", json);

    ObjectReader reader = mapper.reader(Bean.class).withRootName("wrapper");
    Bean bean = reader.readValue(json);
    assertNotNull(bean);

    // also: verify that we can override SerializationFeature as well:
    ObjectMapper wrapping = rootMapper();
    json = wrapping.writer().withRootName("something").writeValueAsString(new Bean());
    assertEquals("{\"something\":{\"a\":3}}", json);
    json = wrapping.writer().withRootName("").writeValueAsString(new Bean());
    assertEquals("{\"a\":3}", json);

    bean = wrapping.reader(Bean.class).withRootName("").readValue(json);
    assertNotNull(bean);
}

15

我在使用Spring开发restful应用时遇到了类似的问题。我需要支持非常异构的API,其中一些具有根元素,而另一些则没有。我找不到比实时配置此属性更好的解决方案。很遗憾,在Jackson中没有对每个类的根元素拆包提供支持。无论如何,希望对某些人有所帮助。

@Component
public class ObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
    private void autoconfigureFeatures(JavaType javaType) {
        Annotation rootAnnotation = javaType.getRawClass().getAnnotation(JsonRootName.class);
        this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, rootAnnotation != null);
    }

    @Override
    protected Object _readMapAndClose(JsonParser jsonParser, JavaType javaType) throws IOException, JsonParseException, JsonMappingException {
        autoconfigureFeatures(javaType);
        return super._readMapAndClose(jsonParser, javaType);
    }

}

1
我已经放置了代码,但我认为我需要配置Spring才能使用它。您能否更详细地描述如何使用这个类? - Alireza Fattahi

4
非常简单:
创建一个对象映射器的实例,并启用包装和展开根值。
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);

@JsonRootName("yourname") 注解添加到您的 DTO 中

@JsonRootName("root")
public class YourDto {
    // ...
}

0
作为 Seagabond 帖子的更新,如果您想在编写参数值时具有相同的效果,可以重写附加的写入方法。
@Component
public class ObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
    private void autoconfigureFeatures(Object value) {
        JavaType javaType = _typeFactory.constructType(value.getClass());
        autoconfigureFeatures(javaType);
    }
    private void autoconfigureFeatures(JavaType javaType) {
        Annotation rootAnnotation = javaType.getRawClass().getAnnotation(JsonRootName.class);
        this.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, rootAnnotation != null);
    }

    @Override
    public void writeValue(DataOutput out, Object value) throws IOException {
        autoconfigureFeatures(value);
        super.writeValue(out, value);
    }

    @Override
    public void writeValue(Writer w, Object value) throws IOException, JsonGenerationException, JsonMappingException {
        autoconfigureFeatures(value);
        super.writeValue(w, value);
    }

    @Override
    public byte[] writeValueAsBytes(Object value) throws JsonProcessingException {
        autoconfigureFeatures(value);
        return super.writeValueAsBytes(value);
    }

    @Override
    public String writeValueAsString(Object value) throws JsonProcessingException {
        autoconfigureFeatures(value);
        return super.writeValueAsString(value);
    }

    @Override
    protected Object _readMapAndClose(JsonParser jsonParser, JavaType javaType) throws IOException, JsonParseException, JsonMappingException {
        autoconfigureFeatures(javaType);
        return super._readMapAndClose(jsonParser, javaType);
    }

}

0

我也遇到了这种问题。在我定义RestTemplate和它的MessageConverters的配置类中,我添加了以下代码:

Annotation rootAnnotation = mapper.getTypeFactory().getClass().getAnnotation(JsonRootName.class); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, Objects.nonNull(rootAnnotation));

基本上,这个配置将帮助Mapper识别提供的类是否有“@JsonRootName”注释。如果没有,则无需搜索根目录。希望这会对您和其他人有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接