Converting a JSON with array of objects to proper Java object - java

I have a simple Spring Boot project in which a scheduler periodically consumes a RESTful API and converts the incoming JSON file.
The JSON file is actually an array of Objects with some Keys and Values:
[
{"CoID":1,"CoName":"کشاورزی و دامپروری مگسال","CoNameEnglish":"MagsalAgriculture & Animal Husbandry Co.","CompanySymbol":"MAGS","CoTSESymbol":"زمگسا","GroupID":1,"GroupName":"كشاورزی و دامپروری","IndustryID":1,"IndustryName":"كشاورزی، دامپروری و خدمات وابسته به آن","InstCode":"5054819322815158","TseCIsinCode":"IRO1MAGS0006","TseSIsinCode":"IRO1MAGS0001","MarketID":1,"MarketName":"بورس"},
{"CoID":2,"CoName":"ذغالسنگ نگین طبس","CoNameEnglish":"Negin Tabas Lignite Co.","CompanySymbol":"TBAS","CoTSESymbol":"کطبس","GroupID":2,"GroupName":"استخراج و انبار ذغال سنگ سخت","IndustryID":2,"IndustryName":"استخراج ذغال سنگ","InstCode":"8977369674477111","TseCIsinCode":"IRO1TBAS0004","TseSIsinCode":"IRO1TBAS0001","MarketID":1,"MarketName":"بورس"},{"CoID":3,"CoName":"معدنی و صنعتی چادرملو","CoNameEnglish":"Chadormalu Mining & Industrial Co.","CompanySymbol":"CHML","CoTSESymbol":"کچاد","GroupID":3,"GroupName":"استخراج سنگ معدن های فلزی آهنی","IndustryID":3,"IndustryName":"استخراج كانه های فلزی","InstCode":"18027801615184692","TseCIsinCode":"IRO1CHML0000","TseSIsinCode":"IRO1CHML0001","MarketID":1,"MarketName":"بورس"}
...
]
I have a class called Company with similar fields to one of objects in the array within the JSON file:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Company {
private int CoID;
private String CoName;
private String CoNameEnglish;
private String CompanySymbl;
private String CoTSESymbl;
private int GroupID;
private String GroupName;
private int IndustryID;
private String IndustryName;
private String IndustryCode;
private String TseCIsinCode;
private String TseSIsinCode;
private int MarketID;
private String MarketName;
// And proper getters, setters and constructor //
I also created a wrapping class called CompanyList:
public class CompanyList {
private ArrayList<Company> companyList;
public ArrayList<Company> getCompanyList() {
return companyList;
}
public void setCompanyList(ArrayList<Company> companyList) {
this.companyList = companyList;
}
public CompanyList() {
}
#Override
public String toString() {
return "CompanyList [companyList=" + companyList + "]";
}
}
I have tried three different ways to fulfill this requirement:
First:
Object[] forNow = restTemplate.getForObject("somewhere", Object[].class);
List<Object> cp= Arrays.asList(forNow);
This one works properly.
Second:
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Company>> response = restTemplate.exchange(
"somewhere",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Company>>(){});
List<Company> companies = response.getBody();
log.info(companies.toString());
This one is compiled successfully but returns null and 0 in all fields.
Third:
CompanyList cp = restTemplate.getForObject("somewhere", CompanyList.class);
log.info(cp.getCompanyList().toString());
This one raises an exception:
Error while extracting response for type [class ir.pisys.rest.CompanyList] and content type [application/json;charset=utf-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of ir.pisys.rest.CompanyList out of START_ARRAY token;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of ir.pisys.rest.CompanyList out of START_ARRAY token
So I have some questions here:
1- Is the first approach an optimized one? (Compared to others)
2- How can I fix the two other approaches?

The second and third approaches should work fine.
You need to check your json response structure.
You could use following jsons for tests (they work with your code):
Second approach:
[{"tseCIsinCode":null,"tseSIsinCode":null,"coName":"n1","industryID":0,"coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryName":null,"industryCode":null,"marketID":0,"marketName":null},{"tseCIsinCode":null,"tseSIsinCode":null,"coName":"n2","industryID":0,"coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryName":null,"industryCode":null,"marketID":0,"marketName":null}]
Third:
{"companyList":[{"coName":"n1","coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryID":0,"industryName":null,"industryCode":null,"tseCIsinCode":null,"tseSIsinCode":null,"marketID":0,"marketName":null},{"coName":"n2","coID":0,"coNameEnglish":null,"companySymbl":null,"coTSESymbl":null,"groupID":0,"groupName":null,"industryID":0,"industryName":null,"industryCode":null,"tseCIsinCode":null,"tseSIsinCode":null,"marketID":0,"marketName":null}]}
Update:
Second approach fix:
Change your json fields name - "CoName" -> "coName", "CoID" -> "coID" and so on. After that changes it will work pirfectly.
Third approach fix:
Wrap your json with "{\"companyList\":[...]
And change fields name as for second approach
Second Update
If you can't change json from response. You could use mapping in your Company class
#JsonProperty("CoName")
private String CoName;

Related

Issue with serializing JSON from a rest call

Newbie developer here. I am trying to make a call to a public API. The API receives the name of a drink as a string and returns information and recipe for that name. The response from the API looks like this:
{
"drinks":[
{
"id": ...
"name": ...
"recipe": ...
"category": ...
"alcoholic": ...
... many other fields ...
},
{
...
}
...
]
}
I am only interested in name, recipe and category. I have a domain class for this purpose that looks like this
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class Drink {
#JsonProperty("name")
private String name;
#JsonProperty("category")
private String category;
#JsonProperty("recipe")
private String recipe;
}
I also implemented a client to call the endpoint using restTemplate. Here is the call that client makes:
ResponseEntity<List<Drink>> response = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Drink>>() {
});
My goal is to call the API, get the response and only the fields that I want and store it in a list of Drink. However when I try to run the app locally and make a call I am getting this error:
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.ArrayList<Drink>` from Object value (token `JsonToken.START_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.util.ArrayList<Drink>` from Object value (token `JsonToken.START_OBJECT`)
When I use ResponseEntity<String> instead, it works but returns the whole json as a string, which does not seem like a good approach. How can I get this approach to work?
The problem is mismatch between json structure and object structure. The object you deserialize into must represent correctly the json. It's an object with a field drinks, which is an array of objects(drinks in your case). Correct java class would be:
public class Wrapper {
private List<Drink> drinks;
//getters and setters
#Override
public String toString() {
return "Wrapper{" +
"drinks=" + drinks +
'}';
}
}
Other option would be to write custom deserializer, which can extract drinks from the tree before deserializing directly into a list.]
Edit: Added toString() override for debugging purposes.

How To Extract Data From JSON with Rest Template in Java

i have to extract the first 5 articles from https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21, having a result like this:
{
"total": 5,
"articles": [
{
"source": "Ilmessaggero.it",
"title": "Title",
"author": "Author",
"url": "URL"
}
]
}
I did this, having all the JSON as String as output for the localhost...
#RequestMapping("/news")
public Article connection() {
return restTemplate.getForObject
("https://newsapi.org/v2/top-headlines?sources=bbc-news&apiKey=19acc3a371d145ecb37a093f9985ea21", Article.class);
The result in the localhost is:
{"source":null,"title":null,"author":null,"url":null}
But the problem now is, how do i put the data into the list of articles?
and how do i save them into mongodb? thanks for the effort
I solved it! SImply, the NewsAPI json of Article has a field called Source, which i was trying to parse as a string, but it was NOT! Infact, it is a field described with another object! I simply had to create a class called Source with id and name, and it works! Thanks everyone for the effort!
Here's the codes of the classes:
public class Article {
private Source source;
private String author;
private String title;
private String url;
//getters and setters
News, which has a list of articles:
public class News {
private int totalResults;
private List<Article> articles;
//getters and setters
And source, which is called in Article:
public class Source {
private String id;
private String name;
//getters and setters
Here it is! The parse code is the same of the answer. Just change the return type (Article) as News and the Article.class parameter of getForObject into News.class
A simple (i.e. missing exception handling, etc) way is as follows:
First, you need a class to represent the data you are receiving, with fields that match the API response fields, for example:
public class Article {
private String source;
private String title;
... // more fields
// getters and setters
}
The code to fetch the data from the API then looks like this:
RestTemplate template = ... // initialized earlier
ResponseEntity<Article[]> response = template.exchange(
API_URL, // url to the api
HttpMethod.GET, // use the Http verb "GET"
new HttpEntity<>(headers), // optional headers, e.g. for basic auth
Article[].class // the expected response type is Article[]
);
Article[] articles = response.getBody();
List<Article> list = Arrays.asList(articles); // if you need to use collections
Note, a ResponseEntity being non-null does not imply that the request was successful. You can use responseEntity.getStatusCode() to determine the status code of the response.
Be careful, however, since by default, RestTemplate throws an exception when a non-200 error code is recieved (HttpClientErrorException and HttpServerErrorException for 4XX and 5XX codes respectively). If you want your own custom error handling, you should call:
template.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
// implement here
}
#Override
public void handleError(ClientHttpResponse response) throws IOException {
// implement here
}
});
For persistence into MongoDB, you can use JPA, although JPA is not a perfect fit for MongoDB due to its inherently relational nature clashing with Mongo's non-relational structure. Something like Spring Data can more sensibly map this, and is worth looking into: https://spring.io/projects/spring-data-mongodb
EDIT - calling this code
Typically, I will create an class/interface with implementation (called ArticleResource for example) that looks like:
public class ArticleResource {
private final RestTemplate template = new RestTemplate();
public List<Article> getAllArticles() {
ResponseEntity<Article[]> response = template.exchange(API_URL, HttpMethod.GET, new HttpEntity<>(headers), Article[].class);
// some error checking here
return response.getBody() == null ? Collections.emptyList() : Arrays.asList(response.getBody());
}
}
For methods that expect a single value (e.g. findArticleByTitle(String title)) I typically return an Optional<Article> (it is bad practice to return Optional<List<T>>, as an empty list represents "no values" already).
From there in your code you can call:
ArticleResource resource = new ArticeResource();
// if you want to print all the names for example:
resource.getAllArticles().stream().map(Article::getName).forEach(System.out::println);

HttpRequestHandlingMessagingGateway JSON array payload

I can't get an HTTP inbound adapter to convert a JSON array to a list of objects of type SendgridTxEvent, it always ends up with ArrayList<LinkedHashMap> instead of List<SendgridTxEvent>. The config:
public HttpRequestHandlingMessagingGateway sendgridMessageAdapter(#Qualifier("sendgridWebhookEvents") MessageChannel channel) {
HttpRequestHandlingMessagingGateway httpInboundChannelAdapter = new HttpRequestHandlingMessagingGateway(false);
RequestMapping mapping = new RequestMapping();
mapping.setMethods(HttpMethod.POST);
mapping.setPathPatterns("/webhook/sendgrid");
ParameterizedTypeReference<List<SendgridTxEvent>> ptr = new ParameterizedTypeReference<List<SendgridTxEvent>>() {
};
httpInboundChannelAdapter.setRequestMapping(mapping);
httpInboundChannelAdapter.setRequestChannel(channel);
httpInboundChannelAdapter.setRequestPayloadType(ResolvableType.forType(ptr));
return httpInboundChannelAdapter;
}
If I set the request payload type to httpInboundChannelAdapter.setRequestPayloadType(ResolvableType.forType(SendgridTxEvent.class)) and feed a JSON object to it (instead of an array), jackson deserializes SendgridTxEvent correctly, so the problem only occurs with an array input. Input examples can be found here.
How do I go about consuming JSON arrays in an HTTP inbound adapter?
SendgridTxEvent class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class SendgridTxEvent {
public enum Event {
PROCESSED,
DROPPED,
DELIVERED,
BOUNCE,
DEFERRED,
OPEN,
CLICK,
UNSUBSCRIBE,
SPAMREPORT;
#JsonCreator
public static Event forValue(String value) {
return Event.valueOf(value.toUpperCase());
}
}
private String email;
private Long timestamp;
private Event event;
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<String> category;
private String sgEventId;
private String sgMessageId;
//getters, setters
}
I've raised an issue to fix this in the Framework: https://github.com/spring-projects/spring-integration/issues/2806
Meanwhile as a workaround I would suggest to expect a payload in the HttpRequestHandlingMessagingGateway as String or byte[], then use a POJO #Transformer downstream with direct conversion via ObjectMapper and already your expected <List<SendgridTxEvent> type.
Another simple option that you can expect just SendgridTxEvent[].class and than convert it into the list downstream.

Convert multiple Java Beans to JSON

I have multiple Java bean classes that are associated to each other (JSON Array + JSON Object) since they have a nested structure.
There are about 10 classes. Is there a way to collectively convert these classes or at-least one by one?
I had created these classes out of a JSON data which I don't have access to right now.
So, now, what I'm looking forward is to create a dummy JSON out of those classes.
Using GSON, I tried converting one of these Bean classes however, I got an empty result. Here is one of the beans called Attachment.java.
Attachment.java
package mypackagename;
import java.io.Serializable;
public class Attachment implements Serializable{
private Payload payload;
private String type;
public Payload getPayload() {
return payload;
}
public void setPayload(Payload payload) {
this.payload = payload;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Implementation
Gson gson = new Gson();
Attachment attachment = new Attachment();
String json = gson.toJson(attachment);
Sure you got an empty result. Because your JSON object is empty. You should add data to your object and test it again as below:
Attachment attachment = new Attachment(new Payload("Test Payload"), "Test attachment");
String json = new Gson().toJson(attachment);
Log.e("Test", "Json: " + json); // result: Json: {"payload":{"test":"Test Payload"},"type":"Test attachment"}
To avoid empty object, you have to set a default value to your payload and type becaus Gson will ignore any null value.
This section of the Gson User Guide: https://sites.google.com/site/gson/gson-user-guide#TOC-Finer-Points-with-Objects
The fourth bullet point explains how null fields are handled.

Custom response on bad request using spring RestController

I have the following controller. I am using Spring to create Restful APIs.
#RestController
public class UserController extends RestControlValidator {
#RequestMapping(value = "/user/", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List newUser(#RequestBody #Valid UserInput input,BindingResult result)
{Some code}
}
The UserInput class looks like this:
public class UserInput{
#NotEmpty
private String emailId;
#NotEmpty
private String fName;
private String lName;
private int sex;
//getters and setters
Now when I try and access /user/ with data {"sex":"Male"}, I get the following response:
I want the response in case of such a request to be:
{"errors":{"sex":"The value must be an integer"}}
Is there any way of customising BAD REQUEST responses in Spring?
Considering the current scenario the most ideal solution would be to alter the behavior of HandlerMethodArgumentResolve as the json to pojo constructed by #RequestBody fails because we dont get a chance to check the wrong data and this check can very well be done in the custom message converter
A. first we would need to create LanguageMessageConverter as follows
public class LanguageMessageConverter extends
AbstractHttpMessageConverter<Language> {
private Gson gson = new Gson();
public LanguageMessageConverter() {
super(new MediaType("application", "json", Charset.forName("UTF-8")));
}
#Override
protected boolean supports(Class<?> clazz) {
return Language.class.equals(clazz);
}
Map<String, String> mp = new HashMap<>();
#Override
protected Language readInternal(Class<? extends Language> clazz,
HttpInputMessage httpInputMessage) throws IOException,
HttpMessageNotReadableException {
Map langmp = gson.fromJson(
convertStreamToString(httpInputMessage.getBody()), Map.class);
for (Field field : clazz.getDeclaredFields()) {
if (!langmp.get(field.getName()).getClass().getCanonicalName().equals(field.getType().getCanonicalName())) {
if (field.getType().getCanonicalName().equals("java.lang.Integer")||field.getType().getCanonicalName().toString().equals("int")) {
langmp.put(field.getName(), "0");
} else if (field.getType().equals("java.lang.String")) {
//TODO COde needs to be improved here because this check is not efficient
langmp.put(field.getName(), "wrong");
}
}
}
Language lang = gson.fromJson(gson.toJson(langmp), clazz);
return lang;
}
we need to set the media type new MediaType("application", "json", Charset.forName("UTF-8")) which will make sure this class intervenes the mentioned MIME type
Considering we need to manipulate the result I found it best to convert it to map langmp (There are better JSON Parsers which can be used)
Since we need to to understand the existing type I used reflection api to get the fields via getDeclaredFields()
Using the above made the logical check using the datatype to understand if the type is incorrect for eg if the field datatype is int and if it is found as String then corresponding map value will be substituted
once that is done the map will hold the updated values where in if the data was wrong a default value would be set eg if the int var is set to 0 since the originating json had a String in it.
Once that is done the updated map is converted to the concerned class.
B. Secondly we need to register the custom MessageConverter in the dispatcher xml i.e. LanguageMessageConverter
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class="com.comp.org.controller.LanguageMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
register-defaults="true" is very important since we are adding Custom MessageConverter but we also need the other existing converters working along with the one we have added
LanguageMessageConverter needs to be registered here.
C. Considering the concerned pojo is populated with the necessary details it would reach our controller post processing in the custom converter now we would add the manual validation eg. if the int variable has 0 the necessary error json should be returned
As per your request even if the json consists of the wrong data the custom message converter should process it and accordingly in the controller we can validate the condition mentioned.
The code definitely can be improved further. Kindly let me know if this solution fulfilled your requirement or any part of the code requires further elaboration and hopefully addressed your concern.
I had the same issue, than I solved that way:
Create an Object called Error, like that (don't forget to implement Serializable...):
private String fieldName;
private String errorCode;
private String defaultMessage;
public Error() {
}
public Error(String fieldName, String errorCode, String defaultMessage) {
this.fieldName = fieldName;
this.errorCode = errorCode;
this.defaultMessage = defaultMessage;
}
/* getters, setters */
Inside the #RestController method you ave to call inputValidator.validate() method (if you didn't create an Object Validator for your UserInput then we're really don't speaking the same language...)
// validating the userInput
userInputValidator.validate(userInput, bindingResult);
if (bindingResult.hasErrors()) {
List<Error> errors = new ArrayList<>(bindingResult.getErrorCount());
for (FieldError fieldWithError : bindingResult.getFieldErrors()) {
errors.add(new Error(fieldWithError.getField(), fieldWithError.getCode(), fieldWithError.getDefaultMessage()));
}
return errors;
}
// in case of success:
return null;
Finally you'll have to translate the JSON object to your client side. You'll have two kind of objects:
3.1. null (undefined depending on the language you're using)
3.2. A JSON object like that:
[
{
"fieldName": "name",
"errorCode": "user.input.name.in.blank",
"defaultMessage": "Insert a valid name!"
},
{
"fieldName": "firstPhone",
"errorCode": "user.input.first.phone.blank",
"defaultMessage": "Insert a valid first phone!"
}
]

Categories

Resources