json parsing with resttemplate - java

I have a json response as below
{
"#odata.context": "some context value here",
"value": [{
"#odata.id": "odata id value1",
"#odata.etag": "W/\"CQEet/1EgOuA\"",
"Id": "id1",
"Subject": "subject1"
}, {
"#odata.id": "odata id value2",
"#odata.etag": "W/\"CyEet/1EgOEk1t/\"",
"Id": "id2",
"Subject": "subject2"
}]
}
How do I create a bean class(MyMessage) to parse the "value" using spring resttemplate?
RestTemplate rest = new RestTemplate();
ResponseEntity<MyMessage> response = rest.exchange(url, HttpMethod.GET, entity, MyMessage.class);
Could someone please help?

Annotate bean properties with #JsonProperty to set JSON field name for the property if it is different.
See:
JsonProperty annotation and When is the #JsonProperty property used and what is it used for?
Example (bean properties are public for example simplicity):
MyMessage class:
public class MyMessage {
#JsonProperty("#odata.context")
public String context;
#JsonProperty("value")
public Value[] values;
}
Value class:
// PascalCaseStrategy is to resolve Id and Subject properties
#JsonNaming(PascalCaseStrategy.class)
public class Value {
#JsonProperty("#odata.id")
public String odataId;
#JsonProperty("#odata.etag")
public String odataEtag;
public String id;
public String subject;
}

Related

How to deserialize JSON via JsonTypeInfo with unknown property

I need to deserialize JSON looks like the following:
{
"data": [{
"id": "id1",
"type": "type1"
"name": "John",
...
},
{
"id": "id2",
"type": "type2",
"name": "Rebecca",
...
},
{
"id": "id3",
"type": "unknown",
"name": "Peter",
...
}]
}
For deserializing JSON which I have written above I have created a couple of classes:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type",
defaultImpl = DefaultData.class
)
#JsonSubTypes({
#JsonSubTypes.Type(value = Type1Data.class, name = "type1"),
#JsonSubTypes.Type(value = Type2Data.class, name = "type2")
})
public class AbstractData {
public final String id;
public final String type;
public final String name;
public AbstractData(String id, String type, String name) {
this.id = id;
this.type = type;
this.name = name;
}
}
public class Type1Data extends AbstractData {
#JsonCreator
public Type1Data(#JsonProperty("id") String id,
#JsonProperty("name") String name
) {
super(id, "type1", name);
}
}
public class DefaultData extends AbstractData {
#JsonCreator
public DefaultData(#JsonProperty("id") String id,
#JsonProperty("type") String type,
#JsonProperty("name") String name
) {
super(id, type, name);
}
}
public class Main {
public static void main(String... args) {
ObjectMapper mapper = new ObjectMapper();
AbstractData data = mapper.readValue(json, AbstractData.class);
}
}
I get an exception if I use default implementation:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'unknown' as a type
The class DefaultData I need to avoid a deserialization exception if I will get the unknown type.
How can I fix this issue?
Summary
Right now it is not clear what is the exact root cause of the problem, because your example works for me with several corrections.
Still, please, consider the corrections as a draft.
Corrections
Data class for root object: Introduced
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
import java.util.StringJoiner;
public class RootData {
public final AbstractData[] data;
#JsonCreator
public RootData(#JsonProperty("data") final AbstractData[] data) {
this.data = data;
}
#Override
public String toString() {
return new StringJoiner(", ", RootData.class.getSimpleName() + "[", "]")
.add("data=" + Arrays.toString(data))
.toString();
}
}
AbstractData data class: Updated to deserialize type property
Please, see the Javadoc:
Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.
Updated annotation by adding visible = true:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type",
visible = true,
defaultImpl = DefaultData.class
)
Additionally, please, see the related question: java - Jackson - #JsonTypeInfo property is being mapped as null?.
Data classes: Implemented toString() method
(Omitted.)
Main: Updated to use root data class
Please, note that I have corrected the JSON document: added the missing comma after "type": "type1".
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String... args) throws JsonProcessingException {
final String jsonDocumentString =
"""
{
"data": [{
"id": "id1",
"type": "type1",
"name": "John"
},
{
"id": "id2",
"type": "type2",
"name": "Rebecca"
},
{
"id": "id3",
"type": "unknown",
"name": "Peter"
}]
}
""";
final ObjectMapper mapper = new ObjectMapper();
final RootData rootData = mapper.readValue(jsonDocumentString, RootData.class);
System.out.println(rootData);
}
}
The program completes successfully, i.e. without an exception being thrown.
The program outputs:
RootData[data=[Type1Data[id='id1', type='type1', name='John'], Type2Data[id='id2', type='type2', name='Rebecca'], AbstractData[id='id3', type='unknown', name='Peter']]]
The actual result (output) is the same as the expected result.

Struggling to Map JSON payload to model class

I am trying to map my incoming json payload to an arraylist of my model class.
I have a solution but its unintuitive.
I try to do this but get compilation errors-
ObjectMapper mapper = new ObjectMapper();
ArrayList<ModelClass> = mapper.readValue(items, RoleAttribute.class);
FYI I am trying to save this data in a Mongo collection.
Controller-
#PostMapping(value="/resource", consumes="application/json")
public Iterable<ModeClass> createResources(#RequestBody JSONObject requestBody ) throws JsonParseException, JsonMappingException, IOException {
System.out.println(requestBody.getClass());
return serviceImpl.saveResources(requestBody);
}
Model class-
#Data
#AllArgsConstructor
#Document(collection="collection-name")
public
class ModelClass{
#Field
private String ID;
#Field
private String description;
}
The payload is coming in the following format-
{
"data": [
{
"ID": "1",
"description": "desc1"
},
{
"ID": "2",
"description": "desc2"
},
{
"ID": "3",
"description": "desc3"
},
{
"ID": "4",
"description": "desc4"
}
....
]
}
I know I should be using jackson but I can't seem to figure this out. Do I need to change my POJO? Do I need to create custom Jackson config?
You can do it with json annotation. I also notice that your values are represented as data in json so that also needs to be taken care of. Look at below code. That will solve your problem.
import com.fasterxml.jackson.annotation.JsonProperty;
#Data
#AllArgsConstructor
#Document(collection="collection-name")
public class ModelClass{
#Field
#JsonProperty("ID")
private String classID;
#Field
#JsonProperty("description")
private String classDescription;
public String getClassID() {
return classID;
}
public void setClassID(String classID) {
this.classID = classID;
}
public String getClassDescription() {
return classDescription;
}
public void setClassDescription(String classDescription) {
this.classDescription = classDescription;
}
}
And wrapper Data class as below
class Data {
ModelClass[] data;
public ModelClass[] getData() {
return data;
}
public void setData(ModelClass[] data) {
this.data = data;
}
}
And json conversion code as below
ObjectMapper mapper = new ObjectMapper();
// json is your incoming json as a string. You can put inputstream also
Data values = mapper.readValue(json, Data.class);
System.out.println(values.getData().length);
System.out.println(values.getData()[0].getClassID());
You would need a container class for the data field, something like:
#Data
#Document(collection="collection-name")
public class DataClass{
private List<ModelClass> data;
}
Doing it via Jackson should be automatic this way, in controller:
public Iterable<ModeClass> createResources(#RequestBody DataClass requestBody ) {

Map Complex Json to Pojo Class

I am sending the following request (using Spring Boot)
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, request, String.class);
response is the json object(i have ommitted lot of fields in json object)
{
"customer": {
"id": 100,
"ci": {
"lDb": "11",
"localId": "1"
},
"cusdata": {},
"rating": {
"id": 3140,
"dateTime": "2019-09-21 06:45:41.10",
"rawData": {
"seg": "XYZ",
"seg2": "XYZ",
"et": "XYZ_CORP",
"CountryCodes": [
"IN"
],
"doBusiness": "2017-09-20"
],
...
....
...
...
"status": "SUCCESS"
}
I need to map the below fields to a Pojo Class
1.localId
2.seg
3.seg2
4.status
How can i create the PojoClass such that those fields are mapped automatically
So basically how will my PojoClass should look like?
ResponseEntity<PojoClass> response = restTemplate.exchange(url, HttpMethod.GET, request, PojoClass.class);
I suggest that you use sites like http://www.jsonschema2pojo.org/. There, you can select many options on the right panel and adjust POJO you want to get from JSON schema.
Your PojoClass has to follow the structure of the JSON that your are receiving and have the fields that your are interested (or all of them).
For the first level class:
public class PojoClass {
private Customer customer;
private String status;
...
}
Then, create a Customer class for the customer fields and create more classes for the rest of the fields:
public class Customer {
public String id;
public CI ci;
public CustData custData;
...
}
Create a custom class PojoClass
public class PojoClass {
private Integer id;
private Object ci;
private Object cusdata;
private Object rating;
private Object status;
}
ResponseEntity<PojoClass> responseEntity = restTemplate.exchange(url,HttpMethod.GET,request,new ParameterizedTypeReference<PojoClass>(){
});

Mapping Json into nested POJO

I've the following JSON from some upstream api
{
"Id": "",
"Name": "",
"Age": ""
}
And I need to map this above json to a downstream request paylaod (POJO) .
public class Employee
{
#JsonProperty("Id")
private Integer Id;
private User user;
}
public class User {
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Right now I'm doing something like
Employee employee = new ObjectMapper().treeToValue(JsonNode node,Employee.class);
But this is giving null in User Object.
The challenge here is , that the json we are getting from upstream can't be changed . So , is there is any way to map the fields into the nested User object , without changing the structure of json received from upstream.
One Solution is : map the fields separately into User object and then set it into the Employee object . But that's not an efficient solution , because for null validations we would need to do validations separately for User and Employee objects. If the nesting is complex then , validation will be hell of replicated code .
Your JSON does not comply with your Employee class.
Because name and age is at the same level as id, but you want to wrapped in a class User.
So either:
Change the json the structure to
{
"id": "",
"user": {
"name": "",
"age": ""
}
}
Or
Unwrap the User class, the Employee class will be:
public class Employee
{
#JsonProperty("Id")
private Integer Id;
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Edit
If you can't choose either option 1 or 2, you have only one option left is to create custom deserializer:
Write a deserializer:
public class EmployeeDeserializer extends StdDeserializer<Item> {
public EmployeeDeserializer() {
this(null);
}
public EmployeeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Employee deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("Id")).numberValue();
String name = node.get("Name").asText();
String age = node.get("Age")).asText();
User user = new User(name, age);
return new Employee(id, user);
}
}
Then register this deserializer:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Employee.class, new EmployeeDeserializer());
mapper.registerModule(module);
Employee readValue = mapper.readValue(json, Employee.class);
Another way to register deserializer is:
#JsonDeserialize(using = EmployeeDeserializer.class)
public class Employee {
It seems you are not nesting your JSON correctly. Or your Object Structure is wrong.
JSON should be:
{
"Id": "",
"user" : {
"Name": "",
"Age": ""
}
}
The json structure does not match the structure of your classes.
if the json was like;
{
"Id": "an-id,
"user": {
"Name": "Joe",
"Age": "21"
}
}
Then your code to deserialise to an Employee object would work.

How to bind map of custom objects using #RequestBody from JSON

I need to send map of custom objects Map<String, Set<Result>> from frontend to backend.
So I think it should be possible to build JSON, send it to Controller via Ajax and receive it in Controller via #RequestBody annotation which should bind json to object. right?
Controller:
#RequestMapping(value = "/downloadReport", method = RequestMethod.POST)
public ResponseEntity<byte[]> getReport(#RequestBody Map<String, Set<Result>> resultMap)
{
Context context = new Context();
context.setVariable("resultMap", resultMap);
return createPDF("pdf-report", context);
}
JSON:
{
"result": [
{
"id": 1,
"item": {
"id": 3850,
"name": "iti"
},
"severity": "low",
"code": "A-M-01",
"row": 1,
"column": 1,
"description": "Miscellaneous warning"
}
]
}
Model:
public class Result {
private Integer id;
private Item item;
private String severity;
private String code;
private Integer row;
private Integer column;
private String description;
//getter & setters
//hashCode & equals
}
public class Item {
private Integer id;
private String name;
//getter & setters
}
After send such a JSON like above by ajax I am getting error message from browser:
The request sent by the client was syntactically incorrect
If I change JSON to send empty set like below then it works but of course my map has empty set:
{"result": []}
So, Why I am not able to receive filled map with set of objects? Why binding/unmarshalling do not work as expected and what I should do to make it works?
Note:
I am using Jackson library and marshalling for other case for #ResponseBody works fine. Problem is with unmarshalling and binding object via #RequestBody.
In order for jackson to properly deserialize your custom classes you need to provide #JsonCreator annotated constructor that follows one of the rules defined in the java doc. So for your Item class it could look like this:
#JsonCreator
public Item(#JsonProperty("id") Integer id,
#JsonProperty("name") String name) {
this.id = id;
this.name = name;
}
you have to deal with map differently,
first create wrapper class
public MyWrapperClass implements Serializable {
private static final long serialVersionUID = 1L;
Map<String, List<String>> fil = new HashMap<String, List<String>>();
// getters and setters
}
then you should take request in controller,
#PostMapping
public Map<String,List<String>> get(#RequestBody Filter filter){
System.out.println(filter);
}
Json Request should be like
{
"fil":{
"key":[
"value1",
"value2"
],
"key":[
"vakue1"
]
}
}

Categories