How to map a nested value to a property using Jackson annotations?

Let's say I'm making a call to an API that responds with the following JSON for a product:

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

I'm able to map the product id and name just fine using Jackson annotations:

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

And then using the fromJson method to create the product:

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

But now I'm trying to figure out how to grab the brand name, which is a nested property. I was hoping that something like this would work:

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

But of course it didn't. Is there an easy way to accomplish what I want using annotations?

The actual JSON response I'm trying to parse is very complex, and I don't want to have to create an entire new class for every sub-node, even though I only need a single field.


You can achieve this like that:

String brandName;

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

This is how I handled this problem:

Brand class:

package org.answer.entity;

public class Brand {

    private Long id;

    private String name;

    public Brand() {

    }

    //accessors and mutators
}

Product class:

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
}

Here, the brand instance will be ignored by Jackson during serialization and deserialization, since it is annotated with @JsonIgnore.

Jackson will use the method annotated with @JsonGetter for serialization of java object into JSON format. So, the brandName is set with brand.getName().

Similarly, Jackson will use the method annotated with @JsonSetter for deserialization of JSON format into java object. In this scenario, you will have to instantiate the brand object yourself and set its name property from brandName.

You can use @Transient persistence annotation with brandName, if you want it to be ignored by persistence provider.


You can use JsonPath-expressions to map nested properties. I don't think there's any official support (see this issue), but there's an unofficial implementation here: https://github.com/elasticpath/json-unmarshaller