I am using resttemplate with jackson to marshall/unmarshall java/json objects.
What would be the best strategy to serialize/deserialize
a Map that may contain key value pairs such that keys are strings and values could
be various types for example an ArrayList of custom objects
I did some research on this site and found the use of #JsonAnyGetter #JsonAnySetter
could be used in this situation, but wasnt sure of how to deserialize in the context
of resttemplate getforobject method. Would one have to write a custom httpmessageconverter
to accomplish the deserialization?
Thanks in advance.
We'll assume you have a response like this:
{ key1: "something", key2: 3}
You'll want to have a DTO that has those fields:
class CustomResponse {
private String key1;
private long key2;
}
Make sure you add getters and setters for the above.
Now make your request:
restTemplate.postForObject(url, requestObject, CustomResponse.class);
The request object can be either a DTO like the above or just use Arrays and Maps to construct the requestObject.
You should add this annotation to your response DTOs. This ensures that if there are fields in the response that don't map in your DTO, they will be ignored.
#JsonIgnoreProperties(ignoreUnknown = true)
Related
I'm developing a Rest Client using Spring Boot and Spring Framework (spring-boot-starter-parent 2.1.6.RELEASE)
I have a class representing a response object as shown below:
public class ValidateResponse {
private String ResponseCode;
private String ResponseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
I'm creating a web-hook for an external api and I need to return a JSON object to for a specific endpoint (the JSON object properties must start with uppercase(s)). I'm calling returning the object from a PostMapping method nested in a RequestMapping root path:
#PostMapping("hooks/validate")
public ValidateResponse responseObj(#RequestHeader Map<String, String> headersObj) {
ValidateResponse response = new ValidateResponse("000000", "Success");
logger.info("Endpoint = hooks/validate | Request Headers = {}", headersObj);
return response;
}
However, when I hit the endpoint from postman I'm getting duplicate varialbes
{
"ResponseCode": "000000",
"ResponseDesc": "Success",
"responseCode": "000000",
"responseDesc": "Success"
}
I understand that the pojo-json conversion is handled by spring but I don't understand why the conversion is yielding duplicate variables.
Note: I know the ResponseDesc and the ResponseCode are not declared using the best standards for naming variables (camelCasing).
I've done some digging and according to the Java Language Specification
An identifier is an unlimited-length sequence of Java letters and Java digits, the first of which must be a Java letter.
and
The "Java letters" include uppercase and lowercase ASCII Latin letters A-Z (\u0041-\u005a), and a-z (\u0061-\u007a), and, for historical reasons, the ASCII underscore (_, or \u005f) and dollar sign ($, or \u0024). The $ character should be used only in mechanically generated source code or, rarely, to access pre-existing names on legacy systems.
So, I'm assuming its syntactically correct to define a variable using the Camelcase format [Need clarification on this].
I'm considering having to create the JSON object manually but I'd like to know the cause of this behaviour first. Any pointers are appreciated.
Jackson deserializes all the public fields that it comes across. However if you want Jackson to return the response in your expected element names (in your case elements starting with capital letters), make the fields private and annotate them with the #JsonProperty(expected_name_here). Your class file will typically looks as shown below
public class ValidateResponse {
#JsonProperty("ResponseDesc")
private String responseCode;
#JsonProperty("ResponseDesc")
private String responseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
Note: The getters and setters for these fields should be public, otherwise Jackson won't see anything to deserialize in the class.
public class ValidateResponse {
#JsonProperty("ResponseDesc")
public String responseCode;
#JsonProperty("ResponseDesc")
public String responseDesc;
//getters and setters
//constructors using fields
//empty constructor
}
This must fix your problem, however I do not know the reason as it requires deep Jackson investigation.
EDIT
I found out the reason.
The field got duplicated because in you case you had:
2 public fields named in upper case -> they are to be processed by jackson
2 getters getResponseCode and getResponseDesc -> they are to be resolved
as accessors for properties responseCode and responseDesc accordingly.
Summing this up - you have 4 properties resolved by Jackson. Simply making your fields private will resolve your issue, however I still advise using JsonProperty approach.
I added a com.google.code.gson dependency in the projects pom.xml file to configure Spring Boot to use Gson (instead of the default jackson).
The Json object returned from the hooks/validate endpoint must have its property names starting with a capital letter. Using a java class to generate the response object was resulting to camelCased property names so I resolved to create the JSON response object manually. Here's the code for creating the custom JSON object:
public ResponseEntity<String> responseObj(#RequestHeader Map<String, String> headersObj) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
JsonObject response = new JsonObject();
response.addProperty("ResponseCode", "00000000");
response.addProperty("ResponseDesc" , "Success");
logger.info("Endpoint = hooks/validate | Request Headers = {}", headersObj);
return ResponseEntity.ok().headers(responseHeaders).body(response.toString());
}
Note The JSON object is returned as a String so the response from the endpoint must have an additional header to define MediaType to inform the calling system that the response is in JSON format:
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
then add the header to the response:
return ResponseEntity.ok().headers(responseHeaders).body(response.toString());
I have come across a problem of parsing json data . I am building project using spring boot based on REST api . When i have to parse data corresponding to domain then it is very easy , i use RequestBody in controller method with domain name but in current scenerio i have a list of domain in json form :
{
"data":[
{
"type":"abc",
"subtypes":[
{
"leftValue":"BEACH",
"rightValue":"MOUNTAIN",
"preferencePoint":60
},
{
"leftValue":"ADVENTURE",
"rightValue":"LEISURE",
"preferencePoint":60
}
]
},
{
"type":"mno",
"subtypes":[
{
"leftValue":"LUXURY",
"rightValue":"FUNCTIONAL",
"preferencePoint":60
},
{
"leftValue":"SENSIBLE",
"rightValue":"AGGRESIVE",
"preferencePoint":0
}
]
}
]
}
I am sending data in list where type is the property of class Type
and class Type has list of Subtypes class and subtype class contains leftValue and rightValue as enums
I am using spring boot which uses jackson liberary by default and i want to parse this data into corresponding Type class using Jackson. Can any one provide me solution.
It wasn't clear to me if you have static or dynamic payload.
Static payload
For static one, I would personally try to simplify your payload structure. But your structure would look like this. (I skipped getters and setters. You can generate them via Lombok library).
public class Subtype{
private String leftValue;
private String rightValue;
private int preferencePoint;
}
public class Type{
private String type;
private List<Subtype> subtypes;
}
public class Data{
private List<Type> data;
}
Then in your controller you inject Data type as #RequestBody.
Dynamic payload
For dynamic payload, there is option to inject LinkedHashMap<String, Object> as #RequestBody. Where value in that map is of type Object, which can be casted into another LinkedHashMap<String, Object> and therefore this approach support also nested objects. This can support infinite nesting this way. The only downside is that you need to cast Objects into correct types based on key from the map.
BTW, with pure Spring or Spring Boot I was always able to avoid explicit call against Jackson API, therefore I don't recommend to go down that path.
Is it possible to configure jackson to always add the type of the serialized object to the generated json output.
For example:
package org.acme;
class ClassA
{
String a;
String b;
}
and I want the generated json to be:
["org.acme.ClassA",{"a":"str1","b":"str2"}]
You can do that with enableDefaultTyping() of the ObjectMapper
e.g.
mapper.enableDefaultTyping(DefaultTyping.OBJECT_AND_NON_CONCRETE);
See ObjectMapper API
If your are free to change from Jackson and do not especially need the format to match the one your are showing you can try Genson http://code.google.com/p/genson.
For example if your requirement is to be able to deserialize interfaces or abstract classes based on the original type of the object you serialized you can do:
interface Entity {}
static class Person implements Entity {}
Genson genson = new Genson.Builder().setWithClassMetadata(true).create();
// json will be equal to {"#class":"my.package.Person"}
String json = genson.serialize(new Person());
// and now Genson is able to deserialize it back to Person using the information
// in the Json Object
Person person = (Person) genson.deserialize(json, Entity.class);
Another nice feature is the ability to define aliases for your classes, so you show less information in the json stream but also this allows you to do refactoring without worring of existing json streams (for example if you store it in a database).
Genson genson = new Genson.Builder().addAlias("person", Person.class).create();
// json value is {"#class": "person"}
String json = genson.serialize(new Person());
Have a look at the wiki.
I'm having issues using Jackson to map a Javascript posted JSON array of hashes (Tag).
Here is the data received by the controller #RequestBody (It is send with correct json requestheader):
[{name=tag1}, {name=tag2}, {name=tag3}]
Here is the controller:
#RequestMapping(value = "purchases/{purchaseId}/tags", method = RequestMethod.POST, params = "manyTags")
#ResponseStatus(HttpStatus.CREATED)
public void createAll(#PathVariable("purchaseId") final Long purchaseId, #RequestBody final List<Tag> entities)
{
Purchase purchase = purchaseService.getById(purchaseId);
Set<Tag> tags = purchase.getTags();
purchaseService.updatePurchase(purchase);
}
When I debug and view the 'entities' value it shows as an ArrayList of generic objects, not as a list of objects of type 'Tag' as I would expect.
How can I get jackson to map a passed array of objects to a list of obejcts of type 'Tag'?
Thanks
It sounds like Spring is not passing full type information for some reason, but rather a type-erased version, as if declaration was something like List<?> tag. I don't know what can be done to fully resolve this (may need something from Spring integration team), but one work-around is to define your own type like:
static class TagList extends ArrayList<Tag> { }
and use that instead. This will retain generic parameterization through super-type declarations so that even if Spring only passes equivalent of TagList.class, Jackson can figure out the Tag parameter.
Another way to do this is to rather obtain an array than a List, as follows:
#RequestBody Tag[] entities
Jackson requires a default constructor with no parameters on custom Objects, so you'll need to simply add a default constructor to your Tag class.
In your case simply add to your Tag class:
public Tag(){}
So I have a class that I was planning on using for simple JSON serialization.
public class Thing {
private int field1;
private String some_other_field;
private List<SubType> subs;
private list<AnotherType> another;
public String toJson() {
Gson g = new Gson();
g.toJson(this);
}
}
So the documentation shows that if you want to serialize generic types, you need to specify a TypeToken:
Type listtype = new TypeToken<List<SubType>>() {}.getType();
gson.toJson(subs, listtype);
But then, how does this work if I have a whole class I want to serialize for? Where do I get to specify the serialization type for those two List<> types so that I can just pass the whole object in and get a serialized output? Is that even possible?
From the doc it seems that if you serialize a complete object with toJson(...), it deals with the generics attributes properly.
toJson(Object)
"Note that this method works fine if the any of the object fields are of generic type, just the object itself should not be of a generic type"
What output did you get with your object ?