Json dynamic deserialization with jackson - java

I've already have a look at the question "Jackson dynamic property names" but it does not really answer to my question.
I want to deserialize something like this :
public class Response<T> {
private String status;
private Error error;
private T data;
}
but data can have different names since different services exist and return the same structure with some different data. For example 'user' and 'contract' :
{
response: {
status: "success",
user: {
...
}
}
}
or
{
response: {
status: "failure",
error : {
code : 212,
message : "Unable to retrieve contract"
}
contract: {
...
}
}
}
I'd like genericize my responses objects like this :
public class UserResponse extends Response<User> {}
I've tried the following but i'm not sure it is my use case or if don't use it in the good way :
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
#JsonSubTypes({#Type(value = User.class, name = "user"),
#Type(value = Contract.class, name = "contract")})
Finally, i've created a custom Deserializer. It works but i'm not satisfied:
public class ResponseDeserializer extends JsonDeserializer<Response> {
#Override
public Response deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Response responseData = new Response();
Object data = null;
for (; jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
String propName = jp.getCurrentName();
// Skip field name:
jp.nextToken();
if ("contract".equals(propName)) {
data = mapper.readValue(jp, Contract.class);
} else if ("user".equals(propName)) {
data = mapper.readValue(jp, User.class);
} else if ("status".equals(propName)) {
responseData.setStatus(jp.getText());
} else if ("error".equals(propName)) {
responseData.setError(mapper.readValue(jp, com.ingdirect.dg.business.object.community.api.common.Error.class));
}
}
if (data instanceof Contract) {
Response<Contract> response = new Response<Ranking>(responseData);
return response;
}
if (data instanceof User) {
Response<User> response = new Response<User>(responseData);
return response;
}
// in all other cases, the type is not yet managed, add it when needed
throw new JsonParseException("Cannot parse this Response", jp.getCurrentLocation());
}
}
Any idea to do this clean with annotations ? Thanks in advance !

Jackson framework provides inbuilt support for dynamic types.
//Base type
#JsonTypeInfo(property = "type", use = Id.NAME)
#JsonSubTypes({ #Type(ValidResponse.class),
#Type(InvalidResponse.class)
})
public abstract class Response<T> {
}
//Concrete type 1
public class ValidResponse extends Response<T>{
}
//Concrete type 2
public class InvalidResponse extends Response<T>{
}
main {
ObjectMapper mapper = new ObjectMapper();
//Now serialize
ValidResponse response = (ValidResponse)(mapper.readValue(jsonString, Response.class));
//Deserialize
String jsonString = mapper.writeValueAsString(response);
}

Have you tried:
public class AnyResponse {
private String status;
private Error error;
private Contract contract;
private User user;
// And all other possibilities.
}
// ...
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
This should fill in whatever object appears in the JSON and leave the rest null.
You could then fill in a Response with the relevant object.

Related

ResponseBuilder toString() returns object classes in string, not just the raw response string

I'm presently migrating from the Java ASK-SDK v1 to Java ASK SDK v2.
I'm trying to return a webhook call using the ResponseBuilder class that I built my response up and the data is correct, however when I try to populate the HTTP body with the JSON text, the ResponseBuilder.toString() value doesn't just populate the data with just the string, I get the following:
Optional[class Response {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>Some of the things you can say are What would you like to do?</speak>
}
card: null
reprompt: class Reprompt {
outputSpeech: class SsmlOutputSpeech {
class OutputSpeech {
type: SSML
playBehavior: null
}
ssml: <speak>You can say ..., is that what you want?</speak>
}
}
directives: []
shouldEndSession: false
canFulfillIntent: null
}]
Is there another way to get the string for the body of the response? The BaseSkillResponse has a getResponse() call, however, I cannot figure out how to use the class to generate the String response output.
I was able to get the string with the following in my class:
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
myFunction(){
try{
return toJsonString(responseBuilder.build().get());
} catch(IOException e) {
e.printStackTrace();
}
}
public String toJsonString(Response response)throws IOException {
return OBJECT_MAPPER.writeValueAsString(response);
}
Solve this by doing the following:
public String toJsonString(Response response)throws IOException
{
JacksonSerializer jacksonSerializer = new JacksonSerializer();
constructedResponse = jacksonSerializer.serialize(response);
JSONObject jsonObject = new JSONObject();
jsonObject.put("response",constructedResponse);
}

What JSON parsing is simpliest?

For example, I have some REST API testing task.
I took Unirest framework, and what I have got some JSON extractors,
protected int extractStatus (HttpResponse<JsonNode> login) {
return login.getStatus();
}
protected String extractError (HttpResponse<JsonNode> login) {
return login.getBody().getObject()
.getJSONObject("data")
.getJSONObject("error")
.toString();
}
protected String extractEmail (HttpResponse<JsonNode> login) {
return login.getBody().getObject()
.getJSONObject("data")
.getJSONObject("result")
.getJSONObject("userProfile")
.getString("registrationEmail");
}
For my simple tests:
public class LoginJSON extends Request {
#Test
public void validLoginTest() {
response = login("probe#grr.la", "9876541");
Assert.assertEquals(200, extractStatus(response));
Assert.assertNotNull("ID expected", extractId(response));
Assert.assertNotNull("Token expected", extractToken(response));
Assert.assertEquals("probe#grr.la", extractEmail(response));
Assert.assertEquals("M", extractGender(response));
Assert.assertEquals("EmailEnabled", true, extractEmailEnabled(response));
Assert.assertEquals("EmailDisabled",false, extractEmailDisabled(response));
Assert.assertEquals(2, extractRolesCount(response));
Assert.assertTrue("User role expected", extractRoleByName(response, "ROLE_USER"));
Assert.assertTrue("Admin role expected", extractRoleByName(response, "ROLE_ADMIN"));
}
Maybe there was more simpliest way?
Try Gson with Retrofit!
HttpResponse<JsonNode> jsonResponse = request.asJson();
Gson gson = new Gson();
String responseJSONString = jsonResponse.getBody().toString();
MyResponseObject myObject = gson.fromJson(responseJSONString, MyResponseObject.class);
Classes
class MyResponseObject {
#Serializable("data")
private MyDataObject myData;
#get set methods
}
class MyDataObject {
#Serializable("result")
private MyResultObject myResultObject;
#get set methods
}
class MyResultObject {
#Serializable("userProfile")
private UserProfileDao userProfileDao;
#get set methods
}
class UserProfileDao {
#Serializable("registerationEmail")
private String registerationEmail;
#get set methods
}
You could do a try catch for successful parse or failed parse.

How to ask gson to avoid escaping json in a json response?

I have a response object like this:
public class TestResponse {
private final String response;
private final ErrorCodeEnum error;
private final StatusCodeEnum status;
// .. constructors and getters here
}
I am serializing above class using Gson library as shown below:
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
System.out.println(gson.toJson(testResponseOutput));
And the response I am getting back is shown below:
{
"response": "{\"hello\":0,\"world\":\"0\"}",
"error": "OK",
"status": "SUCCESS"
}
As you can see, my json string in "response" field is getting escaped. Is there any way I can ask gson not to do that and instead return a full response like this:
{
"response": {"hello":0,"world":"0"},
"error": "OK",
"status": "SUCCESS"
}
And also - Is there any problem if I do it above way?
NOTE: My "response" string will always be JSON string or it will be null so only these two values will be there in my "response" string. In "response" field, I can have any json string since this library is calling a rest service which can return back any json string so I am storing that in a string "response" field.
If your response field can be arbitrary JSON, then you need to:
Define it as an arbitrary JSON field (leveraging the JSON type system already built into GSON by defining it as the root of the JSON hierarchy - JsonElement)
public class TestResponse {
private final JsonElement response;
}
Convert the String field to an appropriate JSON object representation. For this, you can use GSON's JsonParser class:
final JsonParser parser = new JsonParser();
String responseJson = "{\"hello\":0,\"world\":\"0\"}";
JsonElement json = parser.parse(responseJson); // Omits error checking, what if responseJson is invalid JSON?
System.out.println(gson.toJson(new TestResponse(json)));
This should print:
{
"response": {
"hello": 0,
"world": "0"
}
}
It should also work for any valid JSON:
String responseJson = "{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}";
JsonElement json = parser.parse(responseJson);
System.out.println(gson.toJson(new TestResponse(json)));
Output:
{
"response": {
"arbitrary": "fields",
"can-be": {
"in": [
"here",
"!"
]
}
}
}
I know this is old but just adding an potential answer in case it is needed.
Sounds like you just want to return the response without escaping. Escaping is a good thing, it will help to prevent security issues and prevent your JS application from crashing with errors.
However, if you still want to ignore escaping, try:
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create();
add simple TypeAdapter and use jsonValue(value)
gson 2.8.0
version 1:
#Test
public void correctlyShow() {
TestResponse2 src = new TestResponse2("{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}");
Gson create = new GsonBuilder().registerTypeAdapter(String.class, ADAPTER).create();
Stopwatch createStarted = Stopwatch.createStarted();
String json2 = create.toJson(src);
System.out.println(json2 + " correctlyShow4 " + createStarted.stop());
}
public class TestResponse2 {
private final String response;
public TestResponse2(String response) {
this.response = response;
}
public String getResponse() {
return response;
}
}
private static final TypeAdapter<String> ADAPTER = new TypeAdapter<String>() {
#Override
public String read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Unsupported Operation !!!");
}
#Override
public void write(JsonWriter out, String value) throws IOException {
out.jsonValue(value);
}
};
...
vesrion 2
#Test
public void correctlyShow() {
TestResponse2 src = new TestResponse2("{\"arbitrary\":\"fields\",\"can-be\":{\"in\":[\"here\",\"!\"]}}");
String json2 = new Gson().toJson(src);
System.out.println(json2 + " correctlyShow4 ");
}
public class TestResponse2 {
#JsonAdapter(value = AdapterStringJson.class)
private final String response;
public TestResponse2(String response) {
this.response = response;
}
public String getResponse() {
return response;
}
}
private class AdapterStringJson extends TypeAdapter<String> {
#Override
public String read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Unsupported Operation !!!");
}
#Override
public void write(JsonWriter out, String value) throws IOException {
out.jsonValue(value);
}
}
You should have a nested object.
public class Response {
private final Integer hello;
private final String world;
}
public class TestResponse {
private final Response response;
private final ErrorCodeEnum error;
private final StatusCodeEnum status;
// .. constructors and getters here
}
Instead of a String, depending on your needs, you could use a Map (or similar) or a nested Object. There should not be a problem representing it this way but in your example, if it were a String, there would be a problem if you didn't escape characters such as the double-quote.

Documenting a wrapped REST response using swagger UI

I have a WidgetDto that I have annotated with swagger UI annotations. The final response wraps a list of WidgetDtos with a layer of metadata (per page 21 of this RESTful best practices document). For example:
{
"data" : [
{
"id" : 1234,
"prop1" : "val1"
...
},
{
"id" : 5678,
"prop1" : "val2"
...
},
...
]
}
My java code looks like this:
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response getWidgets() {
List<WidgetDto> widgets;
...
Map<String, Object> responseBody = new HashMap<>();
responseBody.put("data", widgets);
return Response.ok(responseBody).build();
}
I'd like to reuse this pattern on multiple resources, and I don't want to create list DTOs for every response type. Is there an elegant way to use swagger to document these types of response bodies?
Your metadata is not a part of your resource but it's a part of your resource's representation.
In my case, responses types are 'application/hal+json' and 'application/json', each of them use a different wrapper with different metadatas.
To solve this problem, I created an extern document to explain these two wrappers and for each of them, how a single resource and a list of resources are represented with metadata.
I think my choice is correct because I separate the resource of its representations (per page 7 'Manipulation of Resources Through Representations' of this RESTful best practices document)
In your case, you returns a list of WidgetDtos, the layer of metadata is a part of the representation of your resource.
However, you can use a generic class like Resource and Resources used by spring-hateoas :
public class Resources<T> implements Iterable<T> {
private final Collection<T> content;
Resources(Iterable<T> content) {
this.content = new ArrayList<T>();
for (T element : content) {
this.content.add(element);
}
}
}
And use it like this:
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response getWidgets() {
List<WidgetDto> widgets;
...
return Response.ok(new Resources<WidgetDto>(widgets)).build();
}
I faced a similar problem a few months ago when I was developing a project for school. The solution is to create an envelope and always return it. The envelope will contain a feild "data" which is a generic; so you will be able to bind it to any data type.
Note that even though I used it I later on read that it should be used scarecly (I think your case is a good example of usage) but technically an Exception object should be thrown if the request failed.
Anyway this is my Response class which I used to return all my responses:
public class Response <AnyData> {
private static final String SUCCESS = "success";
private static final String FAILURE = "failure";
private String status;
private AnyData data;
private String error;
private Response(String status, AnyData data, String error) {
this.status = status;
this.data = data;
this.error = error;;
}
private Response(String status, AnyData data) {
this(status, data,"");
}
private Response(String status, String error) {
this(status, null, error);
}
public static <AnyData> Response<AnyData> success(AnyData data) {
return new Response<AnyData>(SUCCESS, data);
}
public static <AnyData> Response<AnyData> failure(String error) {
return new Response<AnyData>(FAILURE, error);
}
public static <AnyData> Response<AnyData> unimplemented() {
return new Response<AnyData>(FAILURE, "Missing implementation in the backend.");
}
public static <AnyData> Response<AnyData> failureUserNotFound() {
return Response.failure("User not found!");
}
public static <AnyData> Response<AnyData> failureBusinessNotFound() {
return Response.failure("Business not found!");
}
// Removed getters and setters for simplicity.
}
After this is set we will just create the responses right from the Comtroller. I changed it a bit to make it work with the sample is should be legible enough. Note that I have static methods for my responses: 'success()', 'error()'...
#RestController
#Api(tags={"Widgets"})
public class WidgetController {
#RequestMapping(value="/api/widgets", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get all widgets.")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Returns the list of widgets.")
})
public Response<List<WidgetDto>> getWidgets() {
List<WidgetDto> widgets = new LinkedList<>();
widgets.add(new WidgetDto(1234, "val1"));
widgets.add(new WidgetDto(5678, "val2"));
return Response.success(widgets);
}
}
And here is a sample of the response body:
Hope this helps.
You can define the responseContainer attribute in the #ApiOperation annotation.
The value List will wrap your WidgetDto in a container.
#ApiOperation(
value = "Get all widgets.",
response = WidgetDto.class,
responseContainer = "List"
)

spring generic json response

I am using Spring MVC and returning JSON as response. I would like to create a generic JSON response where I can put in any TYPE and want the response to look like this
{
status : "success",
data : {
"accounts" : [
{ "id" : 1, "title" : "saving", "sortcode" : "121212" },
{ "id" : 2, "title" : "current", "sortcode" : "445566" },
]
}
}
So I created a Response<T> object
public class Response<T> {
private String status;
private String message;
T data;
...
...
}
Is this the correct way of doing this, or is there a better way?.
How do you use this Response object in Spring controller to return an empty response object and/or a populated response object.
Thanks in advance GM
UPDATE:
In order to get the similar JSON output as the one described, i.e. with "accounts" key in JSON, I had to use Response<Map<String, List<Account>>> the following in the controller:
#RequestMapping(value = {"/accounts"}, method = RequestMethod.POST, produces = "application/json", headers = "Accept=application/json")
#ResponseBody
public Response<Map<String, List<Account>>> findAccounts(#RequestBody AccountsSearchRequest request) {
//
// empty accounts list
//
List<Account> accountsList = new ArrayList<Account>();
//
// response will hold a MAP with key="accounts" value="List<Account>
//
Response<Map<String, List<Account>>> response = ResponseUtil.createResponseWithData("accounts", accountsList);
try {
accountsList = searchService.findAccounts(request);
response = ResponseUtil.createResponseWithData("accounts", accountsList);
response.setStatus("success");
response.setMessage("Number of accounts ("+accounts.size()+")");
} catch (Exception e) {
response.setStatus("error");
response.setMessage("System error " + e.getMessage());
response.setData(null);
}
return response;
}
Is this the right way of doing this? i.e. in order to get the "accounts" key in JSON output?
While your example JSON is not valid (status and data are not enclosed in quotations), this approach will work.
You will want to ensure that you have the Jackson jars on your classpath, and Spring will take care of the rest.
To get this to work, I would create a constructor for your response class that looks something like this:
public class Response<T> {
private String status;
private String message;
private T data;
public Response(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
//...getter methods here
}
And then in your Spring controller, you just return this object from your method that is mapped with #RequestMapping
#Controller
public class MyController {
#RequestMapping(value="/mypath", produces="application/json")
public Response<SomeObject> myPathMethod() {
return new Response<SomeObject>("200", "success!", new SomeObject());
}
}

Categories