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

JavaJsonJackson

Java Problem Overview


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.

Java Solutions


Solution 1 - Java

You can achieve this like that:

String brandName;

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

Solution 2 - Java

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.

Solution 3 - Java

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

Solution 4 - Java

The best is to use setter methods:

JSON:

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

setter method for lat or lng will look like:

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

if you need to read both (as you normally would do) then use a custom method

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

Solution 5 - Java

This is my solution to solve any issue related to JSON nested mapping.

First store your response as a Map:

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

Now you can read any nested value in your JSON tree. For example:

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

In this case, my JSON is:

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

Solution 6 - Java

To make it simple ..I have written the code ...most of it is self explanatory.

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

Solution 7 - Java

Hi here is the complete working code.

//JUNIT TEST CLASS

public class 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 of 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 

Complete gist : https://gist.github.com/jeorfevre/7c94d4b36a809d4acf2f188f204a8058

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkenskeView Question on Stackoverflow
Solution 1 - JavaJacek GrobelnyView Answer on Stackoverflow
Solution 2 - JavaSunny KCView Answer on Stackoverflow
Solution 3 - JavagogstadView Answer on Stackoverflow
Solution 4 - JavaToseef ZafarView Answer on Stackoverflow
Solution 5 - JavajulianmView Answer on Stackoverflow
Solution 6 - JavaSrikanth LankapalliView Answer on Stackoverflow
Solution 7 - JavajeorfevreView Answer on Stackoverflow