Jackson Deserialization of Embedded Java Object - java

I have to deserialize following json using Jackson library into Customer class
{
"code":"C001",
"city": "Pune",
"street": "ABC Road"
}
and Classes as
class Address{
String city;
String street;
}
class Customer{
String code;
Address address;
}
I have found similar question on stack
Java jackson embedded object deserialization
but answer does not apply to my case. Also I only want to use Jackson library.
How can I map this json to Customer object?

You can put a #JsonUnwrapped annotation on the Address field in the customer class. Here is an example:
public class JacksonValue {
final static String JSON = "{\n"
+" \"code\":\"C001\",\n"
+" \"city\": \"Pune\",\n"
+" \"street\": \"ABC Road\"\n"
+"}";
static class Address {
public String city;
public String street;
#Override
public String toString() {
return "Address{" +
"city='" + city + '\'' +
", street='" + street + '\'' +
'}';
}
}
static class Customer {
public String code;
#JsonUnwrapped
public Address address;
#Override
public String toString() {
return "Customer{" +
"code='" + code + '\'' +
", address=" + address +
'}';
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Customer.class));
}
}
Output:
Customer{code='C001', address=Address{city='Pune', street='ABC Road'}}

What you need is a custom deserializer. Jackson How-To: Custom Deserializers
For your use case it could be something like this:
class CustomerDeserializer extends JsonDeserializer<Customer>
{
public Customer deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
JsonNode node = p.getCodec().readTree(p);
String code = node.get("code").asText();
String city = node.get("city").asText();
String street = node.get("street").asText();
Address adr = new Address(city, street);
return new Customer(code, adr);
}
}

Your JSON object for a customer should look like this:
{
"code":"C001",
"address":{
"city": "Pune",
"street": "ABC Road"
}
}

Without some additional transformation this json structure can't be mapped to two classes. Either write a class CustomerAddress that will be having all three fields from json and then create Address getAddress() and Customer getCustomer() in it or transform the json to nest the address information inside the customer field as suggested by #eztam.
public CustomerAddress {
private String code;
private String city;
private String street;
public Address getAddress() {
return new Address(city, street);
}
public Address getCustomer() {
return new Customer(code, this.getAddress());
}
}

Try this !!!
{
"code":"customer1",
"address":{
"type":"nested",
"properties":{
"city":"Hyderabad",
"street":"1000ftRoad"
}
}
}

Related

How do I receive a dto, with a map as one of it's atributes, as a parameter of my controller? (REST API)

When I try to send it, it show me the "error": "Bad Request",
"trace": "...HttpMessageNotReadableException: JSON parse error: Cannot deserialize Map key of type java.time.LocalDate from String "quoteDate": Failed to deserialize java.time.LocalDate"
The JSON I am sending via postman:
{
"stockId":"test3",
"quotes":[
{
"quoteDate":"2003-05-14",
"quoteValue":"35.9"
},
{
"quoteDate":"2016-03-28",
"quoteValue":"55.0"
}
]
}
The controller:
#PostMapping("/stock/save")
public void saveQuotes(#RequestBody StockDTO stockDTO) {
System.out.println(stockDTO.toString());
}
The DTO
public class StockDTO {
String id;
String stockId;
Map<LocalDate, Double> quotes;
}
For quotes, the data type that should come as per the json string is a List. but was given a Map in the DTO.
The DTO should be ::
import com.google.gson.Gson;
import java.util.List;
public class GsonMappingExample {
public static void main(String[] args) {
String jsonString = "{\"stockId\":\"test3\",\"quotes\":[{\"quoteDate\":\"2003-05-14\",\"quoteValue\":\"35.9\"},{\"quoteDate\":\"2016-03-28\",\"quoteValue\":\"55.0\"}]}";
Gson gson = new Gson();
StockDTO stockDTO = gson.fromJson(jsonString, StockDTO.class);
System.out.println(stockDTO);
}
}
class StockDTO {
private final String stockId;
private final List<Quote> quotes;
public StockDTO(String stockId, List<Quote> quotes) {
this.stockId = stockId;
this.quotes = quotes;
}
#Override
public String toString() {
return "StockDTO{" +
"stockId='" + stockId + '\'' +
", quoteList=" + quotes +
'}';
}
}
class Quote {
private final String quoteDate;
private final Double quoteValue;
public Quote(String quoteDate, Double quoteValue) {
this.quoteDate = quoteDate;
this.quoteValue = quoteValue;
}
#Override
public String toString() {
return "Quote{" +
"quoteDate=" + quoteDate +
", quoteValue=" + quoteValue +
'}';
}
}
PS: Here I'm using Gson library to parse the json string, spring-boot automatically does that (using Jackson library I think!)

#JsonUnwrapped setting null as value for child pojo class

Request:
{
"name":"iswarya",
"dept":{
"deptName":"eee",
"location":"firstfloor"
},
"additionalDetails":{
"projectName":"finalyearproject"
}
}
Response:
{
"name": "iswarya",
"deptName": null,
"location": null,
"projectName": null
}
Controller class:
#PostMapping(value="/objectMApper")
public String createEmployee(#RequestBody AnnotationTestBean demoEntity) throws JsonProcessingException {
ObjectMapper obj=new ObjectMapper();
return obj.writeValueAsString(demoEntity);
}
In the given example the request for JSON is not wrapped, so its dept and additionalDetails should not be annotated with #JsonUnwrapped.
Instead, a response should be created extending the request class, having a copy constructor, and overriding appropriate getters annotated as #JsonUnwrapped.
The example below uses Lombok annotations to generate getters/setters/constructors.
#Data
#AllArgsConstructor
#NoArgsConstructor
static class Request {
private String name;
private Department dept;
private Details additionalDetails;
}
#Data
static class Department {
private String deptName;
private String location;
}
#Data
static class Details {
private String projectName;
}
static class Response extends Request {
public Response(Request request) {
super(request.name, request.dept, request.additionalDetails);
}
#Override #JsonUnwrapped
public Department getDept() { return super.getDept(); }
#Override #JsonUnwrapped
public Details getAdditionalDetails() { return super.getAdditionalDetails(); }
}
Test
ObjectMapper om = new ObjectMapper();
String json = "{\r\n" +
" \"name\":\"iswarya\",\r\n" +
" \"dept\":{\r\n" +
" \"deptName\":\"eee\",\r\n" +
" \"location\":\"firstfloor\"\r\n" +
" },\r\n" +
" \"additionalDetails\":{\r\n" +
" \"projectName\":\"finalyearproject\"\r\n" +
" }\r\n" +
"}";
Request request = om.readValue(json, Request.class);
Response response = new Response(request);
String str = om.writerWithDefaultPrettyPrinter().writeValueAsString(response);
System.out.println(str);
Output
{
"name" : "iswarya",
"deptName" : "eee",
"location" : "firstfloor",
"projectName" : "finalyearproject"
}

How to convert JSON field name to Java bean class property with Jackson

I have access to a RESTful API which returns JSON Strings, such as the following:
{
"Container1": {
"active": true
},
"Container2": {
"active": false
},
}
The problem is that the RESTful API is a bit maldesigned. The field name contains the data already. With the Jackson library it is not possible to deserialize the field name to a property name of the corresponding Java bean class. I assume, this isn't intended by the JSON specification neither. The above JSON string needs to be deserialized to an instance of the following class:
public class Container {
private Boolean active;
private String name;
}
I end up with UnrecognizedPropertyException for the field Container1.
I thought to configure to ignore unknown properties and to provide a JsonDeserializer for that property like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Container {
private Boolean active;
private String name;
#JsonDeserialize(using = FieldNameToPropertyDeserializer.class)
public void setName(String name) {
this.name = name;
}
}
and the FieldNameToPropertyDeserializer:
public class FieldNameToPropertyDeserializer extends StdDeserializer<String> {
public FieldNameToPropertyDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
return parser.getCurrentName();
}
}
The invocation of the deserialization is achieved as follows:
String jsonString = response.readEntity(String.class);
ObjectMapper objectMapper = new ObjectMapper();
ObjectReader readerFor = objectMapper.readerFor(Container.class);
MappingIterator<Container> mappingIterator = readerFor.readValues(jsonString);
while (mappingIterator.hasNext()) {
Container container = (Container) mappingIterator.next();
containers.add(container);
}
But I only receive empty objects (properties set to null) because the parsing of the properties is skipped since I set #JsonIgnoreProperties(ignoreUnknown = true).
Is this possible at all? Or should I implement something like a post-processing afterwards?
How about this. Create a class ContainerActive like this
public class ContainerActive {
private boolean active;
// constructors, setters, getters
}
And you could just do
Map<String, ContainerActive> map = mapper.readValue(jsonString, new TypeReference<Map<String, ContainerActive>>() {});
With this you will have "Container1", "Container2" as the keys and ContainerActive Object as values which has active field.
Just a quick solution, if the object is such that, that all of it object is a container object you can receive the JSON inside and JSONObject you may use below code
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestSO {
public static void main(String[] args) throws JsonParseException, JsonMappingException, JSONException, IOException {
String jsonString = "{\r\n" +
" \"Container1\": {\r\n" +
" \"active\": true\r\n" +
" },\r\n" +
" \"Container2\": {\r\n" +
" \"active\": false\r\n" +
" },\r\n" +
"}";
JSONObject jsonObject = new JSONObject(jsonString);
ObjectMapper mapper = new ObjectMapper();
for (String key : jsonObject.keySet()) {
Container container = mapper.readValue(jsonObject.get(key).toString(), Container.class);
System.out.println(container);
}
}
static class Container{
private String name;
private Boolean active;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
#Override
public String toString() {
return "Container [name=" + name + ", active=" + active + "]";
}
}
}

Jackson: Ignore deserializing one property and leave it as list of strings [duplicate]

I've got a following JSON from API:
"hotel_data": {
"name": "Hotel Name",
"checkin_checkout_times": {
"checkin_from": "14:00",
"checkin_to": "00:00",
"checkout_from": "",
"checkout_to": "12:00"
},
"default_language": "en",
"country": "us",
"currency": "USD",
"city": "Miami"
}
I'm using Jackson library to deserialize this JSON to Java object. I don't want to create a special class for checkin_checkout_times object. I just want to get it as a plain text. Like this "checkin_from": "14:00", "checkin_to": "00:00", "checkout_from": "", "checkout_to": "12:00".
In my POJO for hotel_data this checkin_checkout_times should be as a string i.e.:
#JsonProperty("checkin_checkout_times")
private String checkinCheckoutTimes
Is this possible to get this part of the JSON as a plain text?
EDIT: Error that I'm getting com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (String)...
Make use of JsonNode.
Just make the following setter for the field checkinCheckoutTimes in your POJO for hotel_data and it should work for you.
public void setCheckinCheckoutTimes(JsonNode node) {
this.checkinCheckoutTimes = node.toString();
}
Example
String str = "{ \"id\": 1, \"data\": { \"a\": 1 } }";
try {
System.out.println(new ObjectMapper().readValue(str,Employee.class));
} catch (IOException e) {
e.printStackTrace();
}
Where Employee is as follows:
class Employee
{
private int id;
private String data;
public Employee() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getData() {
return data;
}
public void setData(JsonNode node) {
this.data = node.toString();
}
#Override
public String toString() {
return "Employee{" +
"id=" + id +
", data='" + data + '\'' +
'}';
}
}
gives the following output:
Employee{id=1, data='{"a":1}'}
You can also write a custom deserializer as described in the article:
public class RawJsonDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
return mapper.writeValueAsString(node);
}
}
and then use it with annotation in your class:
public class HotelData {
#JsonProperty("checkin_checkout_times")
#JsonDeserialize(using = RawJsonDeserializer.class)
private String checkinCheckoutTimes;
// other attributes
// getters and setters
}

Jackson calls setter but field of nested object remains null

There is a class with another static one inside. Jackson calls setters while deserializing json. But some setters doesn't work as expected.
Classes def:
#Entity
#JsonIgnoreProperties(ignoreUnknown=true)
#JsonPropertyOrder({ "name", "imageLink", "weeklyGross", "weeklyChange" })
public class WeeklyBoxOffice {
#Embeddable
public static class WeeklyBoxOfficeID implements Serializable {
public String country;
public String name;
public WeeklyBoxOfficeID() {
}
}
public WeeklyBoxOffice() {
this.id = new WeeklyBoxOfficeID();
}
#EmbeddedId
#JsonUnwrapped
WeeklyBoxOfficeID id;
private long weeklyGross;
private double weeklyChange;
// "weeklyGross":"$294,710"
public void setWeeklyGross(String weeklyGross) throws ParseException {
System.out.println("setWeeklyGross called, weeklyGross = " + weeklyGross);
this.weeklyGross = (long) NumberFormat.getCurrencyInstance(Locale.US)
.parse(weeklyGross);
}
// "weeklyChange": "-57.1%"
public void setWeeklyChange(String weeklyChange) {
this.weeklyChange = weeklyChange.equals("-") ? 0 : Double.parseDouble(weeklyChange.replace('%', ' '));
}
public void setCountry(String country) {
this.id.country = country;
}
// #JsonProperty("name")
public void setName(String name) {
System.out.println("Set name caled, name = " + name);
this.id.name = name;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
public static void main(String[] args) throws IOException {
String json = "{\n" +
" \"TrendingMovies\": [\n" +
" {\n" +
" \"name\": \"The Dark Tower\",\n" +
" \"weeklyGross\": \"$461,377\",\n" +
" \"weeklyChange\": \"5.0\"\n" +
" }\n" +
" ]\n" +
"}\n";
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
List<WeeklyBoxOffice> list = mapper.readValue(mapper.readTree(json).get("TrendingMovies").toString(), new TypeReference<List<WeeklyBoxOffice>>() {});
list.forEach(System.out::println);
}
Output:
Set name caled, name = The Dark Tower
setWeeklyGross called, weeklyGross = $461,377
WeeklyBoxOffice{ id=WeeklyBoxOfficeID{country='null', name='null'},
weeklyGross=461377, weeklyChange=5.0 }
Why call of setName has no effect while setWeeklyGross and setWeeklyChange does and the fields of nested object remains null?
The issue is the #JsonUnwrapped and it will work fine if your remove it. If you follow your code with a debugger, you'll see that Jackson (in this order):
Calls WeeklyBoxOffice constructor that sets WeeklyBoxOfficeID id to an empty object (all null).
Calls setters for each JSON field by matching names. setName is called and name is set inside WeeklyBoxOfficeID id.
Handles any fields annotated as unwrapped. It can't find a name field in JSON as it was consumed before by getName. Therefore it now sets it to null.
The code will also work if you keep #JsonUnwrapped but remove the setCountry and setName setters which is presumably simpler. Basically use one or the other.

Categories

Resources