Spring boot #JsonProperty to map nested object through parent.child path - java

I have the following Data structure coming to my server through an Http call
person {
name: "xyz",
age: 30,
status: {
cd: "M",
desc: "Married"
}
}
Then I have the following entity class
#Setter
#Getter
public class Person {
private String name;
private int age;
#JsonProperty("status.cd")
private String status_cd;
#JsonProperty("status/desc")
private String status_desc;
}
Either way I try I'm not able to get spring boot to match the json property text (these two values are always null)... is that doable & if so, how?
the final JSON I need to be produced is the following
{
name: "XYZ",
age: 30,
status_cd: "M", // <-- Child attached to parent (flattened)
status_desc: "Married" // <-- Child attached to parent (flattened)
}
So basically I need to attach my child info to the parent.
UPDATE
I was able to get one of the properties flattened with the parent by using a custom JsonDeserializer as follows
public class nameDeserializer extends JsonDeserializer<String> {
#Overrde
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
PersonStatus st = p.valueAs(PersonStatus.class)
return st.getStatusCd();
}
This works for only one property set with #JsonDeserialize(using = nameDeserializre.class) per class.... once I attach two deserializers to two properties, I start getting errors about json exceptions.
Thank you

Try something like this
public class Person {
private String name;
private int age;
private Status status;
//your getter setter
}
public class Status {
private String cd;
private String desc;
// getter setter
}

Related

How can I prevent an empty JSON object from being deserialized to a Java bean? [duplicate]

At the deserialization process (which as I understand is the process of converting JSON data into a Java Object), how can I tell Jackson that when it reads a object that contains no data, it should be ignored?
I'm using Jackson 2.6.6 and Spring 4.2.6
The JSON data received by my controller is as follows:
{
"id": 2,
"description": "A description",
"containedObject": {}
}
The problem is that the object "containedObject" is interpreted as is and it's being instantiated. Therefore, as soon as my controller reads this JSON data, it produces an instance of the ContainedObject object type but I need this to be null instead.
The easiest and fastest solution would be that in the JSON data received, this value be null like this:
{
"id": 2,
"description": "A description",
"containedObject": null
}
But this isn't possible since I'm not in control of the JSON data that is sent to me.
Is there an annotation (like this explained here) that works for the deserialization process and could be helpfull in my situation?
I leave a representation of my classes for more information:
My entity class is as follows:
public class Entity {
private long id;
private String description;
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
And my contained object class as follows:
public class ContainedObject {
private long contObjId;
private String aString;
//Contructor, getters and setters omitted
}
I would use a JsonDeserializer. Inspect the field in question, determine, if it is emtpy and return null, so your ContainedObject would be null.
Something like this (semi-pseudo):
public class MyDes extends JsonDeserializer<ContainedObject> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
//read the JsonNode and determine if it is empty JSON object
//and if so return null
if (node is empty....) {
return null;
}
return node;
}
}
then in your model:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = MyDes.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
Hope this helps!
You can implement a custom deserializer as follows:
public class Entity {
private long id;
private String description;
#JsonDeserialize(using = EmptyToNullObject.class)
private ContainedObject containedObject;
//Contructor, getters and setters omitted
}
public class EmptyToNullObject extends JsonDeserializer<ContainedObject> {
public ContainedObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long contObjId = (Long) ((LongNode) node.get("contObjId")).numberValue();
String aString = node.get("aString").asText();
if(aString.equals("") && contObjId == 0L) {
return null;
} else {
return new ContainedObject(contObjId, aString);
}
}
}
Approach 1 : This is mostly used. #JsonInclude is used to exclude properties with empty/null/default values.Use #JsonInclude(JsonInclude.Include.NON_NULL) or #JsonInclude(JsonInclude.Include.NON_EMPTY) as per your requirement.
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
private String empId;
private String firstName;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String lastName;
private String address;
private String emailId;
}
More info about the jackson annotations : https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
Approach 2 : GSON
use GSON (https://code.google.com/p/google-gson/)

PUT request with JSON payload sent from Postman, nested object not parsed

I didn't have this problem before, with other POJOs, I'm not sure what's different this time, but I can't get this working and I could not find an exact solution for this.
I have this POJO called Component (with some Hibernate annotations):
#Entity
#Table(name="component", uniqueConstraints={#UniqueConstraint(
columnNames = {"name", "component_type"})})
public class Component {
#Column(name="id")
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="name")
private String name;
#Column(name="component_type")
private String componentType;
#Column(name="serial_number")
private int serialNumber;
#Column(name="active_since")
private String activeSince;
#Embedded
private ComponentWearoutModel wearout;
public Component() {
}
public Component(String name, String componentType, int serialNumber, String activeSince,
ComponentWearoutModel wearout) {
this.name = name;
this.componentType = componentType;
this.serialNumber = serialNumber;
this.activeSince = activeSince;
this.wearout = wearout;
}
public ComponentWearoutModel getModel() {
return wearout;
}
public void setModel(ComponentWearoutModel wearout) {
this.wearout = wearout;
}
//more getters and setters
}
ComponentWearoutModel:
#Embeddable
public class ComponentWearoutModel {
private String componentType; //dont mind the stupid duplicate attribute
private Integer componentLifeExpectancy;
private Float componentWearOutLevel;
private Float actionThreshold;
public ComponentWearoutModel() {
}
public ComponentWearoutModel(String componentType, int componentLifeExpectancy, float componentWearOutLevel,
float actionThreshold) {
this.componentType = componentType;
this.componentLifeExpectancy = componentLifeExpectancy;
this.componentWearOutLevel = componentWearOutLevel;
this.actionThreshold = actionThreshold;
}
//getters and setters
}
The sample payload I use:
{
"name": "component name",
"componentType": "airfilter2",
"serialNumber": 573224,
"activeSince": "2016-04-10 17:38:41",
"wearout":
{
"componentType": "airfilter",
"componentLifeExpectancy": 1000,
"componentWearOutLevel": 0.24,
"actionThreshold": 0.2
}
}
And finally the resource class:
#Path("myresource")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public class MyResource {
DatabaseManager dm = DatabaseManager.getInstance();
#PUT
#Path("Component")
public Response storeComponent(Component component){
System.out.println("reached");
System.out.println(component.getComponentType()); //okay
System.out.println(component.getModel().getComponentType()); //nullpointerexception
ComponentWearoutModel model = new ComponentWearoutModel("type", 1000, 1f, 0.2f);
component.setModel(model); //this way it's saved in the db just fine
dm.save(component);
return Response.status(Status.OK).entity(component).build();
}
}
Without the prints, only the fields which are not part of the ComponentWearoutModel class are stored in the database table, the other columns are null. So when I try to print one of them, I get an exception, I just dont understand why. If I create a ComponentWearoutModel in the resource method and add it to the component, everything is fine in the database.
UPDATE:
so my mistake was that I named the ComponentWearoutModel attribute as "wearout" in the Component.class, but the autogenerated getters and setter were called getModel/setModel and moxy could not parse my payload because of this. Solution: change the attribute name to "model" in Component class and in payload too.
Please ensure that the attribute names you are using in the POJO are same as what are being sent in the json string.
Since there are no jackson etc annotations being used in your POJO to tell it the corresponding json mapping, the underlying code will directly use the names given in json string. If you are using the string "model", the convertor code will look for a "setModel" method in your POJO.
In the above example, either call everything "model", or "wearable".

Jackson library: custom mapping of a Java object in a JSON element

I have two class
public class Person {
private long id;
private String name;
private Gender gender;
// getter and setter omitted
}
and
public class Gender {
private long id;
private String value;
// getter and setter omitted
}
By default the JSON mapping with Jackson library of a Person object is:
{
id: 11,
name: "myname",
gender: {
id: 2,
value: "F"
}
}
I'd like to known how to configure Jackson to obtain:
{
id: 11,
name: "myname",
gender: "F"
}
I don't want mapping all the Gender object but only its value property.
You can use a custom serializer:
public class GenderSerializer extends JsonSerializer<Gender> {
public GenderSerializer() {
}
#Override
public void serialize(Gender gender, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(gender.getValue());
}
}
And in your Person class:
public class Person {
private long id;
private String name;
#JsonSerialize(using = GenderSerializer.class)
private Gender gender;
}
you might wanna see this for custom mapping OR if you need a short cut then you can change getter/setter of gender like this
public String getGender(String type){
this.getGender().getValue();
}
public void setGender(String value){
Gender gender = new Gender();
gender.setId(2);
gender.setValue(value);
this.gender = gender;
}
further you can also put condition for setting male/female on value= M/F
No need for custom serializer/deserializer if you can modify Gender class. For serialization you can use #JsonValue, for deserialization simple constructor (optionally annotated with #JsonCreator):
public class Gender {
#JsonCreator // optional
public Gender(String desc) {
// handle detection of "M" or "F"
}
#JsonValue
public String asString() { // name doesn't matter
if (...) return "M";
return "F";
}
}
Alternatively you could use a static factory method instead of constructor; if so, it must be annotated with #JsonCreator.
And return type of method annotated with #JsonValue can be anything that Jackson knows how to serialize; here we use String, but Enum, POJO and such are fine as well.

Spring class RequestBody and MongoRepository

I have class:
class TestClass {
#Id
private ObjectId id;
private ObjectId parentId;
private String name;
private String describe;
private String privateData;
public TestClass(ObjectId parentId, String name, String describe, String privateData) {
this.parrentId = parrentId;
this.name = name;
this.describe = describe;
this.privateDate = privateData;
}
// get/set methods...
}
Can I use this class in MongoRepository and #RequestBody? Is it safe? parrentId and privateData is private properties and RequestBody does not have to fill them.
mongorepository:
public interface TestClassRepository extends MongoRepository<TestClass, String> {
public TestClass findById(ObjectId id);
}
post method:
#RequestMapping(value="/testclass", method=RequestMethod.POST)
public void create(#RequestBody TestClass testClass) {
testClass.setParentId(...);
repo.insert(testClass);
}
For example:
{"name": "test", "describe": "test", "id": "54d5261a8314fe3c650d5b1d", "parentId": "54d5261a8314fe3c650d5b1d", "privateData": "WrongPrivateData"}
How can I do that it was impossible to set properties id, parentId, privateDate?
Or need I create new class for RequestBody? I don't want duplicate code.
It should be better and safe to use separate models for DAO and VO layers(view). If your models currently looks the same, it doesn't mean that they will stay the same in future. You can use the Dozer Mapping framework for mappings between your models. It's easy,fast and safe.
If you need to skip some field from mongotemplate mapping use #Transient annotation.
P.S. You don't need findById method, because mongotemplate already have find method which uses key as param. TestClass should have an empty constructor.

Struts2 + Json Serialization of items

I have the following classes:
public class Student {
private Long id ;
private String firstName;
private String lastName;
private Set<Enrollment> enroll = new HashSet<Enrollment>();
//Setters and getters
}
public class Enrollment {
private Student student;
private Course course;
Long enrollId;
//Setters and Getters
}
I have Struts2 controller and I would like to to return Serialized instance of Class Student only.
#ParentPackage("json-default")
public class JsonAction extends ActionSupport{
private Student student;
#Autowired
DbService dbService;
public String populate(){
return "populate";
}
#Action(value="/getJson", results = {
#Result(name="success", type="json")})
public String test(){
student = dbService.getSudent(new Long(1));
return "success";
}
#JSON(name="student")
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
It returns me the serializable student object with all sub classes, but I would like to have only student object without the hashset returned .
How can I tell Struts to serialize only the object?
I do have Lazy loading enabled and hashset is returned as proxy class.
See the answer here which shows the use of include and exclude properties. I don't think the example clearly shows excluding nested objects however I have used it for this purpose. If you still have issues I'll post a regex which will demonstrate this.
Problem with Json plugin in Struts 2
Edit:
Here is an example of using exclude properties in an annotation which blocks the serialization of a nested member:
#ParentPackage("json-default")
#Result(type = "json", params = {
"excludeProperties",
"^inventoryHistory\\[\\d+\\]\\.intrnmst, selectedTransactionNames, transactionNames"
})
public class InventoryHistoryAction extends ActionSupport {
...
inventoryHistory is of type InventoryHistory a JPA entity object, intrnmst references another table but because of lazy loading if it were serialized it would cause an Exception when the action is JSON serialized for this reason the exclude parameter has been added to prevent this.
Note that
\\
is required for each \ character, so a single \ would only be used in the xml where two are required because of escaping for the string to be parsed right.
#Controller
#Results({
#Result(name="json",type="json"
, params={"root","outDataMap","excludeNullProperties","true"
,"excludeProperties","^ret\\[\\d+\\]\\.city\\.province,^ret\\[\\d+\\]\\.enterprise\\.userinfos","enableGZIP","true"
})
})
public class UserinfoAction extends BaseAction {
#Action(value="login")
public String login(){
if(jsonQueryParam!=null && jsonQueryParam.length()>0)
{
user = JsonMapper.fromJson(jsonQueryParam, TUserinfo.class);
}
Assert.notNull(user);
//RESULT="ret" addOutJsonData: put List<TUserinfo> into outDataMap with key RESULT for struts2 JSONResult
addOutJsonData(RESULT, service.login(user));
return JSON;
}
public class TUserinfo implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String userid;
private String username;
private String userpwd;
private TEnterpriseinfo enterprise;
private String telphone;
private TCity city;
......
}
public class TEnterpriseinfo implements java.io.Serializable {
private String enterpriseid;
private String enterprisename;
private Set<TUserinfo> userinfos = new HashSet<TUserinfo>(0);
.......}
before set the excludeProperties property,the result is below:
{"ret":[
{
"city":{"cityename":"tianjin","cityid":"12","cityname":"天津"
,"province": {"provinceename":"tianjing","provinceid":"02","provincename":"天津"}
}
,"createddate":"2014-01-07T11:13:58"
,"enterprise":{"createddate":"2014-01-07T08:38:00","enterpriseid":"402880a5436a227501436a2277140000","enterprisename":"测试企业2","enterprisestate":0
,"userinfos":[null,{"city":{"cityename":"beijing","cityid":"11","cityname":"北京","province":{"provinceename":"beijing","provinceid":"01","provincename":"北京市"}
},"comments":"ceshi","createddate":"2004-05-07T21:23:44","enterprise":null,"lastlogindate":"2014-01-08T08:50:34","logincount":11,"telphone":"2","userid":"402880a5436a215101436a2156e10000","username":"0.5833032879881197","userpwd":"12","userstate":1,"usertype":0}]
}
,"lastlogindate":"2014-01-08T10:32:43","logincount":0,"telphone":"2","userid":"402880a5436ab13701436ab1b74a0000","username":"testUser","userpwd":"333","userstate":1,"usertype":0}]
}
after set the excludeProperties property,there are not exist province and userinfos nodes, the result is below:
{"ret":
[{
"city":{"cityename":"tianjin","cityid":"12","cityname":"天津"}
,"createddate":"2014-01-07T11:13:58"
,"enterprise":{"createddate":"2014-01-07T08:38:00","enterpriseid":"402880a5436a227501436a2277140000","enterprisename":"测试企业2","enterprisestate":0}
,"lastlogindate":"2014-01-08T11:05:32","logincount":0,"telphone":"2","userid":"402880a5436ab13701436ab1b74a0000","username":"testUser","userpwd":"333","userstate":1,"usertype":0
}]
}

Categories

Resources