Binding JSON to Arraylist in Java Spring? - java

I want to be able to pass in data from a POST request and hit my API in which the JSON I pass through binds to the POTD class. Within POTD, I have an ArrayList of type POTDResources, which is where I am having trouble binding the data.
I'm eventually just going to use the entire Problem object, but for right now I am just testing with two fields within the class.
I need the JSON input to be able to bind to an ArrayList of type POTDResources
I'm passing in data like the following:
{
"problemTitle": "Balancing Binary Tree",
"resources": [{"name": "youtube", "link": "http://yotube.com"}]
}
The API call first hits:
Controller
#RequestMapping(value = "/generatepotd", method = RequestMethod.POST)
#ResponseBody
public void generatePOTD(#RequestBody POTD problem) throws IOException {
POTD prob = new POTD();
prob.setProblemTitle(problem.getProblemTitle());
prob.setResources(problem.getResources());
tempGen.generateProblemOfTheDay(prob);
}
POTD Class
package com.algoq.algoq.models;
import org.springframework.context.annotation.Description;
import java.util.ArrayList;
#Description("Handles the fields required for processing problem of the day")
public class POTD {
private String subject;
private String problemTitle;
private String problemDescription;
private ArrayList<POTDResources> resources;
// POTD(String subject, String problemTitle, String problemDescription, ArrayList<POTDResources> resources) {
POTD(String problemTitle, ArrayList<POTDResources> resources) {
// this.subject = subject;
this.problemTitle = problemTitle;
// this.problemDescription = problemDescription;
this.resources = resources;
}
public POTD() {
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getProblemTitle() {
return problemTitle;
}
public void setProblemTitle(String problemTitle) {
this.problemTitle = problemTitle;
}
public String getProblemDescription() {
return problemDescription;
}
public void setProblemDescription(String problemDescription) {
this.problemDescription = problemDescription;
}
public ArrayList<POTDResources> getResources() {
return resources;
}
public void setResources(ArrayList<POTDResources> resources) {
this.resources = resources;
}
}
POTD Resource Class
package com.algoq.algoq.models;
public class POTDResources {
private String name;
private String link;
public POTDResources(String name, String link) {
this.name = name;
this.link = link;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
Error Message
{
"timestamp": 1513192593064,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not construct instance of com.algoq.algoq.models.POTDResources: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.algoq.algoq.models.POTDResources: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)\n at [Source: java.io.PushbackInputStream#1710624f; line: 3, column: 17] (through reference chain: com.algoq.algoq.models.POTD[\"resources\"]->java.util.ArrayList[0])",
"path": "/generatepotd"
}

Your problems lies with the error message:
no suitable constructor found
You are overriding the default constructor, and the ObjectMapper can't create an instance of your Model class.
Try to add a default constructor for POTDResources:
public POTDResources() {
}
Jackson, which is used by Spring for JSON and XML processing, can work 2 ways with Object.
Constructor and Setter based.
If it can find a constructor with the same field names and types, it will try to use that one. If no suitable constructor, then it will try to create an instance of the object, and use setters. You didn't have a suitable constructor and it failed to create the instance.

Add empty constructor to your POTDResources class:
public POTDResources() {}
The reason being that JSON mapper would first try to initialize your class, and only then would apply values to it

Your problem is that POTDResources does not have a default constructor:
public POTDResources() {}
Jackson requires a no-arg constructor.

Related

Define generic parameter type of an object after a request

Summary
I'm trying to define the generic type of params based on the Type property when the request arrives at the controller ?
If the Type is UPDATE, set the generic type of params to MovieParam, if the type CREATE, set it to CarParam.
Request Json
{
"values":[
{
"context":{
"type":"UPDATE",
"ids":[1,2,3,4,5],
"params":{
"code":1256,
"year":588987,
"name":"Suits Mike Harvey Specter",
"channel":"NetFlix"
}
}
},
{
context":{
"type":"CREATE",
"ids":[1,2,3,4,5],
"params":{
"brand": "Chevrolett",
"engine": 2.0,
"segments": "Autos",
"year": "2014",
"name": "Celta"
}
}
}
]
}
Mappings in Java with Spring
public class Request {
List<Value> values;
}
public class Value {
Context context;
}
public class Context<T> {
String type;
List<Long> ids;
T params;
}
public class MovieParam {
Long code;
Long year;
String name;
String channel;
}
public class CarParam {
String brand;
Long engine;
String segments;
String name;
Long year;
}
My Controller
#PostMapping
private ResponseEntity<?> publish(#RequestBody Request request) {}
When my controller receives the payload shown above, the params property is of type linkedhashmap because it doesn't know the type of that object. I would like to transform this type to the corresponding shown above.
I would like to think of something simpler and more direct, sometimes an interceptor, or some implementation of a strategy based on Type

Quarkus Kafka Reactive - Deserializing SubClass (ERROR)

I am using Quarkus to receive messages from Kafka.
When I use the method with just one class, deserialization happens normally.
When my class is subclassed, I can't continue with the deserialization and an error occurs.
My Input in console kafka:
{"id":"73707ad2-0732-4592-b7e2-79b07c745e45","currentstep":"Debit-approval","payload": "{\"idCard\": 2,\"balance\": 456,\"pin\":222}","sagastatus": "STARTED","stepstatus": "{\"credit-approval\":\"STARTED\"}","type":"order-placement","version": 1}
My Method.
#Incoming("process-transaction-in")
public void process(TransactionModel transaction) throws InterruptedException { }
my deserialize class
import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer;
public class TransactionDeserializer extends ObjectMapperDeserializer<TransactionModel> {
public TransactionDeserializer() {
super(TransactionModel.class);
}
My class Model
public class TransactionModel {
public TransactionModel(String id,
String currentStep,
PayloadModel payload,
String sagaStatus,
String stepStatus,
String type,
String version) {
this.id = id;
this.currentStep = currentStep;
this.payload = payload;
this.sagaStatus = sagaStatus;
this.stepStatus = stepStatus;
this.type = type;
this.version = version;
}
public String id;
public String currentStep;
public PayloadModel payload;
public String sagaStatus;
public String stepStatus;
public String type;
public String version;
public TransactionModel() {
payload = new PayloadModel();
}
}
}
The Class PayloadModel
public class PayloadModel {
public PayloadModel(String idCard,
String current,
String pin)
{
this.idCard = idCard;
this.current = current;
this.pin = pin;
}
public String idCard;
public String current;
public String pin;
public PayloadModel() {}
}
Error:
SRMSG18249: Unable to recover from the deserialization failure (topic: process-transaction), configure a DeserializationFailureHandler to recover from errors.: java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of payment.model.PayloadModel (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"idCard": 2,"balance": 456,"pin":222}')
I followed the following tutorial: https://quarkus.io/guides/kafka#kafka-serialization
Has anyone experienced this problem?
As you can see in the error log Jackson finds at least one constructor for PayloadModel class but it's not the one it's expecting since the "payload" parameter in your Kafka payload is a string and not a JSON Object. Try to change the way you serialize your data so that payload is serialized as an object.
Sorry for posting this as a response I don't have enough reputation to comment.

How to map various possible types of a JSON field to a POJO attribute?

I have a POJO which is used for mapping the values received from rabbitmq. The messages are sent from other third-party services which I don't have control over. The problem is that these services don't send the certain fields in a consistent manner.
For example, in the case of an error field, this field will only have information when the third-party service wants to send a message to inform that there is an error. Some services may send in their JSON message to the queue this way:
{
id: 123,
name: "PersonA",
error: null
}
And then there are also services that may send their JSON message this way:
{
id: 123,
name: "PersonA",
error: []
}
And there are also services that may just omit the field entirely:
{
id: 123,
name: "PersonA"
}
Of course, if there is an error, that error field will contain fields within it:
{
id: 123,
name: "PersonA",
error: {
message: "Cannot find PersonA"
}
}
So, there is a chance that the error field can be null, undefined, array or an object.
The problem is, I can only define a single type in my POJO for the error field.
public class MyMessage {
private int id;
private MessageError error;
private String name;
#JsonCreator
public MyMessage(
#JsonProperty("id") int id,
#JsonProperty("error") MessageError error, // error is a MessageError object already but what is sent in could be null, undefined, an array or an object
#JsonProperty("name") String name
) {
this.id = id;
this.error = error;
this.name = name;
}
}
public class MessageError {
private String message;
#JsonCreator
public MessageError(
#JsonProperty("message") String message
) {
this.message = message;
}
}
I'm using Spring with Jackson.
In this case, how can I handle and map all the possible values that maybe assigned to the error field in the message to the POJO when it gets deserialised?
If you don't have any markers in the json data to identify its source and thus its shape, I would try to set error field type to object, and if it doesn't work or fit, to "json raw" as explained in answers of How can I include raw JSON in an object using Jackson?
So you can proceed the error field in the appropriate way according to the content you would discover at runtime, peeking from expected ones you detailed above.
The simplest way to resolve this problem is to define error field as a common Object type
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"id",
"name",
"error"
})
public class MyMessage {
#JsonProperty("id")
private int id;
#JsonProperty("name")
private String name;
#JsonProperty("error")
private Object error;
#JsonProperty("id")
public int getId() {
return id;
}
#JsonProperty("id")
public void setId(int id) {
this.id = id;
}
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
#JsonProperty("error")
public Object getError() {
return error;
}
#JsonProperty("error")
public void setError(Object error) {
this.error = error;
}
}
And then you have to map it to another DTO:
// someplace where you will map it
MyMessage message = source.getMessageFromQueue();
if(message.getError() == null) {
// do something
} else {
Object error = message.getError();
handleError(error);
}
private void handleError(Object error) {
if(error instanceOf MessageError) {
handleMessageError((MessageError) error) // handle as known MessageError object
} else if(error instanceOf String) {
handleStringError((String) error) // handle as String, write in logs maybe
} else if(error instanceOf List) {
handleErrors((String) error) // handle as List errors
} else {
handleUndefinedError()
}
}

Parse Json to object containing objects

I have the following code with json, that I got from accuweather
{
"Headline":{
"EffectiveDate":"2019-07-29T07:00:00+06:00",
"EffectiveEpochDate":1564362000,
"Severity":3,
"Text":"The heat wave will continue through Friday",
"Category":"heat",
"EndDate":"2019-08-02T19:00:00+06:00",
"EndEpochDate":1564750800
},
"DailyForecasts":[
{
"Date":"2019-07-29T07:00:00+06:00",
"EpochDate":1564362000,
"Temperature":{
"Minimum":{
"Value":19.1,
"Unit":"C",
"UnitType":17
},
"Maximum":{
"Value":36.7,
"Unit":"C",
"UnitType":17
}
},
"Day":{
"Icon":30,
"IconPhrase":"Hot",
"HasPrecipitation":false
},
"Night":{
"Icon":35,
"IconPhrase":"Partly cloudy",
"HasPrecipitation":false
},
"Sources":[
"AccuWeather"
]
}
]
}
I try to parse this object to the POJO via jackson
public static void main( String[] args )
{
String x = "{\"Headline\":{\"EffectiveDate\":\"2019-07-29T07:00:00+06:00\",\"EffectiveEpochDate\":1564362000,\"Severity\":3,\"Text\":\"The heat wave will continue through Friday\",\"Category\":\"heat\",\"EndDate\":\"2019-08-02T19:00:00+06:00\",\"EndEpochDate\":1564750800},\"DailyForecasts\":[{\"Date\":\"2019-07-29T07:00:00+06:00\",\"EpochDate\":1564362000,\"Temperature\":{\"Minimum\":{\"Value\":19.1,\"Unit\":\"C\",\"UnitType\":17},\"Maximum\":{\"Value\":36.7,\"Unit\":\"C\",\"UnitType\":17}},\"Day\":{\"Icon\":30,\"IconPhrase\":\"Hot\",\"HasPrecipitation\":false},\"Night\":{\"Icon\":35,\"IconPhrase\":\"Partly cloudy\",\"HasPrecipitation\":false},\"Sources\":[\"AccuWeather\"]}]}";
ObjectMapper objectMapper = new ObjectMapper();
try {
Weather weather = objectMapper.readValue(x, Weather.class);
System.out.println(weather);
} catch (IOException e) {
e.printStackTrace();
}
}
I have all the models specified in the json like Headline, array of DailyForecasts, Temperature that consists of TemperatureItems(named minimum and maximum as in json) and etc, all of them have private fields and public constructor, getters and setters. However I do not have some of the fields as I want to omit them(Day, Night, EpochDate, Source).
When I run the program I get the error
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of test.models.weather.Weather (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I have also tried Gson but it return object with Null values
Am I doing something wrong? Is there another way to do it?
Edit: These are the models, #LazerBass was right, as I firstly didn't include default constructors, now the error has changed:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Headline" (class test.models.weather.Weather), not marked as ignorable (2 known properties: "headline", "dailyForecasts"])
public class TemperatureItem {
public double value;
public String unit;
public String unitType;
public TemperatureItem() {
}
//Getters and setters
}
public class Temperature {
private TemperatureItem maximum;
private TemperatureItem minimum;
public Temperature(TemperatureItem maximum, TemperatureItem minimum) {
this.maximum = maximum;
this.minimum = minimum;
}
public Temperature() {
}
//Getters and setters
}
public class DailyForecasts {
private LocalDateTime date;
private Temperature temperature;
public DailyForecasts(LocalDateTime date, Temperature temperature) {
this.date = date;
this.temperature = temperature;
}
public DailyForecasts() {
}
//Getters and setters
}
public class Headline {
private LocalDateTime effectiveDate;
private int severity;
private String text;
private String category;
private LocalDateTime endDate;
public Headline() {
}
public Headline(LocalDateTime effectiveDate, Integer severity, String text, String category, LocalDateTime endDate) {
this.effectiveDate = effectiveDate;
this.severity = severity;
this.text = text;
this.category = category;
this.endDate = endDate;
}
//Getters and setters
}
public class Weather {
private Headline headline;
private DailyForecasts[] dailyForecasts;
public Weather() {
}
public Weather(Headline headline, DailyForecasts[] dailyForecasts) {
this.headline = headline;
this.dailyForecasts = dailyForecasts;
}
//Getters and setters
}
I have found out, that if I convert json string to lowercase, I can get some values, although Array and LocalDateTime weren't parsed
To generate the Weather classes and its corresponding classes, use the following link and select the source type as json. It will generate the required classes as per the json string.
http://www.jsonschema2pojo.org/
After generating the classes, you can annotate the fields with #JsonIgnore which are not required.
When Jackson fails with message like "no Creators, like default construct, exist" it needs default, public no-argument constructor for each POJO class you have in your model.
When it fails with message like "Unrecognized field ... not marked as ignorable ..." you need to disable FAIL_ON_UNKNOWN_PROPERTIES feature.
See also:
Jackson Unmarshalling JSON with Unknown Properties
jackson delay deserializing field
Judging from the exception message I would guess that your Weather class is laking an no-argument constructor. Try adding one. E.g.
public class Weather {
public Weather() {
// no arg constructor needed for jackson
}
}

JSON.decodeValue Decode Exception

I want to make this java code works:
RequestManager rm = Json.decodeValue(request.getBodyAsString(), RequestManager.class);
But i have this error:
io.vertx.core.json.DecodeException: Failed to decode:No suitable constructor found for type [simple type, class RequestManager]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: {"messageId":"fsdfsdf"}; line: 1, column: 2]
And here the code of my class :
public class RequestManager {
private String messageId;
private String messageContent;
public RequestManager(String messageId, String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent;
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
}
I really don't know why it's not working and there is only few topics about it, but they were irrelevant.
Someone can help ?
EDIT--
I know have the RequestManager class like this:
public class RequestManager {
private String messageId;
private String messageContent;
public RequestManager(String messageId, String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent + "check";
}
public RequestManager() {
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
But know when i try to print the fields of my RequestManager object created with the JSON.decodeValue it's return me null. I've already done that in the past and had the same error. I think it's because the empty constructor is used instead.
I still don't really understand....
EDIT--2
I have tried to change my class again, here it is:
#JsonIgnoreProperties(ignoreUnknown=true)
public class RequestManager {
#JsonProperty("messageId") private String messageId;
#JsonProperty("messageContent") private String messageContent;
#JsonCreator
public RequestManager(#JsonProperty("messageId") String messageId, #JsonProperty("messageContent") String messageContent) {
this.messageId = messageId;
this.messageContent = messageContent;
System.out.println("This constructor is used.");
}
public RequestManager() {
}
public String getMessageId() {
return messageId;
}
public String getMessageContent() {
return messageContent;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
And this is in my main :
final RequestManager rm = Json.decodeValue("{\"messageId\":\"themessage\"}", RequestManager.class);
System.out.println(rm.getMessageContent());
"{\"messageId\":\"themessage\"}" = the JSON format, i'm sure of it because decodeValue would return a Decode Exception if it wasn't.
Now the field is "nullcheck" when i print it. So it means that the constructor is well used but the fields are nulls. Where am i doint it wrong ?
You could try to have an empty constructor.
It's because you have your own constructor, and JSON doesn't know what values should be passed into it.
There is documentation on their GitHub page explaining how to set up a data object that you expect to be given to you as JSON and converted to Java.
https://github.com/vert-x3/vertx-codegen#data-objects
As per the example that you linked to: http://vertx.io/blog/some-rest-with-vert-x/, notice how they explicitly provide a constructor that takes no arguments, and public setter methods
Whisky()
setName(String name)
setOrigin(String origin)
The alternative is to provide annotations: https://github.com/FasterXML/jackson-annotations. You can choose how to do it, using annotation if you want, or using a bean class (getters and setters). Annotation has the advantage that you can say things like "ignore this value when you convert to JSON", etc. You can be more explicit with annotation. I would recommend picking one and staying with it. Consistency becomes important as your projects grow.

Categories