How to implement dynamic partial JSON serialization with Jackson? - java

I have a POJO with custom setter methods for all properties that track whether the property was explicitly set. The setter stores to fieldNameSet boolean fields and exposes isFieldNameSet getters for those flags. I want Jackson to dynamically serialize the class with only those fields that have isFieldNameSet as true.
Background:
I started writing a custom JsonFilter implementation but it doesn't give any context as to the current object instance being serialized so obviously I can't read the current values of the isFieldNameSet properties.

Quickly hacked from a Jackson example
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JacksonExample {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
User user = createDummyUser();
try {
//Its age here , this is conditional based on your fieldset
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAllExcept("age");
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
// Convert object to JSON string
String jsonInString = jsonInString = mapper.writer(filters).writeValueAsString(user);
System.out.println(jsonInString);
// Convert object to JSON string and pretty print
//System.out.println(jsonInString);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static User createDummyUser() {
User user = new User();
user.setName("mkyong");
user.setAge(33);
List<String> msg = new ArrayList<>();
msg.add("hello jackson 1");
msg.add("hello jackson 2");
msg.add("hello jackson 3");
user.setMessages(msg);
return user;
}
}
package org.soproject;
import java.util.List;
import org.codehaus.jackson.map.annotate.JsonFilter;
#JsonFilter("myFilter")
public class User {
private String name;
private int age;
private List<String> messages;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
// getters and setters
}
Ignores age as you see :
{"name":"mkyong","messages":["hello jackson 1","hello jackson 2","hello jackson 3"]}
Note jackson source is from : https://www.mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/

Related

Mapping JSON response to different types

I'm using Spring 2.6 and we make a GET request via
restTemplate.exchange(url, HttpMethod.GET, httpEntity, ResponseType.class).getBody();
The JSON response can be of two kinds:
1st:
public class ResponseType {
private String data;
}
2nd:
public class ResponseType {
private Subclass data;
}
public class Subclass {
private String classId;
private String detail;
}
In the first version I only get a reference link to the subclass resource.
If the URL contains a 'resolve' flag, than the reference link get expanded already in the first request.
The classId then also specifies what kind of class it is ( 'a.b.c' or 'x.y.z' )
No problem for JSON, but how can I get a mapping in Java?
When having more fields being dynamic (link or instance based on classId) a manual way would be difficult to implement if the combination could be 2 links and 3 objects.
It also could be that a object has the same feature - a filed with a link or a instance of a class specified by classId.
The JSON response would be this:
{
"data": "abskasdkjhkjsahfkajdf-linkToResource"
}
or this:
{
"data": {
"classId": "a.b.subclass",
"detail": "some data"
}
}
or this:
{
"data": {
"classId": "a.b.subclass",
"detail": "some data"
"data2": "some-link-id",
"data3": {
"detailB": "foo",
"detailC": "some-link-id"
}
}
}
Here I do have a possible solution for my problem. The logic to print the address only or the POJO relies soley in the CustomItemSerializer. So it is possible to use this without using duplicate code in controllers.
package com.allianz.clana.datamodel.http.epc.test;
import java.io.IOException;
import java.text.ParseException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import lombok.Data;
import lombok.NoArgsConstructor;
public class JacksonTester2 {
public static void main(String args[]) throws ParseException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Item item2 = new Item("link");
Stuff stuff = new Stuff();
stuff.setItem(item2);
stuff.setFoo("foo");
String jsonStringStuff = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(stuff);
System.out.println(jsonStringStuff);
Item item3 = new Item("{ \"name\":\"ID3\", \"creationDate\":\"1984-12-30\", \"rollNo\": 1 }");
stuff.setItem(item3);
stuff.setFoo("bar");
jsonStringStuff = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(stuff);
System.out.println(jsonStringStuff);
}
}
class CustomItemSerializer extends StdSerializer<Item> {
private static final long serialVersionUID = 1L;
public CustomItemSerializer() {
this(null);
}
public CustomItemSerializer(Class<Item> t) {
super(t);
}
#Override
public void serialize(Item item, JsonGenerator generator, SerializerProvider arg2) throws IOException {
if (item != null) {
if (item.getItem() != null) {
System.out.println("ItemA POJO data");
generator.writePOJO(item.getItem());
} else {
System.out.println("raw data with link");
generator.writeString(item.getRawdata());
}
}
}
}
#Data
class Stuff {
Item item;
String foo;
}
#JsonSerialize(using = CustomItemSerializer.class)
#Data
#NoArgsConstructor
class Item {
private String rawdata;
#JsonIgnore
private ItemA item;
public Item(String rawdata) {
this.rawdata = rawdata;
if (rawdata.contains("{")) {
try {
this.item = new ObjectMapper().readerFor(ItemA.class).readValue(rawdata);
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
}
#Data
#NoArgsConstructor
class ItemA{
private String name;
private int rollNo;
private String creationDate;
public ItemA(String name, int rollNo, String dob) {
this.name = name;
this.rollNo = rollNo;
this.creationDate = dob;
}
}
The output looks like this:
raw data with link
{
"item" : "link",
"foo" : "foo"
}
ItemA POJO data
{
"item" : {
"name" : "ID3",
"rollNo" : 1,
"creationDate" : "1984-12-30"
},
"foo" : "bar"
}
The CustomItemSerializer decides if the link is printed or the POJO.

Jackson ObjectMapper Not Reading Inner Objects

I have a JSON file
{
"readServiceAuthorizationResponse": {
"serviceAuthorization": {
"serviceAuthorizationId": "50043~220106065198",
"status": "Approved",
"receivedDate": "2022-1-6 1:21:12 PM",
"providerFirstName": "Ranga",
"providerLastName": "Thalluri",
"organizationName": "General Hospital",
"serviceLines": [{
"statusReason": "Approved",
"procedureDescription": "Room & board ward general classification",
"requestedQuantity": "1.00",
"approvedQuantity": "1.00",
"deniedQuantity": "",
"quantityUnitOfMeasure": "Day(s)",
"providers": [{
"providerFirstName": "Ranga",
"providerLastName": "Thalluri",
"organizationName": ""
}]
}]
}
}
}
My Java to read this into an object is this:
package com.shawn.dto;
import java.nio.file.Files;
import java.nio.file.Paths;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
#JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceAuthorizationDTO {
public String serviceAuthorizationId;
public String status;
public String receivedDate;
public String providerFirstName;
public String providerLastName;
public String organizationName;
public ServiceLine[] serviceLines;
public static ServiceAuthorizationDTO create(String json) {
ObjectMapper m = new ObjectMapper();
try {
Outer outer = m.readValue(json, Outer.class);
return outer.readServiceAuthorizationResponse.serviceAuthorization;
} catch (Exception e) {
return null;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class ReadServiceAuthorizationResponse {
public ServiceAuthorizationDTO serviceAuthorization;
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class Outer {
public ReadServiceAuthorizationResponse readServiceAuthorizationResponse;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class ServiceLine {
String statusReason;
String procedureDescription;
String requestedQuantity;
String approvedQuantity;
String deniedQuantity;
String quantityUnitOfMeasure;
Provider[] providers;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Provider {
String providerFirstName;
String providerLastName;
String organizationName;
}
public static void main(String[] args) {
try {
String json = new String(Files.readAllBytes(Paths.get("c:/temp/test.json")));
ServiceAuthorizationDTO dao = ServiceAuthorizationDTO.create(json);
System.out.println("serviceAuthorizationId: " + dao.serviceAuthorizationId);
System.out.println("serviceLines[0].procedureDescription: " + dao.serviceLines[0].procedureDescription);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
When I run it I get this:
serviceAuthorizationId: 50043~220106065198
serviceLines[0].procedureDescription: null
The outer fields in the object like providerId are read from the JSON. But the serviceLines array shows 1 element, and all fields in that class are empty.
Any ideas? This is the first time I've used real objects with JSON. I've always mapped it into Map objects and pulled the fields out manually. Thanks.
Fields in classes ServiceLine and Provider have package-private access modifiers. Jackson can't deserialize into private fields with its default settings. Because it needs getter or setter methods.
Solution 1: Make fields public
public static class ServiceLine {
public String statusReason;
public String procedureDescription;
public String requestedQuantity;
public String approvedQuantity;
public String deniedQuantity;
public String quantityUnitOfMeasure;
public Provider[] providers;
}
Solution 2: Use #JsonAutoDetect annotation
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class ServiceLine {
String statusReason;
String procedureDescription;
String requestedQuantity;
String approvedQuantity;
String deniedQuantity;
String quantityUnitOfMeasure;
Provider[] providers;
}
Solution 3: Change visibility on the ObjectMapper (doc)
public static ServiceAuthorizationDTO create(String json) {
ObjectMapper objectMapper = new ObjectMapper();
try {
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
Outer outer = objectMapper.readValue(json, Outer.class);
return outer.readServiceAuthorizationResponse.serviceAuthorization;
} catch (Exception e) {
return null;
}
}

Creating complex JSON payload from Java Pojo Jackson

I want to create below JSON payload
{
"maxResults":3,
"counter":0,
"customerParameters":{
"filters":[
{
"name":"customerId",
"operator":"=",
"value":["hello"]
}
]
},
"dealerParameters":[
{
"name":"club"
},
{
"name":"token"
}
]
}
Coded so far:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"maxResults",
"counter",
"customerParameters",
"dealerParameters"
})
public class CustomerModel {
#JsonProperty("maxResults")
private Integer maxResults;
#JsonProperty("counter")
private Integer counter;
#JsonProperty("customerParameters")
private CustomerParameters customerParameters;
#JsonProperty("dealerParameters")
private List<DealerParameter> dealerParameters = null;
#JsonProperty("customerParameters")
public CustomerParameters getCustomerParameters() {
return customerParameters;
}
#JsonProperty("customerParameters")
public void setCustomerParameters(CustomerParameters customerParameters) {
this.customerParameters = customerParameters;
}
#JsonProperty("dealerParameters")
public List<DealerParameter> getDealerParameters() {
return dealerParameters;
}
#JsonProperty("dealerParameters")
public void setDealerParameters(List<DealerParameter> dealerParameters) {
this.dealerParameters = dealerParameters;
}
// Getter/Setter for other params
}
CustomerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"filters"
})
public class CustomerParameters {
#JsonProperty("filters")
private List<Filter> filters = null;
// Setter and Getter for filters parameter
}
DealerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name"
})
public class DealerParameter {
#JsonProperty("name")
private String name;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
}
Filter.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name",
"operator",
"value"
})
public class Filter {
#JsonProperty("name")
private String name;
#JsonProperty("operator")
private String operator;
#JsonProperty("value")
private List<String> value = null;
#JsonProperty("value")
public List<String> getValue() {
return value;
}
#JsonProperty("value")
public void setValue(List<String> value) {
this.value = value;
}
// Setter and Getter for other properties
}
Missing Part:
#Controller
public class TestContoller {
RestTemplate restTemplate;
Should I instantiate each pojo class with new operator as below and set all required parameters ? or any other approach of creating JSON payload?
CustomerModel customerModel= new CustomerModel();
customerModel.setMaxResults(1);
Filter filter= new Filter();
filter.setName("customerID");
filter.setOperator("-");
filter.setValue(Arrays.asList("club"));
CustomerParameters customerParameters = new CustomerParameters();
customerParameters.setFilters(Arrays.asList(filter));
customerModel.setCustomerParameters(customerParameters);
For DealerParameter class, I want to create multiple objects with same key different value(see the json payload I mentioned above). Below code creates only one object "name":"dealerId"
DealerParameter dealerParameter = new DealerParameter();
dealerParameter.setName("dealerId");
customerModel.setDealerParameters(dealerParameter);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsString(customerModel);
restTemplate.exchange(todo); // restful service call
}
you are already using "ObjectMapper", And ObjectMapper has readValue() method. By using readValue() method you can populate all data at a time like below:--
ObjectMapper objectMapper = new ObjectMapper();
//populating data from json string to POJO
CustomerModel customerModel = objectMapper.readValue(<json String>,CustomerModel.class);
System.out.println(objectMapper.writeValueAsString(customerModel); // print all data

Changing name while assigning and getting value from and to json - Objectmapper java

While assigning values from DTO to different entity names, I assigned correctly with the help of JsonProperty. FrontEnd expecting in a different name. Values for DTO object will get from different object. That I have to assign to entity. Instead of using plain java and copying, am using objectmapper. Here then entity values will be used by frontend. How to print the entity values in different name? Please check below code.
//DTO Class
import java.util.List;
public class StaffDTO {
private String nameDT;
private List<String> skillDT;
//Getter and Setters
}
//Entity Class
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"nameDT",
"skillDT"
})
public class Staff {
#JsonProperty("nameDT")
private String name;
#JsonProperty("skillDT")
private List<String> skills;
//Getter and Setters
}
//Call Method
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SeatMapCall1 {
public static void main(String[] args) {
try {
StaffDTO staffDTO = createDummyObject();
System.out.println(convertObjectToJson(staffDTO));
Staff staff= convertJsonToObject(convertObjectToJson(staffDTO),Staff.class);
System.out.println(convertObjectToJson(staff));
} catch (Throwable e) {
e.printStackTrace();
}
}
private static <T> T convertJsonToObject(String jsonStrRes,
Class<T> classArg) {
T resObj = null;
try {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
resObj = mapper.readValue(jsonStrRes, classArg);
} catch (Throwable e) {
e.printStackTrace();
}
return resObj;
}
public static <T> String convertObjectToJson(T obj) {
String jsonStringReq = null;
ObjectMapper objMapper = new ObjectMapper();
try {
jsonStringReq = objMapper.writeValueAsString(obj);
} catch (Exception e) {
}
return jsonStringReq;
}
private static StaffDTO createDummyObject() {
StaffDTO staffDTO = new StaffDTO();
staffDTO.setNameDT("mkyong");
List<String> skills = new ArrayList<>();
skills.add("java");
skills.add("python");
staffDTO.setSkillDT(skills);
return staffDTO;
}
}
//Displays output as
{"nameDT":"mkyong","skillDT":["java","python"]}
{"nameDT":"mkyong","skillDT":["java","python"]}
But I want
{"nameDT":"mkyong","skillDT":["java","python"]}
{"name":"mkyong","skills":["java","python"]}
If I use the below getter and setters in Staff class, I am getting expected as below
{"nameDT":"mkyong","skillDT":["java","python"]}
{"nameDT":"mkyong","skillDT":["java","python"],"name":"mkyong","skills":["java","python"]}
Here it includes both nameDT, skillDT and name, skills. I don't need nameDT, skillDT.
#JsonProperty("nameDT")
private String nameDT;
#JsonProperty("skillDT")
private List<String> skillDT;
public String getName()
{ return nameDT; }
public void setName(String nameDT)
{ this.nameDT = nameDT; }
public List<String> getSkill()
{ return skillDT; }
public void setSkill(List<String> skillDT)
{ this.skillDT = skillDT; }

JPA not updating column with Converter class

I'm using a Converter class to store a complex class as JSON text in mySQL. When I add a new entity, the Converter class works as intended. However, when I update the entity, the data in the complex class is not updated in the database but it's updated in memory. Other attributes such as Lat and Long are updated. The breakpoint I placed at the convertToDatabaseColumn method and it did not trigger on update.
Object Class
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
private String country;
#Enumerated(EnumType.STRING)
private StatusType status;
private String street;
private double latitude;
private double longitude;
#Convert(converter=ProjectPropertyConverter.class)
private ProjectProperty property;
}
public class ProjectProperty {
private String description;
private List<String> projectImgs;
private Boolean hasImages;
}
Property Converter Class
#Converter (autoApply=true)
public class ProjectPropertyConverter implements AttributeConverter<ProjectProperty, String> {
#Override
public String convertToDatabaseColumn(ProjectProperty prop) {
try {
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(prop);
return jsonString;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
#Override
public ProjectProperty convertToEntityAttribute(String jsonValue) {
try {
ObjectMapper mapper = new ObjectMapper();
ProjectProperty p = mapper.readValue(jsonValue, ProjectProperty.class);
if(p.getProjectImgs().isEmpty())
{
p.setHasImages(Boolean.FALSE);
}
else
{
p.setHasImages(Boolean.TRUE);
}
return p;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
}
Method to Update Database
public void modifyEntity(Object entity, String query, HashMap params) {
try {
tx.begin();
em.flush();
tx.commit();
} catch (Exception e) {
e.toString();
}
}
I came here looking for same answers. Turns out the problem is JPA doesn't know that your object is dirty. This was solved by implementing equals()/hashcode() methods on this complex objects. In your example, implement equals and hashcode for ProjectProperty
Once that is done, JPA is able to identify via these methods that the underlying object is dirty and converts and persists.

Categories