Mapping a flat map to a nested pojo in jackson - java

How can I map a flat map to a nested pojo?
I've tried using this, however I get an unrecognized field exception on the field sword.
Map<String, Object> values = ...;
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(mapper.writeValueAsString(values), Person.class);
I have this dummy json:
{
"firstName": "Arya",
"lastName": "Stark",
"gender": "Female",
"sword" : "Excalibur",
"shield": "Mighty Shield"
}
a person class:
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
private Equipment equipments;
}
and an equipment class:
#Data
#AllArgsConstructor
public class Equipment {
private String sword;
private String shield;
}

I see this way to solve this problem:
public class Test {
public static void main(String[] args) {
Map<String, Object> values = new HashMap<>();
values.put("firstName", "Arya");
values.put("lastName", "Stark");
values.put("gender", "Female");
values.put("sword", "Excalibur");
values.put("shield", "Mighty Shield");
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Person person = mapper.convertValue(values, Person.class);
person.setEquipments(mapper.convertValue(values, Equipment.class));
System.out.println(person);
}
}
Of course it is not one line deserialization but it works. Result:
Person(firstName=Arya, lastName=Stark, equipments=Equipment(sword=Excalibur, shield=Mighty Shield))
Note: add #NoArgsConstructor to your POJO, it is required by Jackson.

Meanwhile jackson added #JsonUnwrapped and lombok added #Jacksonized this reduces your overhead to a minimum of:
#Value
#Builder
#Jacksonized
public class Person {
private String firstName;
private String lastName;
#JsonUnwrapped
private Equipment equipments;
}
Note: #Jacksonized is only necessary in combination with #Builder. But this prevents us from using the #NoArgsConstructor

I think you have a mistake when you collect attributes for different classes into one map. Why did you do that?
It's easy transform json to object with nested classes if your json has the same structure. In your example it should be:
{
"person": {
"firstName": "name",
"lastName": "name2",
"gender": "male",
"equipment": {
"sword": "s",
"shield": "s2"
}
}
}
That json can be easily transformed to Person.java by objectMapper.readValue(json, Person.class);

Related

Java: Deserialize object with list of Objects

I have this JSON: (passed as Map<String, Object>)
{
"id": 1000,
"lab": [
"LAB1",
"LAB2",
"LAB3"
],
"name": "TEST",
"ref": {
"id": 1000,
"code": "REFCODE",
"description": "REF DESC"
},
"employee": {
"id": 1000,
"name": "Emp1000",
"tin": null,
"active": true
},
"contacts": [
{
"id": 1000,
"name": "Contact 1",
"emailAddress": "contact1#test.com",
"active": true,
"positions": [
{
"position": {
"id": 1000,
"code": "POS",
"description": "POS DESC"
}
}
]
}
],
"status": "NEW"
}
This is my DTO and ContactDTO:
public class DTO {
private Long id;
...
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
public class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
#NotEmpty
private List<ContactPositionDTO> positions;
}
Here is my service class with object mapper and process method which accepts the JSON map:
private ObjectMapper objectMapper() {
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return objectMapper;
}
public void process(final Map<String, Object> map) {
objectMapper().convertValue(map, DTO.class);
}
However, I am getting java.lang.IllegalArgumentException: Cannot deserialize value of type java.util.ArrayList
And if I add DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY I am getting a different error:
java.lang.IllegalArgumentException: Cannot construct instance of ContactDTO (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('[{id=1000, name=Contact 1, .....
You have two alternative options to fix your ContactDTO class:
Add a no-arguments constructor
public ContactDTO() { }
to the class. To fix the then upcoming compiler error
you will need to remove the #Builder annotation.
Keep the #Builder annotation
and add the #Jacksonized annotation to the class.
This will configure the generated builder to cooperate
with Jackson's deserialization.
For more details see Lombok's documentation about #Jacksonized.
To deserialize you need a No arg constructor and to use #Builder you need an all arg constructor.
So you need tu add both.
The example below should work. I just added #Getter annotation to avoid using #JsonProperty
#Getter
public static class DTO {
private Long id;
#JsonProperty("contacts")
private List<ContactDTO> contacts;
}
#Builder
#Getter
#NoArgsConstructor
#AllArgsConstructor
public static class ContactDTO implements Serializable {
private Long id;
private String name;
private String emailAddress;
private Boolean active;
private List<ContactPositionDTO> positions;
}
#Getter
public static class ContactPositionDTO {
private Position position;
#JsonProperty("effectiveStartDate")
private List<Integer> date;
#Getter
static class Position {
private Integer id;
private String code;
private String description;
}
}
NB: you can also use #Jacksonized annotation instead of #NoArgsConstructor and #AllArgsConstructor

Remove method name from posted comment

I made a code which add comments on my localhost:3000 but its parsing to much info i want to remove "commentModel" but if i remove it from CommentRq class i get errors
comment example:
{ "commentModel": { "comment": "komentarz", "date": "3/6/19 9:34 AM" }, "id": 1}
i want it to be { "comment": "komentarz", "date": "3/6/19 9:34 AM" }, "id": 1 }
CommentRq
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class CommentRq {
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private CommentModel commentModel;
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public static class CommentModel {
#JsonProperty("comment")
String resourceName;
#JsonProperty("date")
String resourceNamed;
}
}
CommentBody
public class CommentBody {
Date now = new Date();
#JsonInclude(JsonInclude.Include.NON_NULL)
public CommentRq RequestCommentBody() {
return CommentRq.builder()
.commentModel(new CommentRq.CommentModel(
"komentarz",
(DateFormat.getInstance().format(now))
))
.build();
}
}
Here i create comment
Interface.PostComment postComment = Feign.builder()
.client(new OkHttpClient())
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logger(new Slf4jLogger(Interface.PostComment.class))
.logLevel(Logger.Level.FULL)
.target(Interface.PostComment.class, "http://localhost:3000/comments/");
#When("i try to add a comment")
public void postComment() {
Map<String, Object> headermap = new HashMap<>();
headermap.put("Content-Type", "application/json");
CommentBody requestComment = new CommentBody();
CommentRes commentRes = postComment.postComment(headermap, requestComment.RequestCommentBody());
id = commentRes.getId();
LOGGER.info("Created: " + DateFormat.getInstance().format(now));
}
You can annotate your private CommentModel commentModel with #JsonUnwrapped. It will unwrap your commentModel object and write its fields to the root of the json. This will handle your specific case. But you can revise your request structure as well: put CommentModel fields into CommentRq and map CommentModel object to CommentRq object.

convert List<Object> to List<HashMap<String,Double>> for a json array

I have a Json as shown below:-
{
"componentModelData": {
"modelNumber": "ABC",
"modelName": "",
"modelDescription": "",
"modelVendor": null,
"componentName": "HELO"
},
"revisionData": {
"revisionName": "rev12",
"revisionComment": "Comment",
"modifiedBy": "2323553"
},
"configData": [{"nodes":2085,"FxPOS-SX":16.5051,"FxPOS-SY":11.0479,"FxPOS-SZ":115.3421,"FxPOS-SXY":-13.8094,"FxPOS-SYZ":36.0105
},{"nodes":2085,"FxPOS-SX":16.5051,"FxPOS-SY":11.0479,"FxPOS-SZ":115.3421,"FxPOS-SXY":-13.8094,"FxPOS-SYZ":36.0105}]
}
DataMapping.java
#Getter
#Setter
#JsonSerialize
#NoArgsConstructor
#AllArgsConstructor
public class DataMapping implements Serializable {
/**
*
*/
private static final long serialVersionUID = -2635323042843403966L;
#JsonProperty("componentModelData")
#Valid
public ComponentModelDTO componentModelDTO;
#JsonProperty("revisionData")
#Valid
public RevisionDTO revisionDTO;
#JsonProperty("configData")
private List<Object> configData;
}
I want to convert the configData list to List<HashMap<String,Double>> list where String is the keys like 'nodes,FxPOS-SX,FxPOS-SY' and value is their respective values.
Can anyone please help me how can i achieve this?
Just replace List<Object> with List<Map<String,Double>>. Jackson will do the hard work for you:
#JsonProperty("configData")
private List<Map<String, Double>> configData;

Converting camelCase to underscore when deserializing an object using ObjectMapper

I have a fixture file person.json,
{
"fistName": "John"
"lastName": "Smith"
}
I have a class called Person
public class Person {
private String firstName;
private String lastName;
//.. getters and setters
}
I deserialize Person using ObjectMapper like below
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(new FileInputStream(new File("person.json")),Person.class);
I get this error,
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "firstName" (class com.foo.Person), not marked as ignorable (2 known properties: , "first_name", "last_name"])
I get the same error when I use
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);.
My question is Why do I get this error and how to resolve it?
Because you ask Jackson to use a naming strategy that translates camel case, i.e. firstName to lower case with underscore i.e. first_name.
public class App {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(new FileInputStream(new File("/path/to/person.json")),Person.class);
System.out.println(person);
}
}
#Data // lombok #Data
public class Person {
private String firstName;
private String lastName;
}
and person.json (fixed):
{
"firstName": "John",
"lastName": "Smith"
}
Output:
Person(firstName=John, lastName=Smith)

How can I deserialize a JSON array of "name" "value" pairs to a Pojo using Jackson

I want to deserialize the following JSON object:
{
"id":"001",
"module_name":"Users",
"name_value_list":
{
"user_name": {"name":"user_name", "value":"admin"},
"full_name": {"name":"full_name", "value":"LluĂ­s Pi"},
"city": {"name":"full_name", "value":"Barcelona"},
"postal_code": {"name":"postal_code", "value":"08017"},
...
}
}
into some Java object like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
public class UserEntry
{
private String id;
private String moduleName;
private Person nameValueList;
public String getId()
{
return id;
}
public String getModuleName()
{
return moduleName;
}
public Person getPerson()
{
return nameValueList;
}
}
where Person is the following class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
class Person
{
private String userName;
private String fullName;
private String city;
private String postalCode;
}
using Jackson but I get a deserialization error.
If I change the type of field nameValueList to a Map all the deserialization process goes with no problem and I get a map where the key is the "name" value and the value is the "value" value.
So my question is: is there any simple, or no so simple, way to deserialize this kind of JSON object to a Java Pojo with properties prop_1, prop_2, prop_3and prop_4?
{
"name_value_list":
{
"prop_1": {"name":"prop_1", "value":"value_1"},
"prop_2": {"name":"prop_2", "value":"value_2"},
"prop_3": {"name":"prop_3", "value":"value_3"},
"prop_4": {"name":"prop_4", "value":"value_4"},
...
}
}
Not very simple and not very clean. However you can do it by implementing a any setter field for the JSON attributes in the Person class which don't match any attribute on your UserEntry POJO.
#JsonAnySetter
public void putUserField(String userKey, Map<String, String> userValue)
throws NoSuchFieldException {
String actualFieldName = getActualFieldName(userKey);
Field field = this.getClass().getDeclaredField(actualFieldName);
field.setAccessible(true);
ReflectionUtils.setField(field, this, userValue.get("value"));
}
private String getActualFieldName(String userKey) {
return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, userKey);
}
In addition to that, I had to change the Jackson attributes for the Person class to
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY,
getterVisibility = JsonAutoDetect.Visibility.NONE)
for it to work for attributes like "city" which don't need any name transformation because jackson tries to directly set the field which fails.

Categories

Resources