There is already data present in S3 Bucket which needs to be processed.
Data Follow this particular Schema
{
"serviceId": "9e148371a96b9054e689d3669998ef54ff11cdd3cad9dfa395e4da4c5a50cbc4",
"serviceVersion": 2
}
POJO Classes
abstract class Service {
String serviceId;
int serviceVersion;
}
ServiceA extends Service {
String fieldX;
}
ServiceB extends Service {
}
Code
USing Gson to desrialize this data
Gson gson = new Gson();
String data = "{\n" +
" \"serviceId\": \"9e148371a96b9054e689d3669998ef54ff11cdd3cad9dfa395e4da4c5a50cbc4\",\n" +
" \"serviceVersion\": 2\n" +
"}";
Code fails with exception
Failed to invoke public com.*.transportation.*.Service()() with no args
How do i deserialize this data ?
Registering CustomType Adapters won't help either, since Data is already deserialized, and no Type information is available.
Only solution i can think of here, is to create customerTypeAdapter and decide the Type of Concrete class on the basis of
#Override
public Service deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (Object.nonNull(jsonObject.get("fieldX") {
return jsonDeserializationContext.deserialize(jsonElement,ServiceA.class);
}
jsonDeserializationContext.deserialize(jsonElement,ServiceB.class);
}
Please suggest some the alternative, if this can be solved in a better way?
I have provided this as an example, but the actual object is very large and consists of too many nested interface/abstract classes.
Data is already present in S3/Local Storage and no _type information is present.
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;
I am not able to unmarshall a JSON key which can hold either a string value or an another JSON Object using Jackson Library.
Ex:- Below are the two possible values.
1)
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
2)
"ProviderData": "1C"
Could someone please verify and suggest me on this issue.
You can write custom deserialiser and handle these both cases or write two constructors for ProviderData POJO class and properly use JsonCreator and JsonCreator annotations. See below example:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Response.class));
}
}
class Response {
#JsonProperty("ProviderData")
private ProviderData data;
// getters, setters, toString
}
class ProviderData {
private static final String INVALID_NAME = "INVALID";
private static final String TEXT_NAME = "#text";
#JsonProperty(INVALID_NAME)
private final String invalid;
#JsonProperty(TEXT_NAME)
private final String text;
#JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public ProviderData(String invalid) {
this(invalid, null);
}
#JsonCreator
public ProviderData(#JsonProperty(INVALID_NAME) String invalid, #JsonProperty(TEXT_NAME) String text) {
this.invalid = invalid;
this.text = text;
}
// getters, toString
}
For this JSON payload:
{
"ProviderData": {
"INVALID": "HEX",
"#text": "Sample"
}
}
Above example prints:
Response{data=ProviderData{invalid='HEX', text='Sample'}}
And for String primitive JSON payload:
{
"ProviderData": "1C"
}
Above example prints:
Response{data=ProviderData{invalid='1C', text='null'}}
As you can see, JSON Object is mapped properly using 2-arg constructor and String primitive is mapped using 1-arg constructor and we assume that this value means invalid key from JSON Object example.
See also:
Custom JSON Deserialization with Jackson.
sequentially deserialize using Jackson.
Deserialize strings and objects using jackson annotations in java.
you could deserialize to JsonNode and then extract the contents individually, or deserialize to an Object and use instanceof to determine if it's a Map or another type, or use a custom deserializer to unpack the data into a custom object that handles both cases.
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.
I was looking for some solution around here and I didnt find any correct answer to my question so I would like to ask you.
I have POJO with some simple attribs. and one List of another POJOs.
public class Standard implements Serializable {
private String id;
private String title;
private String description;
private Set<Interpretation> interpretations = new LinkedHashSet<Interpretation>();
}
public class Interpretation implements Serializable {
private String id;
private String title;
private String description;
}
In my controller class, I am returning Standard POJO with GSON.
#RequestMapping(value="/fillStandard", method= RequestMethod.GET)
public #ResponseBody String getStandard(#RequestParam String id) {
Standard s = DAOFactory.getInstance().getStandardDAO().findById(id);
return new Gson().toJson(s);
}
The question is, am I able to get the list of interpretations in my Standard POJO using jQuery ? Something like :
function newStandard() {
$.get("standard/fillStandard.htm", {id:"fe86742b2024"}, function(data) {
alert(data.interpretations[0].title);
});
}
Thanks a lot !
EDIT:
Well, thanks to #Atticus, there is solution of my problem. Hope that it will help somebody.
#RequestMapping(value="/fillStandard", method= RequestMethod.GET, produces="application/json")
public #ResponseBody Standard getStandard(#RequestParam String id) {
Standard s = DAOFactory.getInstance().getStandardDAO().findById(id);
return s;
}
Using #ResponseBody allows you to return the whole POJO, but you need to add produces="application/json" to your #RequestMapping annotation. Then you will be able to catch a returning object as JSON in jQuery like as I supposed.
function newStandard() {
$.get("standard/fillStandard.htm", {id:"idOfStandard"}, function(data) {
alert(data.id); //Standard id
alert(data.interpretations[0].title); //id of Interpretation on first place in array
});
Well you have to create and register your custom serializer.
It goes like this:
//You create your builder that registers your custom serializer with the class you want to serialize
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Standard.class, new StandardSerializer());
//Then you create your Gson object
Gson gson = builder.create();
//Then you pass your object to the gson like
Standard s = DAOFactory.getInstance().getStandardDAO().findById(id);
gson.toJson(s);
Your serializer looks like this:
public class StandardSerializer implements JsonSerializer<Standard>{
#Override
public JsonElement serialize(Standard src, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
//You put your simple objects in like this
obj.add("id",new JsonPrimitive(src.getId()));
//You put your complex objects in like this
JsonObject interpretations = new JsonObject();
//Here you need to parse your LinkedHashSet object and set up the values.
//For the sake of simplicity I just access the properties (even though I know this would not compile)
interpretations.add("title", src.getInterpretation().getTitle());
obj.add("interpretations", interpretations);
return obj;
}
}
In this case your Json would look something like:
{"id":"idValue", "title":"titleValue", "description":"descriptionValue", "interpretations":["id":"interpretationIdValue"]}
Now, you can access your data with jQuery like this:
function newStandard() {
$.get("standard/fillStandard.htm", {id:"fe86742b2024"}, function(data) {
alert(data.interpretations.title);
});
}
I hope this helps.
EDIT:
I see that your response gets converted to the declared method argument type which is String (as stated here: 16.3.3.2 Supported method return types). But what you really want is your Standrad POJO converted to JSON. I am not very familiar with Spring but as I have read here (16.3.2.6 Producible Media Types) there is another, maybe easier solution. If you want to return a JSON object, then change the return type of the
getStandard method to Standard instead of String and add produces="application/json" to your #RequestMapping annotation. As far as I have read this should tell Spring that the return type should be converted to JSON. In this case you do not need to use Gson.