【问题标题】:How to map a nested value to a property using Jackson annotations?如何使用 Jackson 注释将嵌套值映射到属性?
【发布时间】:2016-08-28 21:32:32
【问题描述】:

假设我正在调用一个 API,该 API 响应产品的以下 JSON:

{
  "id": 123,
  "name": "The Best Product",
  "brand": {
     "id": 234,
     "name": "ACME Products"
  }
}

我可以使用 Jackson 注释很好地映射产品 ID 和名称:

public class ProductTest {
    private int productId;
    private String productName, brandName;

    @JsonProperty("id")
    public int getProductId() {
        return productId;
    }

    public void setProductId(int productId) {
        this.productId = productId;
    }

    @JsonProperty("name")
    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }
}

然后使用 fromJson 方法创建产品:

  JsonNode apiResponse = api.getResponse();
  Product product = Json.fromJson(apiResponse, Product.class);

但现在我想弄清楚如何获取品牌名称,这是一个嵌套属性。我希望这样的事情会起作用:

    @JsonProperty("brand.name")
    public String getBrandName() {
        return brandName;
    }

但当然没有。有没有一种简单的方法可以使用注释来完成我想要的事情?

我尝试解析的实际 JSON 响应非常复杂,我不想为每个子节点创建一个全新的类,即使我只需要一个字段。

【问题讨论】:

标签: java json jackson


【解决方案1】:

您好,这里是完整的工作代码。

//JUNIT 测试类

公共类软{

@Test
public void test() {

    Brand b = new Brand();
    b.id=1;
    b.name="RIZZE";

    Product p = new Product();
    p.brand=b;
    p.id=12;
    p.name="bigdata";


    //mapper
    ObjectMapper o = new ObjectMapper();
    o.registerSubtypes(Brand.class);
    o.registerSubtypes(Product.class);
    o.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

    String json=null;
    try {
        json = o.writeValueAsString(p);
        assertTrue(json!=null);
        logger.info(json);

        Product p2;
        try {
            p2 = o.readValue(json, Product.class);
            assertTrue(p2!=null);
            assertTrue(p2.id== p.id);
            assertTrue(p2.name.compareTo(p.name)==0);
            assertTrue(p2.brand.id==p.brand.id);
            logger.info("SUCCESS");
        } catch (IOException e) {

            e.printStackTrace();
            fail(e.toString());
        }




    } catch (JsonProcessingException e) {

        e.printStackTrace();
        fail(e.toString());
    }


}
}




**// Product.class**
    public class Product {
    protected int id;
    protected String name;

    @JsonProperty("brand") //not necessary ... but written
    protected Brand brand;

}

    **//Brand class**
    public class Brand {
    protected int id;
    protected String name;     
}

//junit 测试用例的Console.log

2016-05-03 15:21:42 396 INFO  {"id":12,"name":"bigdata","brand":{"id":1,"name":"RIZZE"}} / MReloadDB:40 
2016-05-03 15:21:42 397 INFO  SUCCESS / MReloadDB:49 

完整要点:https://gist.github.com/jeorfevre/7c94d4b36a809d4acf2f188f204a8058

【讨论】:

  • 我试图映射的实际 JSON 响应非常复杂。它有很多节点和子节点,为每个节点创建一个类会非常痛苦,在大多数情况下我只需要一组三重嵌套节点中的一个字段时更是如此。有没有办法只获取单个字段而不必创建大量新类?
  • 打开一张新的软票并与我分享,我可以帮助你。请分享您要映射的 json 结构。 :)
【解决方案2】:

为了简单起见......我已经编写了代码......其中大部分都是不言自明的。

Main Method

package com.test;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class LOGIC {

    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        String DATA = "{\r\n" + 
                "  \"id\": 123,\r\n" + 
                "  \"name\": \"The Best Product\",\r\n" + 
                "  \"brand\": {\r\n" + 
                "     \"id\": 234,\r\n" + 
                "     \"name\": \"ACME Products\"\r\n" + 
                "  }\r\n" + 
                "}";

        ProductTest productTest = objectMapper.readValue(DATA, ProductTest.class);
        System.out.println(productTest.toString());

    }

}

Class ProductTest

package com.test;

import com.fasterxml.jackson.annotation.JsonProperty;

public class ProductTest {

    private int productId;
    private String productName;
    private BrandName brandName;

    @JsonProperty("id")
    public int getProductId() {
        return productId;
    }
    public void setProductId(int productId) {
        this.productId = productId;
    }

    @JsonProperty("name")
    public String getProductName() {
        return productName;
    }
    public void setProductName(String productName) {
        this.productName = productName;
    }

    @JsonProperty("brand")
    public BrandName getBrandName() {
        return brandName;
    }
    public void setBrandName(BrandName brandName) {
        this.brandName = brandName;
    }

    @Override
    public String toString() {
        return "ProductTest [productId=" + productId + ", productName=" + productName + ", brandName=" + brandName
                + "]";
    }



}

Class BrandName

package com.test;

public class BrandName {

    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "BrandName [id=" + id + ", name=" + name + "]";
    }




}

OUTPUT

ProductTest [productId=123, productName=The Best Product, brandName=BrandName [id=234, name=ACME Products]]

【讨论】:

  • 这行得通,但我试图找到一种解决方案,即使我只需要一个字段,我也不必为每个节点创建一个新类。
【解决方案3】:

你可以这样实现:

String brandName;

@JsonProperty("brand")
private void unpackNameFromNestedObject(Map<String, String> brand) {
    brandName = brand.get("name");
}

【讨论】:

  • 三层深度怎么样?
  • @Robin 那不行吗? this.abbreviation = ((Map&lt;String, Object&gt;)portalCharacteristics.get("icon")).get("ticker").toString();
  • 同样的方法也可以用于解包多个值... unpackValuesFromNestedBrand - 谢谢
  • 三个级别直接使用JsonNode。地图太繁琐了:void unpackName(JsonNode company) { name = company.get("division").get("brand").get("name).asText(); }
【解决方案4】:

您可以使用 JsonPath 表达式来映射嵌套属性。我认为没有任何官方支持(请参阅this 问题),但这里有一个非官方的实现:https://github.com/elasticpath/json-unmarshaller

【讨论】:

    【解决方案5】:

    我是这样处理这个问题的:

    Brand类:

    package org.answer.entity;
    
    public class Brand {
    
        private Long id;
    
        private String name;
    
        public Brand() {
    
        }
    
        //accessors and mutators
    }
    

    Product类:

    package org.answer.entity;
    
    import com.fasterxml.jackson.annotation.JsonGetter;
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.annotation.JsonSetter;
    
    public class Product {
    
        private Long id;
    
        private String name;
    
        @JsonIgnore
        private Brand brand;
    
        private String brandName;
    
        public Product(){}
    
        @JsonGetter("brandName")
        protected String getBrandName() {
            if (brand != null)
                brandName = brand.getName();
            return brandName;
        }
    
        @JsonSetter("brandName")
        protected void setBrandName(String brandName) {
            if (brandName != null) {
                brand = new Brand();
                brand.setName(brandName);
            }
            this.brandName = brandName;
        }
    
    //other accessors and mutators
    }
    

    这里,Jacksonserializationdeserialization 期间将忽略brand 实例,因为它带有@JsonIgnore 注释。

    Jackson将使用@JsonGetter注解的方法将java对象的serialization转换成JSON格式。因此,brandName 设置为 brand.getName()

    同样,Jackson 将使用带有@JsonSetter 注释的方法将deserializationJSON 格式转换为java 对象。在这种情况下,您必须自己实例化 brand 对象并从 brandName 设置其 name 属性。

    如果您希望持久性提供者忽略 @Transient 持久性注解和 brandName,可以使用它。

    【讨论】:

      【解决方案6】:

      最好是使用setter方法:

      JSON:

      ...
       "coordinates": {
                     "lat": 34.018721,
                     "lng": -118.489090
                   }
      ...
      

      lat 或 lng 的 setter 方法如下所示:

       @JsonProperty("coordinates")
          public void setLng(Map<String, String> coordinates) {
              this.lng = (Float.parseFloat(coordinates.get("lng")));
       }
      

      如果您需要同时阅读两者(通常会这样做),请使用自定义方法

      @JsonProperty("coordinates")
      public void setLatLng(Map<String, String> coordinates){
          this.lat = (Float.parseFloat(coordinates.get("lat")));
          this.lng = (Float.parseFloat(coordinates.get("lng")));
      }
      

      【讨论】:

        【解决方案7】:

        这是我解决任何与 JSON 嵌套映射相关的问题的解决方案。

        首先将您的响应存储为地图

                ResponseEntity<Map<String, Object>> response =
                restTemplate.exchange(
                    requestUri,
                    HttpMethod.GET,
                    buildAuthHeader(),
                    new ParameterizedTypeReference<>() {
                    });
        

        现在您可以读取JSON 树中的任何嵌套值。例如:

        response.getBody().get("content").get(0).get("terminal")
        

        在这种情况下,我的 JSON 是:

        {
        "page": 0,
        "total_pages": 1,
        "total_elements": 2,
        "content": [
            {
                "merchant": "000405",
                "terminal": "38010101",
                "status": "Para ser instalada", 
        ....
        

        【讨论】:

          猜你喜欢
          • 2018-09-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-06
          • 2018-03-17
          相关资源
          最近更新 更多