Bagaimana cara memetakan nilai bersarang ke properti menggunakan anotasi Jackson?

90

Katakanlah saya melakukan panggilan ke API yang merespons dengan JSON berikut untuk sebuah produk:

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

Saya dapat memetakan id dan nama produk dengan baik menggunakan anotasi Jackson:

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

Dan kemudian menggunakan metode fromJson untuk membuat produk:

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

Tapi sekarang saya mencoba mencari cara untuk mendapatkan nama merek, yang merupakan properti bertingkat. Saya berharap sesuatu seperti ini akan berhasil:

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

Tapi tentu saja tidak. Adakah cara mudah untuk mencapai apa yang saya inginkan dengan menggunakan anotasi?

Respons JSON sebenarnya yang saya coba parse sangat kompleks, dan saya tidak ingin membuat seluruh kelas baru untuk setiap sub-node, meskipun saya hanya memerlukan satu bidang.

kenske
sumber
2
Saya akhirnya menggunakan github.com/json-path/JsonPath - Spring juga menggunakannya di bawah tenda. Misalnya, di org.springframework.data.web.
dehumanizer

Jawaban:

89

Anda dapat mencapai ini seperti itu:

String brandName;

@JsonProperty("brand")
private void unpackNameFromNestedObject(Map<String, String> brand) {
    brandName = brand.get("name");
}
Jacek Grobelny
sumber
23
Bagaimana dengan tiga level?
Robin Kanters
5
@Robin Bukankah itu berhasil? this.abbreviation = ((Map<String, Object>)portalCharacteristics.get("icon")).get("ticker").toString();
Marcello de Sales
metode yang sama juga dapat digunakan untuk membongkar beberapa nilai ... unpackValuesFromNestedBrand - terima kasih
msanjay
13

Beginilah cara saya menangani masalah ini:

Brand kelas:

package org.answer.entity;

public class Brand {

    private Long id;

    private String name;

    public Brand() {

    }

    //accessors and mutators
}

Product kelas:

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
}

Di sini, brandinstance akan diabaikan oleh Jacksonselama serializationdan deserialization, karena dianotasi dengan @JsonIgnore.

Jacksonakan menggunakan metode yang dianotasi @JsonGetteruntuk serializationobjek java ke dalam JSONformat. Jadi, brandNamedisetel dengan brand.getName().

Demikian pula, Jacksonakan menggunakan metode yang dianotasi dengan @JsonSetterfor deserializationdari JSONformat ke objek java. Dalam skenario ini, Anda harus membuat brandsendiri objek tersebut dan menyetel namepropertinya dari brandName.

Anda dapat menggunakan @Transientanotasi persistensi dengan brandName, jika ingin diabaikan oleh penyedia persistensi.

Cerah KC
sumber
1

Yang terbaik adalah menggunakan metode penyetel:

JSON:

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

metode penyetel untuk lat atau lng akan terlihat seperti:

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

jika Anda perlu membaca keduanya (seperti yang biasa Anda lakukan), gunakan metode khusus

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

Untuk membuatnya sederhana .. Saya telah menulis kodenya ... sebagian besar sudah cukup jelas.

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]]
Srikanth Lankapalli
sumber
6
Ini berfungsi, tetapi saya mencoba menemukan solusi di mana saya tidak perlu membuat kelas baru untuk setiap node meskipun saya hanya membutuhkan satu bidang.
kenske
-7

Hai, ini kode kerja lengkap.

// KELAS TES JUNIT

kelas publik 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 dari testcase junit

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 

Inti lengkap: https://gist.github.com/jeorfevre/7c94d4b36a809d4acf2f188f204a8058

jeorfevre
sumber
1
Respons JSON sebenarnya yang saya coba petakan sangat kompleks. Ini memiliki banyak node dan subnode, dan membuat kelas untuk masing-masing akan sangat menyakitkan, terlebih lagi ketika dalam banyak kasus saya hanya membutuhkan satu bidang dari satu set node bertingkat tiga. Apakah tidak ada cara untuk mendapatkan satu bidang tanpa harus membuat banyak kelas baru?
kenske
buka tiket sof baru dan bagikan dengan saya, saya dapat membantu Anda dalam hal ini. Bagikan struktur json yang ingin Anda petakan. :)
jeorfevre