Mapping a Dynamic JSON Object with Jackson – 用Jackson映射一个动态JSON对象

最后修改: 2018年 12月 26日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

Working with predefined JSON data structures with Jackson is straightforward. However, sometimes we need to handle dynamic JSON objects, which have unknown properties.

用Jackson处理预定义的JSON数据结构是很直接的。然而,有时我们需要处理动态的JSON对象,它有未知的属性

In this quick tutorial, we’ll learn multiple ways of mapping dynamic JSON objects into Java classes.

在这个快速教程中,我们将学习将动态JSON对象映射到Java类的多种方法。

Note that in all of the tests, we assume we have the field objectMapper of type com.fasterxml.jackson.databind.ObjectMapper.

注意,在所有的测试中,我们假设我们有objectMapper类型的com.fastxml.jackson.databind.ObjectMapper字段。

2. Using JsonNode

2.使用JsonNode

Let’s say we want to process product specifications in a web shop. All the products have some common properties, but they have different ones as well, depending on the type of the product.

假设我们想在一个网店中处理产品规格。所有的产品都有一些共同的属性,但根据产品的类型,它们也有不同的属性。

For example, we want to know the aspect ratio of the display of a cell phone, but this property doesn’t make much sense for a shoe.

例如,我们想知道手机显示屏的长宽比,但这个属性对鞋子来说没有什么意义。

The data structure looks like this:

数据结构看起来像这样。

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "details": {
        "displayAspectRatio": "97:3",
        "audioConnector": "none"
    }
}

We store the dynamic properties in the details object.

我们在details对象中存储动态属性。

We can map the common properties with the following Java class:

我们可以用下面的Java类来映射公共属性。

class Product {

    String name;
    String category;

    // standard getters and setters
}

On top of that, we need an appropriate representation for the details object. For example, com.fasterxml.jackson.databind.JsonNode can handle dynamic keys.

除此之外,我们还需要为details对象提供一个适当的表示。例如,com.fasterxml.jackson.databind.JsonNode可以处理动态键

To use it, we have to add it as a field to our Product class:

为了使用它,我们必须把它作为一个字段添加到我们的Product类中。

class Product {

    // common fields

    JsonNode details;

    // standard getters and setters
}

Finally, we verify that it works:

最后,我们验证它是否有效。

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector").asText()).isEqualTo("none");

However, there’s a problem with this solution; our class depends on the Jackson library, since we have a JsonNode field.

然而,这个解决方案有一个问题;我们的类依赖于杰克逊库,因为我们有一个JsonNode字段。

3. Using Map

3.使用地图

We can solve this issue by using java.util.Map for the details field. More precisely, we have to use Map<String, Object>.

我们可以通过对java.util.Map字段使用details来解决这个问题。更确切地说,我们必须使用Map<String, Object>

Everything else can stay the same:

其他一切都可以保持不变。

class Product {

    // common fields

    Map<String, Object> details;

    // standard getters and setters
}

And then we can verify it with a test:

然后我们可以用测试来验证它。

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

4. Using @JsonAnySetter

4.使用@JsonAnySetter

The previous solutions are good options when an object contains only dynamic properties. However, sometimes we have fixed and dynamic properties mixed in a single JSON object.

当一个对象只包含动态属性时,前面的解决方案是不错的选择。然而,有时我们会在一个JSON对象中混合有固定和动态属性

For example, we may need to flatten our product representation:

例如,我们可能需要扁平化我们的产品表现。

{
    "name": "Pear yPhone 72",
    "category": "cellphone",
    "displayAspectRatio": "97:3",
    "audioConnector": "none"
}

We can treat this kind of structure as a dynamic object. Unfortunately, this means we can’t define common properties, we have to treat them dynamically, too.

我们可以把这种结构当作一个动态对象。不幸的是,这意味着我们不能定义常见的属性,我们也必须动态地对待它们。

Alternatively, we could use @JsonAnySetter to mark a method for handling additional, unknown properties. Such a method should accept two arguments, the name and value of the property:

另外,我们可以使用@JsonAnySetter来标记一个用于处理额外的、未知属性的方法。这样的方法应该接受两个参数,即属性的名称和值。

class Product {

    // common fields

    Map<String, Object> details = new LinkedHashMap<>();

    @JsonAnySetter
    void setDetail(String key, Object value) {
        details.put(key, value);
    }

    // standard getters and setters
}

Note that we have to instantiate the details object to avoid NullPointerExceptions.

注意,我们必须将details对象实例化,以避免NullPointerExceptions

Since we store the dynamic properties in a Map, we can use it the same way we did before:

由于我们将动态属性存储在一个Map中,我们可以像以前那样使用它。

String json = "<json object>";

Product product = objectMapper.readValue(json, Product.class);

assertThat(product.getName()).isEqualTo("Pear yPhone 72");
assertThat(product.getDetails().get("audioConnector")).isEqualTo("none");

5. Creating a Custom Deserializer

5.创建一个自定义的反序列化器

For most cases, these solutions work just fine; however, sometimes we need much more control. For example, we could store deserialization information about our JSON objects in a database.

在大多数情况下,这些解决方案工作得很好;然而,有时我们需要更多的控制。例如,我们可以在数据库中存储关于我们的JSON对象的反序列化信息。

We can target those situations with a custom deserializer. Since that’s a more complex topic, we cover it in a different article, getting Started with Custom Deserialization in Jackson.

我们可以针对这些情况使用自定义反序列化器。由于这是一个比较复杂的话题,我们将在另一篇文章中介绍,即开始使用 Jackson 中的自定义反序列化

6. Conclusion

6.结语

In this article, we discussed multiple ways of handling dynamic JSON objects with Jackson.

在这篇文章中,我们讨论了用Jackson处理动态JSON对象的多种方法。

As usual, the examples are available over on GitHub.

像往常一样,这些例子可以在GitHub上找到over