I have a JSON structure where the key and value are stored like this
[
{
"key": "firstName",
"value": "John"
},
{
"key": "lastName",
"value": "Smith"
}
]
I would like to deserialize the array so that each key is a property name, and the appropriate value assigned.
public class Customer {
private String firstName;
private String lastName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
I have thought of creating a custom deserializer to accept an array of customer attributes, and manually setting each property based on the attribute name. My concern is this approach could be become brittle with the addition of more properties, and is not maintainable in the long term.
Does anyone know of any Jackson annotations that I may have overlooked that would aid in this deserialization?
Jackson will be able to deserialise a POJO from JSON Object or Map. You have a list of mini-Maps where each mini-Map contains exactly one property. You need to:
Deserialise input payload to List<Map<String, Object>>
Transform it to single Map object
Convert to Customer instance
Simple example:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class JsonMiniMapsApp {
public static void main(String[] args) throws IOException {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
// read as list of maps
List<Map<String, Object>> entries = mapper.readValue(jsonFile, new TypeReference<List<Map<String, Object>>>() {
});
// collect all values to single map
Map<Object, Object> map = entries.stream().collect(Collectors.toMap(e -> e.get("key"), e -> e.get("value")));
// convert to POJO
Customer customer = mapper.convertValue(map, Customer.class);
System.out.println(customer);
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
class Customer {
private String firstName;
private String lastName;
}
Above code prints:
Customer(firstName=John, lastName=Smith)
Just annotate the properties with #JsonProperty
public class Customer {
#JsonProperty("key")
private String firstName;
#JsonProperty("value")
private String lastName;
....
}
if you want to keep the names during serialization, you should annotate the setter and getter instead of the field.
Here is an example: Different names of JSON property during serialization and deserialization
This can be solved in a quite generic way with Jackson.
First, you will need a small Java class representing any
key/value pair (like for example {"key":"firstName","value":"John"})
from your JSON input. Let's call it Entry.
public class Entry {
private String key;
private Object value;
// getters and setters (omitted here for brevity)
}
Using the class above you can deserialize the JSON input
(as given in your question) to an array of Entry objects.
ObjectMpper objectMapper = new ObjectMapper();
File file = new File("example.json");
Entry[] entries = objectMapper.readValue(file, Entry[].class);
Then you can convert this Entry[] array to a Map<String, Object>,
and further to a Customer object
(using the Customer class as given in your question).
Map<String, Object> map = Arrays.stream(entries)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
Customer customer = objectMapper.convertValue(map, Customer.class);
Related
I have this API request payload containing some nested fields:
{
"myId": "studentOne",
"myFirstName": "joe",
"myLastName": "bloggs",
"demoPackages":
[{
"myparts": "https://example.com/myparts/a1234567-5d25-9gf1-23ua-45pb3874265l",
"myPackages": [
"https:/example.com/myPackages/0sk98926-939a-444a-95ta-8eb40125f7r1"
]
}
]
}
I have this corresponding request model DTO:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DemoRequest {
private String myId;
private String myFirstName;
private String myLastName;
private ArrayList<DemoPackage> demoPackages;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DemoPackage{
private String myparts;
private ArrayList myPackages;
}
}
Now, the challenge. When creating a builder object that holds the API request, I am lost as to how to pass the ArrayList fields. I tried this:
public Object createMyPayload(String myId, String myFirstName, String myLastName, ArrayList myparts, ArrayList myPackages) { //not too sure if I am passing myParts and myPackages correctly here
return DemoRequest.builder()
.myId(myId)
.myFirstName(myFirstName)
.myLastName(myLastName)
.releasePackages(myparts)
.releasePackages(myPackages)
.build();
When I call the createMyPayload() from another class to use the builder object, I am getting a compilation error which suggests that my ArrayList fields data type is wrong:
#When("I send a POST request to the endpoint (.*)$")
public void create(String endpoint, String myId, String myFirstName, String myLastName, ArrayList myparts, ArrayList myPackages) {
String id = "studentOne"
String myFirstName = "joe"
String myLastName = "bloggs"
String myParts = "https://example.com/myparts/a1234567-5d25-9gf1-23ua-45pb3874265l";
String myPackages = "https:/example.com/myPackages/0sk98926-939a-444a-95ta-8eb40125f7r1";
demoClass.post(createPayload.createMyPayload(myId, myFirstName, myLastName, myParts, myPackages), endpoint); // myParts and myPackages throw compilation error that data should be Arraylist but when I change to ArrayList, it's asking me to change back to String
How do I correctly pass myParts and myPackages to the lombok builder object and reuse them elsewhere?
This should work.
Note that I have used ArrayList & Array where [] would be better, but you mentioned API using ArrayList. Used List in declarations rather than ArrayList (because that's better practise)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PackageBuilderDemo {
public static void main(String[] args) {
PackageBuilderDemo packageBuilderDemo = new PackageBuilderDemo();
packageBuilderDemo.createMyPayload("studentOne", "joe", "bloggs", "https://example.com/myparts/a1234567-5d25-9gf1-23ua-45pb3874265l", Arrays.asList(new String[] {"https:/example.com/myPackages/0sk98926-939a-444a-95ta-8eb40125f7r1"}));
}
public DemoRequest createMyPayload(String myId, String myFirstName, String myLastName, String myParts, List<String> myPackages) {
DemoPackage demoPackage = DemoPackage.builder().myparts(myParts).myPackages(myPackages).build();
List<DemoPackage> demoPackages = new ArrayList<>();
demoPackages.add(demoPackage);
return DemoRequest.builder()
.myId(myId)
.myFirstName(myFirstName)
.myLastName(myLastName)
.demoPackages(demoPackages)
.build();
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DemoRequest {
private String myId;
private String myFirstName;
private String myLastName;
private List<DemoPackage> demoPackages;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class DemoPackage{
private String myparts;
private List<String> myPackages;
}
In my Spring project I have several objects that should be serialized to a specific JSON format.
public class Person {
private Integer id;
private String name;
private String height;
private Address address;
}
and
public class Address {
private String street;
private String city;
private String phone;
}
Let assume that Person.height and Address.phone should not appear in the JSON.
The resulting JSON should look like
{
"attributes": ["id", "name", "street", "city"],
"values": [12345, "Mr. Smith", "Main street", "Chicago"]
}
I can create create a standard JSON with an ObjectMapper and some annotations like #JsonProperty and #JsonUnwrapped where I disable some SerializationFeatures. But at the moment I'm not able to create such a JSON.
Is there an easy way to create this JSON? And how would the way back (deserialization) look like?
There are good reasons Jackson doesn't serializes maps in this format. It's less readable and also harder to deserialize properly.
But if you just create another POJO it's very easy to achieve what you want to do:
public class AttributeList {
public static AttributeList from(Object o) {
return from(new ObjectMapper().convertValue(o, new TypeReference<Map<String, Object>>() {}));
}
public static AttributeList from(Map<String, Object> attributes) {
return new AttributeList(attributes);
}
private final List<String> attributes;
private final List<Object> values;
private AttributeList(Map<String, Object> o) {
attributes = new ArrayList<>(o.keySet());
values = new ArrayList<>(o.values());
}
}
This question already has answers here:
Different names of JSON property during serialization and deserialization
(14 answers)
Closed 1 year ago.
I have the following requirement for JSON string conversion to Java Object.
class Person {
private String firstName;
private String lastName;
}
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
Person person = MAPPER.readValue(jsonString, Person.class);
The above conversion returns null as the Person class attribute name doesn't match.
With #JsonProperty it converts correctly, but the final JSON result key is the same key as in jsonString.
{
"FST_NME" : "stack",
"LST_NME" : "overflow"
}
but I am looking for something like below.
{
"firstName" : "stack",
"lastName" : "overflow"
}
I tried renaming the key in jsonString and it works as expected.
But can we achieve the above result using any annotations or any other approach?
Thanks.
You just need to add #JsonProperty in both setter and getters.
In your case,
You are reading JSON string key FST_NME, so you need to add #JsonProperty('FST_NME') in the setter method for firstName and as you want to get the final JSON string with key firstName so you need to add #JsonProperty('firstName') in the getter method of firstName.
And same for lastName.
Following is the working code.
package com.ubaid.stackoverflow;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class Saravanan {
#SneakyThrows
public static void main(String[] args) {
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
Person person = MAPPER.readValue(jsonString, Person.class);
String finalJson = MAPPER.writeValueAsString(person);
log.debug("Final JSON: {}", finalJson);
}
}
class Person {
private String firstName;
private String lastName;
#JsonProperty("firstName")
public String getFirstName() {
return firstName;
}
#JsonProperty("FST_NME")
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#JsonProperty("lastName")
public String getLastName() {
return lastName;
}
#JsonProperty("LST_NME")
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
The output of above code is:
Final JSON: {"firstName":"stack","lastName":"OVERFLOW"}
Add below annotation on gets methods.
#JsonGetter("FST_NME")
public String getFirstName(){
return first Name;
}
Read data to a DTO class (Person DTO) with #JsonProperty
Class Person as you wish
Convert DTO to Person
class PersonDTO {
#JsonProperty(value = "FST_NME")
private String firstName;
#JsonProperty(value = "LST_NME")
private String lastName;
}
class Person {
private String firstName;
private String lastName;
}
ObjectMapper MAPPER = new ObjectMapper();
String jsonString = "{\"FST_NME\":\"stack\",\"LST_NME\":\"OVERFLOW\"}";
PersonDTO persondto = MAPPER.readValue(jsonString, PersonDTO.class);
Person person = new Person();
person.setFirstName(persondto.getFirstName());
person.setLastName(persondto.getLastName());
Thanks for the question about the mismatch of property name. However, I have made them consistent but same error throws.
I'm using Jackson to convert JSON array to Java object array.
The code is as simple as below, here is the code entry:
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
import jackson.vo.User;
public class JsonConvertTest {
public static void main(String args[]){
try{
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("results.json");
User[] users= objectMapper.readValue(file, User[].class);
}catch(Exception e){
e.printStackTrace();
}
}
}
Here is the value object,
package jackson.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
public class User {
#JsonProperty("firstName")
String firstName;
#JsonProperty("lastName")
String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Here is the JSON array:
{
"users":[
{ "firstName":"Tom", "lastName":"Jackson"},
{ "firstName":"Jenny", "lastName":"Mary"},
{ "firstName":"Red", "lastName":"Blue"},
{ "firstName":"Jason", "lastName":"John"},
{ "firstName":"May", "lastName":"Black"}
]
}
The output is:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `jackson.vo.User[]` out of START_OBJECT token
at [Source: (File); line: 1, column: 1]
Thanks for your help in advance.
Root level object in your JSON file is a JSON object, yet you are telling jackson to read the file as an array of users.
Try with the following contents:
[
{ "firstName":"Tom", "lastName":"Jackson"},
{ "firstName":"Jenny", "lastName":"Mary"},
{ "firstName":"Red", "lastName":"Blue"},
{ "firstName":"Jason", "lastName":"John"},
{ "firstName":"May", "lastName":"Black"}
]
I am fairly certain this is an issue of case sensitivity. Jackson by default looks for property "getters" so you have "getFirstName". But, your JSON file is "firstname".
Try again with the case sensitivity the same between your source json file and your class.
Note the spelling of lastname(your POJO) vs lastName(the Json). Either change your pojo to firstName and lastName to match those of your JSON or set annotation above each field like below:
public class User {
#JsonProperty("firstname");
String firstName;
#JsonProperty("lastname");
String lastName;
This is because of the property mismatch. You can try this
public class User {
#JsonProperty("firstname")
String firstName;
#JsonProperty("lastname")
String lastName;
}
#JsonProperty is used to indicate external property name, name used in data format (JSON or one of other supported data formats)
I had the need to convert java objects to Map<String, String> for a REST api wrapper I am writing. Any fields that were complex objects needed to be serialized to json. I figured out how to do that like this:
public static Map<String, String> toContentMap(Object object) throws JsonProcessingException {
Map<String, Object> objectParamMap = MAPPER.convertValue(object, new TypeReference<Map<String, Object>>() {});
Map<String, String> contentMap = new HashMap<>();
for (Entry<String, Object> entry : objectParamMap.entrySet()) {
String key = entry.getKey();
String value = MAPPER.writeValueAsString(entry.getValue());
contentMap.put(key, StringUtils.strip(value, "\""));
}
return contentMap;
}
Now I need a way to get from this Map<String, String> representation back to the pojo object. Is this possible to do using mostly jackson apis?
Edit:
I guess I wasn't clear. I know the POJO I am going to/from. But it should be generic and work for any basic POJO.
An example:
class MyObject {
String fieldA;
Long fieldB;
MyOtherObject fieldC;
List<String> fieldD;
}
class MyOtherObject {
String fieldA;
String fieldB;
}
MyObject object = new MyObject("valueA", 20L,
new MyOtherObject("valueA", "valueB"),
Lists.newArrayList("array1", "array2"));
Map<String, String> contentMap = toContentMap(object);
/*
"fieldA" -> "valueA"
"fieldB" -> "20"
"fieldC" -> "{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"}"
"fieldD" -> "[\"array1\",\"array2\"]"
*/
MyObject newObject = fromContentMap(contentMap);
assertEquals(object, newObject)
Based on your comment
it should work for any POJO I ask it to
I think you are looking for the #JsonAnyGetter and #JsonAnySetter annotations, which will assign the values that it can to the POJO (which you need to specify at deserialization... it can't be generic), but will stick anything not parsed into a Map<String, Object>.
Note, it can't be Map<String, String> because JSON contains more than just Strings as its values.
For example, take the JSON
{
"uid": 1,
"username": "steve",
"email": "steve#example.com"
}
You can generate a Jackson POJO that looks like so.
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"uid",
"username",
"email"
})
public class User {
#JsonProperty("uid")
private Integer uid;
#JsonProperty("username")
private String username;
#JsonProperty("email")
private String email;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("uid")
public Integer getUid() {
return uid;
}
#JsonProperty("uid")
public void setUid(Integer uid) {
this.uid = uid;
}
#JsonProperty("username")
public String getUsername() {
return username;
}
#JsonProperty("username")
public void setUsername(String username) {
this.username = username;
}
#JsonProperty("email")
public String getEmail() {
return email;
}
#JsonProperty("email")
public void setEmail(String email) {
this.email = email;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
You can use
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{'name' : 'rahul'}";
//JSON from String to Object
User user = mapper.readValue(jsonInString, User.class);
or
User user = mapper.convertValue(map, User.class);
if you are converting from json/map to custom object. You can also pass type information to the serialization/deserialization process so that jackson is aware of the internals. Please read more about that here.
We can't do it I am afraid. When an object gets serialized into JSON String, it loses it's class type. So, while deserializing a string into object, parser doesn't know which class to deserialize the object into. However, there are a couple of ways to do it:
Store type info in another map:
Create a new map like this:
Map<String, Class> metaData = new HashMap<>();
It will store the type name along with Class. So, while derializing, we can lookup class type from this map and pass it to readValue method.
Use #Type annotation of ObjectMapper: (only useful if all the objects extend same base class)
Jakson has a feature called Polymorphic Deserialization which will help deserializing into object if all the classes inherit common base class. Have a look at the documentation here.