How to model JSON with key value pairs? - java

This simple JSON is returned by https://httpbin.org/get which is handy for testing.
{
"args": {},
"headers": {
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "AemSConnector v1.0",
"X-Amzn-Trace-Id": "Root=1-606c333f-338353e14fc31e375617f4ba"
},
"origin": "81.40.159.142",
"url": "https://httpbin.org/get"
}
I'm trying to figure out how to build a Java class to model this.
I have tried:
public class ModelTest {
public String origin;
public String url;
public HashMap<String, String> headers;
public HashMap<String, String> args;
// getters and setters and default constructor here...
}
}
And also just this:
public class ModelTest {
public String origin;
public String url;
// getters and setters and default constructor here...
}
}
But when I try to convert the JSON string to this model, I just get a null point exception in the logs, no helpful info.
The code I am using is this:
// ModelTest model = null;
ModelTest model = new ModelTest();
model = (ModelTest) getObjectFromJson(reply, model);
}
:
public static Object getObjectFromJson(String jsonString, Object obj) {
Gson gson = new Gson();
Object returnValue = null;
try {
returnValue = gson.fromJson(jsonString, obj.getClass());
} catch (Exception e) {
log.error("Exception occured in Something :: getObjectFromJson --> ", e);
}
return returnValue;
}
exception:
2021-04-06 12:09:04.245 ERROR [com.adobe.aem.guides.wknd.core.util.MyConnector] Exception occured in Something :: getObjectFromJson -->
java.lang.NullPointerException: null
at com.adobe.aem.guides.wknd.core.util.SpineConnector.getObjectFromJson(MyConnector.java:77) [aem-guides-wknd.core:0.2.1.SNAPSHOT]
at com.adobe.aem.guides.wknd.core.util.SpineConnector.get(MyConnector.java:50) [aem-guides-wknd.core:0.2.1.SNAPSHOT]
at com.adobe.aem.guides.wknd.core.servlets.SpineServlet.doGet(MyServlet.java:64) [aem-guides-wknd.core:0.2.1.SNAPSHOT]

I found a solution. The Model was fine, it was the getObjectFromJson method which was causing the issues, even with a non-null object (as the commenters pointed out)
I scrapped it, and did the mapping in-line and it worked as expected:
Gson gson = new Gson();
model = gson.fromJson(reply, ModelTest.class);

Related

Get both Mapping and Pain Json text in Spring Boot Restful webService

i am trying to parse json body request coming in for a post request using Spring Boot. I would like to map the body to fields on vehicle class and also to store plain json body to some variable as well for future use. But i am always getting stream closed exception when trying to access plain json body. Can someone help me out on this. Thanks In Advance
Code
#RequestMapping(value = "/GetDriverDetails", method = RequestMethod.POST)
public ResponseEntity<Vehicle> GetVehicleDetails(#RequestBody Vehicle vehicle, HttpServletRequest request) {
System.out.println(vehicle);
String json;
if ("POST".equalsIgnoreCase(request.getMethod()))
{
try {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return new ResponseEntity<Vehicle>(HttpStatus.OK);
}
Json request Body
{
"vehicleName": "Brio",
"vehicleModel": "fisrtClass",
"drivers": [
{
"name": "rej",
"licenseNumber": "KLLicense1"
},
{
"name": "Dan",
"licenseNumber": "KLLicense2"
},
{
"name": "bala",
"licenseNumber": "KLLicense3"
},
{
"name": "vijay",
"licenseNumber": "KLLicense4"
},
{
"name": "aravind",
"licenseNumber": "KLLicense5"
},
{
"name": "sathya",
"licenseNumber": "KLLicense6"
}
]
}
Exception
java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.springframework.web.util.ContentCachingRequestWrapper$ContentCachingInputStream.read(ContentCachingRequestWrapper.java:254) ~[spring-web-5.2.9.RELEASE.jar:5.2.9.RELEASE]
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188) ~[na:na]
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:181) ~[na:na]
Can you try following code:
The solution to your main problem, since you are using #RequestBody, contents are already read and mapped to pojo class hence stream is utlized and closed in this case you do not want to use #RequestBody at all. Please find my implementation below:
#PostMapping(path = "update-vehicle-details", consumes = MediaType.ALL_VALUE)
public VehicleDriver updateVehicleDetails(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
final VehicleDriver vehicleDriver = new ObjectMapper().readValue(collect, VehicleDriver.class);
return vehicleDriver;
}
Otherwise, use a simple approach, read the value from application json content type parses in requestbody and converts that body to string and return the same result
#RestController
public static class TestController {
#PostMapping(path = "update-vehicle-details", consumes = MediaType.APPLICATION_JSON_VALUE)
public String updateVehicleDetails(#RequestBody VehicleDriver vehicleDriver) throws JsonProcessingException {
final StringBuilder stringBuilder = new StringBuilder(vehicleDriver.vehicleName);
List<String> driverDetails = Optional.ofNullable(
vehicleDriver.drivers)
.map(Collection::stream)
.orElse(Stream.empty())
.map(d -> "name=: " + d.name + ", license number:" + d.licenseNumber)
.collect(Collectors.toList());
stringBuilder.append("\n");
stringBuilder.append(driverDetails);
String stringRepresentationOfBody = new ObjectMapper().writeValueAsString(vehicleDriver);
// return stringBuilder.toString();
return stringRepresentationOfBody;
}
}
public static class VehicleDriver {
public String vehicleName;
public String vehicleModel;
public List<Driver> drivers;
}
public static class Driver {
public String name;
public String licenseNumber;
}
Try using Object Mapper for converting your vehicle object to json string and in that case you would not be needing request in method argument.
And you are using post request method then if condition is not needed.

How to convert JSON which has array to Java objects

Unable to convert a JSON string which has few elements and an array inside it.
On UI I need to feed the array to bootstrap table.
JSON string:
{
"IsOperationSuccessful": true,
"IsResult": true,
"StatusCode": "OK",
"Response": [{
"PlaylistCreatedBy": "XYZ",
"PlaylistCreatedOn": "10/10/2019 14:10",
"PlaylistDisplayTitle": "blah",
"PlaylistId": 101,
"PlaylistScheduledReleaseTime": "10/10/2019 14:10"
}, {
"PlaylistCreatedBy": "HHJK",
"PlaylistCreatedOn": "10/10/2019 14:10",
"PlaylistDisplayTitle": "blah blah",
"PlaylistId": 102,
"PlaylistScheduledReleaseTime": "10/10/2019 14:10"
}, {
"PlaylistCreatedBy": "HJHJ",
"PlaylistCreatedOn": "10/10/2019 14:10",
"PlaylistDisplayTitle": "UIUI",
"PlaylistId": 103,
"PlaylistScheduledReleaseTime": "10/10/2019 14:10"
}, {
"PlaylistCreatedBy": "KJK",
"PlaylistCreatedOn": "10/10/2019 14:10",
"PlaylistDisplayTitle": "kkj",
"PlaylistId": 104,
"PlaylistScheduledReleaseTime": "10/10/2019 14:10"
}],
"Total": 4
}
The code that I have added so far:
PreRecordedCall morningCallResponse = new PreRecordedCall();
JSONArray playListinfo = null;
String testResponse = "//Json Goes here "
JSONObject finalJson = new JSONObject();
finalJson.put("testResponse", testResponse);
Gson gson = new Gson();
morningCallResponse = gson.fromJson(testResponse,
PreRecordedCall.class);
playListinfo = morningCallResponse.getPreRecordplayListInformation();
One way is to create 2 POJOs (says Response and PreRecordedCall) as follows.
The Response is for the JSON array with key "Response" and the PreRecordedCall is for the whole JSON object. The key is to use List<Response> to store JSON array.
BTW, to follow the naming rule for variables in POJOs with lowerCamelCase, I used #SerializedName for object name mapping.
Class Response
static class Response {
#SerializedName("PlaylistCreatedBy")
private String playlistCreatedBy;
#SerializedName("PlaylistCreatedOn")
private String playlistCreatedOn;
#SerializedName("PlaylistDisplayTitle")
private String playlistDisplayTitle;
#SerializedName("PlaylistId")
private int playlistId;
#SerializedName("PlaylistScheduledReleaseTime")
private String playlistScheduledReleaseTime;
//general getters and setters
}
Class PreRecordedCall
static class PreRecordedCall {
#SerializedName("IsOperationSuccessful")
private Boolean isOperationSuccessful;
#SerializedName("IsResult")
private Boolean isResult;
#SerializedName("StatusCode")
private String statusCode;
#SerializedName("Response")
private List<Response> response;
#SerializedName("Total")
private int total;
//general getters and setters
}
Then you can simply convert the JSON string to pre-defined POJOs as follows:
Gson gson = new Gson();
PreRecordedCall preRecordedCall = gson.fromJson(testResponse, PreRecordedCall.class);
System.out.println(preRecordedCall.getResponse().size());
Console output
4
And if you are using Jackson as your JSON parser, change #SerializedName to #JsonProperty and you can get the same result by following code snippet.
ObjectMapper mapper = new ObjectMapper();
PreRecordedCall preRecordedCall = mapper.readValue(testResponse, PreRecordedCall.class);
System.out.println(preRecordedCall.getResponse().size());
Check if the below code works for you:
String finalJson= "//Json Goes here ";
ObjectMapper objectMapper = new ObjectMapper();
try {
PreRecordedCall morningCallResponse = objectMapper.readValue(json, PreRecordedCall.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
}

JSON Response Has Escaped Quotations Using Jackson and JAX-RS Exception Mapper

I have a simple requirement where, if application encounters an exception, my JAX-RS Rest endpoint should return a custom JSON response with 500 HTTP header status.
Data needed to construct the response comes from an object with several properties (see below). The problem is, I am only interested in one or two values from each property (out of several dozens). And I cannot modify any of these models/classes (some have a Jackson annotation for JSON processing, e.g. null properties should be discarded during serialization).
public class MainObject {
private FirstProperty firstProperty;
private SecondProperty secondProperty;
private ThirdProperty thirdProperty;
// other codes
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
public class FirstProperty {
private boolean bol = true;
private double dob = 5.0;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class SecondProperty {
private String str;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class ThirdProperty {
private int intProp = true;
private List<String> subProperty;
// other properties
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(this); }
catch (Exception e) { return null; }
}
}
The expected JSON that I should be seeing coming back is on the client side (say, a browser -- testing in Edge):
{
"firstProperty" : { "subProperty" : [ "val1" ] },
"secondProperty" : { "str" : "val2", "subproperty" : [ "val3", "val6" ] },
"thirdProperty" : { "subProperty" : [ "val4" ] }
}
Instead, all my field names and their values have their quotations escaped, and extra double quotes around the entire value, e.g.:
{
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }",
"secondProperty" : "{ \"str\" : \"val2\", \"subproperty\" : [ \"val3\", \"val6\" ] }",
"thirdProperty" : "{ \"subProperty\" : [ \"val4\" ] }"
}
Please note the extra " before and after the curly brackets. My environment is:
Java 1.8.45
FasterXML Jackson 2.9.8
Spring Boot 2.0.1
RestEasy (JBoss) JAX-RS
JBoss 6.4
I eliminated the majority of "noise" in the code to see at what point this happens. This is the controller:
#Path("/")
public class MainController {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/rest/path")
public MainObject getMainObject throws MyCustomException {
// A service call that throws MyCustomException
}
}
And JAX-RS ExceptionMapper where I send the response back:
#Provider
public class MyCustomExceptionMapper extends ExceptionMapper<MyCustomException> {
#Override
public Response toResponse(MyCustomException ex) {
HashMap<String, Object> responseBody = new HashMap<>();
String strEx = ex.getStrEx(); // Comes from SecondProperty.str stored in MyCustomException, not that it matters
// Instantiate an empty object that contains
MainObject obj = new MainObject();
obj.getFirstProperty().setSubProperty(ex.getStrs());
obj.getSecondProperty().setStr(strEx);
obj.getSecondProperty().setSubProperty(ex.getStrs());
obj.getThirdProperty().setSubProperty(ex.getStrs());
responseBody.put("firstProperty", serializeFirstProperty(obj.getFirstProperty()));
responseBody.put("secondProperty", serializeSecondProperty(obj.getSecondProperty()));
responseBody.put("thirdProperty", serializeThirdProperty(obj.getThirdProperty()));
Response response = Response.status(/* 500 status */).entity(responseBody).build();
return response;
}
}
Since I only need to send back a very small subset of overall properties from each of my types, I created a custom StdSerializer that would only populate a needed property. For brevity, I only do serializeFirstProperty() but they are all more or less identical:
private StdSerializer<FirstProperty> getFPSerializer(FirstProperty firstProperty) {
return new StdSerializer<FirstProperty>(FirstProperty.class) {
#Override
public void serialize(FirstProperty value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
if (/* there are items in FirstProperty.subProperty */) {
gen.writeArrayFieldStart("subProperty");
for (String str : value.getSubProperty()) {
gen.writeString(str);
}
gen.writeEndArray();
}
gen.writeEndObject();
}
}
private <T> ObjectMapper getCustomOM(StdSerializer<?> serializer) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addSerializer(serializer);
mapper.registerModule(module);
return mapper;
}
Then use these helper methods like:
private String serializeFirstProperty(FirstProperty firstProperty) {
ObjectMapper mapper = getCustomOM(getFPSerializer(firstProperty));
String ser = null;
try { ser = mapper.writeValueAsString(firstProperty); }
catch (JsonProcessingException e) { return null; }
return ser;
}
I have tried countless of configurations with ObjectMapper, e.g. disable(JsonParser.Feature.ALLOW_BACKLASH_ESCAPING_ANY_CHARACTER) (couldn't find any relevant flag for JsonGenerator which I really want to disable in a similar fashion).
Or explicitly returning Object from serializeFirstProperty(), or replacing all the \" with " in serializeFirstProperty() when ser is returned.
Or set custom StdSerializer's JsonGenerator.setCharacterEscapes(new CharacterEscapes() { //... } or play around with JAX-RS Response at no avail. I always seem to get a "string" value with quotations, e.g.:
"firstProperty" : "{ \"subProperty\" : [ \"val1\" ] }"
If I simply just do
responseBody.put("firstProperty", mapper.writeValueAsString(obj.getFirstProperty()));
somehow this produces the right JSON output, however, it includes a lot of unnecessary properties which I don't want in this exception handling case.
Funny thing is, when I peer into response (or responseBody map), everything looks right (I don't see values having double quotations).
Please also note that not only I can't modify the models, but some of their properties are instantiated during creation with default values, so not-null inclusion doesn't work, and they will appear in the final JSON if I don't use a custom serialization.
Does anyone know what's causing this escaped and extra quotations?
I think I misunderstood the question in the first attempt of answering it.
The problem is that you serialize a property as string (using mapper.writeValueAsString(this) and then add it to the responseBody which you think of being string to json object map but it is a string to Java object map. In your case at runtime it is a string mapping to another string (the serialized json object is represented as Java string) and a Java string is a Java object as well.
What you want to do instead is to construct a Java object responseBody instead of a map. It should act as a DTO having all the specific properties etc and then serializing it in in one action using the mapper. Because if you first serialize a property to a json string than it it is just a string from the Java point of view and the mapper has no chance to interpret it as a json object.

JSON to XML conversion without changing the sequence

I am using the below code to convert json to xml of multiple XML files with different JSON structures.
String toXmlRequest = fullRequest.toString();
JSONObject jsonObj = new JSONObject(toXmlRequest);
String XmlRequest = XML.toString(jsonObj);
System.out.println(XmlRequest);
Input
{
"EnrollmentRequest":
{
"data":
{
"commonDataContext":
{
"requestId": "ADA12131",
"clientId": "ABCDEF",
"timestamp":"2013-12-13T11:10:00.715-05:00"
},
"cardNumber" : "123456789012345" ,
"firstName" : "John" ,
"lastName" : "Smith" ,
"email" : "JohnSmith#g.com" ,
"enrollStatus" : "E" ,
"pathEnroll" : "NewAcct",
"cardSavedIndicator" : "Y"
}
}
}
Output
<EnrollmentRequest>
<data>
<firstName>John</firstName>
<lastName>Smith</lastName>
<commonDataContext>
<clientId>ABCDEF</clientId>
<requestId>ADA12131</requestId>
<timestamp>2013-12-13T11:10:00.715-05:00</timestamp>
</commonDataContext>
<pathEnroll>NewAcct</pathEnroll>
<enrollStatus>E</enrollStatus>
<cardSavedIndicator>Y</cardSavedIndicator>
<cardNumber>123456789012345</cardNumber>
<email>JohnSmith#g.com</email>
</data>
</EnrollmentRequest>
The sequence of the output is getting changed. It is not able to keep the actual sequence. Is there any way this can be kept intact.
This is not possible using org.json.JSONObject directly. The reason is that JSONObject uses an internal store of type HashMap. HashMap does not preserve insertion order
It would be possible with a LinkedHashMap, however it does not appear possible to configure JSONObject to use one.
/**
* Construct an empty JSONObject.
*/
public JSONObject() {
this.map = new HashMap<String, Object>();
}
An alternative would be to read using a library that does preserve order, e.g. Jackson....
ObjectMapper mapper = new ObjectMapper();
JsonNode jackson = mapper.readTree(fullRequest);
and then feed that into XML
String xmlRequest = XML.toString(new JSONAdapter(jackson));
with the necessary type adaption to make the Jackson object look like a org.json.JSONObject. Incomplete example below:
private static class JSONAdapter extends JSONObject {
private JsonNode jackson;
public JSONAdapter(JsonNode jackson) {
this.jackson = jackson;
}
#Override
public Iterator<String> keys() {
return jackson.fieldNames();
}
#Override
public Object opt(String key) {
return get(key);
}
#Override
public Object get(String key) throws JSONException {
JsonNode nested = jackson.get(key);
if (nested.isObject()) {
return new JSONAdapter(nested);
} else if (nested.isTextual()) {
return nested.asText();
} else if (nested.isNumber()) {
return nested.asDouble();
} else if (nested.isBoolean()) {
return nested.asBoolean();
}
return null;
}
}
Output
<EnrollmentRequest>
<data>
<commonDataContext>
<requestId>ADA12131</requestId>
<clientId>ABCDEF</clientId>
<timestamp>2013-12-13T11:10:00.715-05:00</timestamp>
</commonDataContext>
<cardNumber>123456789012345</cardNumber>
<firstName>John</firstName>
<lastName>Smith</lastName>
<email>JohnSmith#g.com</email>
<enrollStatus>E</enrollStatus>
<pathEnroll>NewAcct</pathEnroll>
<cardSavedIndicator>Y</cardSavedIndicator>
</data>
</EnrollmentRequest>
For embedded json arrays, you need to apply one more condition to check if it array and return it as json array.
if(nested.isArray()) {
JSONArray arr = new JSONArray();
for(JsonNode value : nested){
arr.put(value.asText());
}
return arr;
}

Issue with rest Service using rest easy

I have a complex object like below
public class TestFilter {
public TestFilter(){
}
private Set<String> m_categories;
private Set<String> m_taskNames;
public Set<String> getCategories() {
return m_categories;
}
public void setCategories(Set<String> categories) {
this.m_categories = categories;
}
public Set<String> getTaskNames() {
return m_taskNames;
}
public void setTaskNames(Set<String> taskNames) {
this.m_taskNames = taskNames;
}
public static TestFilter fromString(String jsonRepresentation){
ObjectMapper mapper = new ObjectMapper();
TestFilter filter= null;
try {
filter = mapper.readValue(jsonRepresentation, TestFilter.class );
} catch (IOException e) {
throw new MyException("Exception while parsing the TestFilter");
}
return filter;
}
}
I am using it in my rest service like below
#GET
#Path("/schedule/info")
public Response getScheduledTasks(#QueryParam(FILTERS)TestFilter testFilter){
if(testFilter == null){
System.out.println("its null");
} else {
System.out.println("not null");
}
return Response.status(Status.OK).entity("Success").build();
}
The url I am using for accessing the object is like below.The url is decoded for ease of reading.
https://myip:port/my-context/rest/schedule/info?filters="{"categories":["C","D"],"taskName":["TaskA"]}"
I had put a debug point on the service, so its hitting the service but the problem is that testFilter is always coming as null.
Please let me know what is the issue with the code.
JSON wrapped in " is just a JSON String. A JSON object should not have the quotes. So just just unwrap the JSON from the quotes
filters={"categories":["C","D"],"taskNames":["TaskA"]}
Another thing, based on your comments. If you want to avoid a 404 NotFound, if you want to change it to something else like 400 Bad Request, then just throw that instead
throw new BadRequestException()
// or new WebApplicationException(400)
It's odd to me that bad query parameters would result in a 404, but this is the specified behavior with JAX-RS. Personally, I just change it to 400 in cases like this.

Categories

Resources