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.
Related
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);
I need to send a minified JSON to a rest service, it seemed all right, however, when printing my JSON into a file several "/" slashes appear separating the fields from the file, as I am new to this type of implementation I was confused ...
Basically, I add objects to a list and at the end, I convert the generic list to Json, this is my method that converts the List object to JSON
public String convertListToJson(List obj) throws IOException {
String jsonInString = null;
ObjectMapper mapper = new ObjectMapper();
mapper.setDefaultPropertyInclusion(JsonInclude.Include.ALWAYS);
mapper.configure(SerializationFeature.USE_EQUALITY_FOR_OBJECT_ID, true);
jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
JsonNode jsonNode = mapper.readValue(jsonInString, JsonNode.class);
String json = jsonNode.toString();
return json;
}
String payload = convertListToJson(object);
System.out.println(payload);
This is the result of printing on the console
[{"id":12,"nomePeriodo":"2012","inicio":"2012-01-01T02:00:00Z","fim":"2012-12-31T02:00:00Z"},{"id":13,"nomePeriodo":"2013","inicio":"2013-01-01T02:00:00Z","fim":"2013-12-31T02:00:00Z"},{"id":14,"nomePeriodo":"2014","inicio":"2014-01-01T02:00:00Z","fim":"2014-12-31T02:00:00Z"},{"id":15,"nomePeriodo":"2015","inicio":"2015-01-01T02:00:00Z","fim":"2015-12-31T02:00:00Z"},{"id":16,"nomePeriodo":"2016","inicio":"2016-01-01T02:00:00Z","fim":"2016-12-31T02:00:00Z"},{"id":17,"nomePeriodo":"2017","inicio":"2017-01-01T02:00:00Z","fim":"2017-12-31T02:00:00Z"},{"id":18,"nomePeriodo":"2018","inicio":"2018-01-01T02:00:00Z","fim":"2018-12-31T02:00:00Z"}]
This is the method I use for printing to file
printJson(payload);
private void printJson(String payload) {
ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(new File("c:\\server\\JSON.json"), payload);
} catch (IOException e) {
e.printStackTrace();
}
}
When I print to a file this is the result:
"[{\"id\":12,\"nomePeriodo\":\"2012\",\"inicio\":\"2012-01-01T02:00:00Z\",\"fim\":\"2012-12-31T02:00:00Z\"},{\"id\":13,\"nomePeriodo\":\"2013\",\"inicio\":\"2013-01-01T02:00:00Z\",\"fim\":\"2013-12-31T02:00:00Z\"},{\"id\":14,\"nomePeriodo\":\"2014\",\"inicio\":\"2014-01-01T02:00:00Z\",\"fim\":\"2014-12-31T02:00:00Z\"},{\"id\":15,\"nomePeriodo\":\"2015\",\"inicio\":\"2015-01-01T02:00:00Z\",\"fim\":\"2015-12-31T02:00:00Z\"},{\"id\":16,\"nomePeriodo\":\"2016\",\"inicio\":\"2016-01-01T02:00:00Z\",\"fim\":\"2016-12-31T02:00:00Z\"},{\"id\":17,\"nomePeriodo\":\"2017\",\"inicio\":\"2017-01-01T02:00:00Z\",\"fim\":\"2017-12-31T02:00:00Z\"},{\"id\":18,\"nomePeriodo\":\"2018\",\"inicio\":\"2018-01-01T02:00:00Z\",\"fim\":\"2018-12-31T02:00:00Z\"}]"
So you have these \ bars separating the items in json...
Am I doing something wrong or is this common?
If possible I would like to remove them from my archive
I am trying to integrate Spring Cloud streams and publishing a custom Java Object across services with RabbitMQ as broker. The object I am publishing looks like:
public class AppMessageEnvelope implements Serializable {
...
private Object messageBody;
private Date sentAt = new Date();
...
// setters and getters
}
This is just a wrapper object and the original object is put in messageBody. The object I am putting in messageBody looks like:
public class Job {
...
private String message;
private Map<MyEnum, String> myMap;
...
}
Note that both AppMessageEnvelope and Job are in a different model project which is imported as a Maven dependency in the publisher and subscriber Spring Boot projects, so models are exactly the same.
In producer, I publish the object as:
#EnableBinding(Source.class)
public class JobDistributor {
private final Source jobQueue;
#Autowired
public JobDistributor(Source jobQueue) {
this.jobQueue = jobQueue;
}
public AppMessageEnvelope publishJob(AppMessageEnvelope message) {
LOG.info("Sending message: {}.", message);
jobQueue.output().send(MessageBuilder.withPayload(message).build());
return message;
}
}
In consumer, I get the message as:
#Component
#EnableBinding(Sink.class)
public class JobConsumer {
private final JobManager jobManager;
private final ObjectMapper objectMapper;
#Autowired
public JobConsumer(
JobManager jobManager, ObjectMapper objectMapper) {
this.jobManager = jobManager;
this.objectMapper = objectMapper;
}
#StreamListener(target = Sink.INPUT)
public void processData(AppMessageEnvelope messageEnvelope) {
LOG.info("Envelope received: {}.", messageEnvelope);
try {
TypeReference<Job> mapType = new TypeReference<Job>() {};
Job job = objectMapper.readValue(messageEnvelope.getMessageBody().toString(), mapType);
jobManager.processRequest(job);
} catch (Exception ex) {
LOG.error("Couldn't convert to correct object for processing: {}.", ex);
}
}
}
I try to use TypeReference to convert the internal object into a correct object but I get an error as:
JobConsumer - Couldn't convert to correct object for processing: {}.
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('i' (code 105)): was expecting double-quote to start field name
at [Source: (StringReader); line: 1, column: 3]
Before the message is converted, I log it:
JobConsumer - Envelope received: AppMessageEnvelope{..., messageBody={id=5bf3a7302dbe9c7cf9927c60, jobId=8c0bfcb0b21248e694b5cd52337a1f9e, submittedAt=2018-11-20T06:18:24+0000, lastUpdatedOn=null, message=null, ..., fileContentMap={FILE_BYTES=JVBERi0xLjUKJb/3ov}}, sentAt=Tue Nov 20 11:48:24 IST 2018}
I tried configuring ObjectMapper as:
#Autowired
private ObjectMapper objectMapper() {
JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
return new ObjectMapper(factory);
}
I tried enabling unquoting fields with this too:
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
I tried solutions provided by this blog and some similar SO problem but nothing solved. What am I missing?
Use below json conversion to convert JSON to Model / Entity
ObjectMapper objectMapper = new ObjectMapper();
Job job = objectMapper.convertValue(messageEnvelope.getMessageBody().toString(), Job.class);
This may be funny error please copy your json and past into online json formatter.
if the validation wrong please rewrite the json manually or encode it.
Error cause: character encoding issue at time of copy from various sources
Here the problem:
Job job = objectMapper.readValue(messageEnvelope.getMessageBody().toString(), mapType);
Just like this:
Job job = objectMapper.readValue(objectMapper.writeValueAsString(messageEnvelope.getMessageBody()), mapType);
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;
}
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.