Comment mapper une valeur imbriquée à une propriété à l'aide d'annotations Jackson?

90

Disons que je passe un appel à une API qui répond avec le JSON suivant pour un produit:

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

Je suis capable de mapper l'identifiant et le nom du produit très bien en utilisant les annotations 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;
    }
}

Et puis en utilisant la méthode fromJson pour créer le produit:

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

Mais maintenant, j'essaie de comprendre comment saisir le nom de la marque, qui est une propriété imbriquée. J'espérais que quelque chose comme ça fonctionnerait:

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

Mais bien sûr, non. Existe-t-il un moyen simple d'accomplir ce que je veux en utilisant des annotations?

La réponse JSON que j'essaie d'analyser est très complexe et je ne veux pas avoir à créer une nouvelle classe entière pour chaque sous-nœud, même si je n'ai besoin que d'un seul champ.

Kenske
la source
2
J'ai fini par utiliser github.com/json-path/JsonPath - Spring l'utilise également sous le capot. Par exemple, dans leur org.springframework.data.web.
déshumaniseur

Réponses:

89

Vous pouvez réaliser ceci comme ça:

String brandName;

@JsonProperty("brand")
private void unpackNameFromNestedObject(Map<String, String> brand) {
    brandName = brand.get("name");
}
Jacek Grobelny
la source
23
Que diriez-vous de trois niveaux de profondeur?
Robin Kanters
5
@Robin Ça ne marcherait pas? this.abbreviation = ((Map<String, Object>)portalCharacteristics.get("icon")).get("ticker").toString();
Marcello de Sales
la même méthode peut également être utilisée pour décompresser plusieurs valeurs ... unpackValuesFromNestedBrand - merci
msanjay
13

Voici comment j'ai géré ce problème:

Brand classe:

package org.answer.entity;

public class Brand {

    private Long id;

    private String name;

    public Brand() {

    }

    //accessors and mutators
}

Product classe:

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
}

Ici, l' brandinstance sera ignorée par Jacksonpendant serializationet deserialization, puisqu'elle est annotée avec @JsonIgnore.

Jacksonutilisera la méthode annotée avec @JsonGetterfor serializationde l'objet java dans le JSONformat. Donc, le brandNameest défini avec brand.getName().

De même, Jacksonutilisera la méthode annotée avec @JsonSetterfor deserializationof JSONformat en objet java. Dans ce scénario, vous devrez instancier brandvous-même l' objet et définir sa namepropriété à partir de brandName.

Vous pouvez utiliser @Transientl'annotation de persistance avec brandName, si vous souhaitez qu'elle soit ignorée par le fournisseur de persistance.

Sunny KC
la source
1

Le mieux est d'utiliser des méthodes de setter:

JSON:

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

La méthode setter pour lat ou lng ressemblera à:

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

si vous avez besoin de lire les deux (comme vous le feriez normalement), utilisez une méthode personnalisée

@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
la source
-6

Pour faire simple ... j'ai écrit le code ... la plupart est explicite.

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
la source
6
Cela fonctionne, mais j'essaie de trouver une solution où je n'ai pas à créer une nouvelle classe pour chaque nœud même si je n'ai besoin que d'un seul champ.
kenske
-7

Bonjour, voici le code de travail complet.

// CLASSE DE TEST JUNIT

classe publique 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 de junit testcase

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 

Essentiel complet: https://gist.github.com/jeorfevre/7c94d4b36a809d4acf2f188f204a8058

jeorfevre
la source
1
La réponse JSON que j'essaie de mapper est très complexe. Il a beaucoup de nœuds et de sous-nœuds, et créer une classe pour chacun serait très pénible, d'autant plus que dans la plupart des cas, je n'ai besoin que d'un seul champ d'un ensemble de trois nœuds imbriqués. N'y a-t-il pas un moyen d'obtenir simplement le champ unique sans avoir à créer une tonne de nouvelles classes?
kenske
ouvrez un nouveau ticket sof et partagez-le avec moi, je peux vous aider à ce sujet. Partagez la structure json que vous souhaitez cartographier s'il vous plaît. :)
jeorfevre