Wie ordne ich einen verschachtelten Wert mithilfe von Jackson-Annotationen einer Eigenschaft zu?


88

Angenommen, ich rufe eine API auf, die mit dem folgenden JSON für ein Produkt antwortet:

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

Ich kann die Produkt-ID und den Namen mithilfe von Jackson-Anmerkungen gut zuordnen:

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

Verwenden Sie dann die fromJson-Methode, um das Produkt zu erstellen:

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

Aber jetzt versuche ich herauszufinden, wie man den Markennamen ergreift, der eine verschachtelte Eigenschaft ist. Ich hatte gehofft, dass so etwas funktionieren würde:

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

Aber natürlich nicht. Gibt es eine einfache Möglichkeit, mithilfe von Anmerkungen das zu erreichen, was ich möchte?

Die eigentliche JSON-Antwort, die ich zu analysieren versuche, ist sehr komplex und ich möchte nicht für jeden Unterknoten eine ganz neue Klasse erstellen müssen, obwohl ich nur ein einziges Feld benötige.


2
Am Ende habe ich github.com/json-path/JsonPath verwendet - Spring verwendet es auch unter der Haube. Zum Beispiel in ihrem org.springframework.data.web.
Entmenschlichung

Antworten:


87

Sie können dies so erreichen:

String brandName;

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

23
Wie wäre es mit drei Ebenen tief?
Robin Kanters

4
@Robin Würde das nicht funktionieren? this.abbreviation = ((Map<String, Object>)portalCharacteristics.get("icon")).get("ticker").toString();
Marcello de Sales

Dieselbe Methode kann auch zum Entpacken mehrerer Werte verwendet werden ... unpackValuesFromNestedBrand - danke
msanjay

11

So habe ich dieses Problem behandelt:

Brand Klasse:

package org.answer.entity;

public class Brand {

    private Long id;

    private String name;

    public Brand() {

    }

    //accessors and mutators
}

Product Klasse:

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
}

Hier wird die brandInstanz von Jacksonwährend serializationund ignoriert deserialization, da sie mit kommentiert ist @JsonIgnore.

Jacksonverwendet die mit @JsonGetterfor serializationvon Java-Objekt annotierte Methode in das JSONFormat. Also brandNameist das mit eingestellt brand.getName().

In ähnlicher Weise Jacksonwird die mit @JsonSetterfor deserializationof JSONformatierte Methode in das Java-Objekt verwendet. In diesem Szenario müssen Sie das brandObjekt selbst instanziieren und seine nameEigenschaft festlegen brandName.

Sie können die @TransientPersistenzanmerkung mit verwenden brandName, wenn Sie möchten, dass sie vom Persistenzanbieter ignoriert wird.



1

Am besten verwenden Sie Setter-Methoden:

JSON:

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

Die Setter-Methode für lat oder lng sieht folgendermaßen aus:

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

Wenn Sie beide lesen müssen (wie Sie es normalerweise tun würden), verwenden Sie eine benutzerdefinierte Methode

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

-5

Um es einfach zu machen ... Ich habe den Code geschrieben ... das meiste davon ist selbsterklärend.

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

6
Das funktioniert, aber ich versuche eine Lösung zu finden, bei der ich nicht für jeden Knoten eine neue Klasse erstellen muss, auch wenn ich nur ein Feld benötige.
Kenske

-6

Hallo hier ist der komplette Arbeitscode.

// JUNIT TEST CLASS

öffentliche Klasse sof {

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

//Console.log des Junit-Testfalls

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 

Vollständiger Inhalt: https://gist.github.com/jeorfevre/7c94d4b36a809d4acf2f188f204a8058


1
Die tatsächliche JSON-Antwort, die ich abzubilden versuche, ist sehr komplex. Es hat viele Knoten und Unterknoten, und das Erstellen einer Klasse für jeden wäre sehr schmerzhaft, umso mehr, wenn ich in den meisten Fällen nur ein einziges Feld aus einer Reihe von dreifach verschachtelten Knoten benötige. Gibt es nicht eine Möglichkeit, nur ein einzelnes Feld abzurufen, ohne eine Menge neuer Klassen erstellen zu müssen?
Kenske

Öffne ein neues Sof-Ticket und teile es mit mir. Ich kann dir dabei helfen. Teilen Sie bitte die JSON-Struktur mit, die Sie zuordnen möchten. :)
jeorfevre
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.