I have a controller with #RequestBody
#PostMapping("/")
public String doSomething(#RequestBody ??? foo)
foo can be 2 different objects. Foobject or Barobject.
I don't know what will the object sent.
These objects are totally different without any kind of common fields where I could use
#JsonTypeInfo and #JsonSubTypes
I can workaround this and use ObjectMapper to try to map it to on of the 2 classes:
#RequestBody Object json
...
ObjectMapper objectMapper = new ObjectMapper();
try {
Foobject obj = objectMapper.convertValue(json, Foobject.class);
}
catch (IllegalArgumentException ei){
Barobject obj = objectMapper.convertValue(json, Barobject.class);
}
Is there a proper way to do this? Or better said, alternatives?
I know this goes against REST API development, because this should be another method, each with its own object.
You can choose to use, Object, class like this and then take advantage of the function
mapper.readValue(jsonStr, Fooobject.class);
method throws JsonMappingException
#PostMapping("/")
public String doSomething(#RequestBody Object foo) {...}
boolean isA(String json, Class expected) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json, expected);
return true;
} catch (JsonMappingException e) {
e.printStackTrace();
return false;
}
}
Well you said that "I don't know what will the object sent", so you can only do trial and error.
Else modify your request to involve a "Type" or something (I would do this by Making a parent class with only "type" member of ObjectType type, and child classes extending that parent class.
enum ObjectType {FOO, BAR}
Method 1: Use readValue() function of jackson
Method 2: Do a string search (just to make it faster) something unique non-nullable field about any of the object.
Method 3: Modify the request.
Managed to do it with Jackson.
Create parent class, and both objects extend from parent.
On the parent added:
#JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
#JsonSubTypes({
#JsonSubTypes.Type(Foobject.class),
#JsonSubTypes.Type(Barobject.class),,
})
This works pretty well, and I guess is a clean solution.
Also, has the advantage to be done on a Spring layer, where other validations defined on fields of the classes will occur.
That wasn't the case with other solutions.
Related
I would like to deserialize a json string to a java object. I wanted to write a generic method.
public class ObjectSerializer<T> {
T t;
private ObjectMapper mapper = new ObjectMapper();
/* public Person deSerial(String json) throws Exception {
TypeReference<Person> typeRef = new TypeReference<Person>() {};
return mapper.readValue(json, typeRef);
} */
public T deSerialize(String jsonInput) throws Exception {
TypeReference<T> typeRef
= new TypeReference<T>() {};
return mapper.readValue(jsonInput, typeRef);
}
}
When I call deSerialize(validPersonJsonString) [validPersonJsonString : valid person JSON String], it is not working, it it throwing me the error:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.Person.
Whereas, when in call the commented deSerial method, it works fine. Please explain the issue.
Thanks.
Jackson doesn't support TypeReference with generic type parameters because of type erasure. Here's the bug about it.
As far as your use case is concerned, you don't need to use ObjectSerializer class at all. You can directly expose ObjectMapper object to your service layer and perform the deserialization.
If you want to shield the json serializing mechanism from your service layer then you can wrap ObjectMapper implementation into another class (like you have done in your code) but then, accept the class type as a method argument rather than a generic type.
I have a ServerEndpoint that will be recieving different JSON formats. Since we are only allowed one message handler per message type, my one decoder will have to convert the message to the corresponding Java objects.
In my decoder, I am trying to convert the message recieved to either SubClassA or SubClassB (which implemets the same interface) using the ObjectMapper class. The ObjectMapper class has a readValue method that requires the type I am trying to map the json to and will throw an exception when it cannot convert to the specified type.
I am currently decoding it like the following, but it is not very elegant.
#Override
public Message decode(String message) throws DecodeException {
ObjectMapper mapper = new ObjectMapper();
try {
SubclassA obj = mapper.readValue(message, SubclassA .class);
return obj;
} catch (IOException e) {
}
try {
SubclassB obj = mapper.readValue(message, SubclassB .class);
return obj;
} catch (IOException e) {
}
throw new DecodeException(message, "Failed to decode message.");
}
What is the best way to decode the JSON string into the corresponding Java object using ObjectMapper?
Use Jackson, you can declare subtypes for the parent object. Your Json will contain #type with with the name of the subtype, look at this post
Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
Jackson is one of the best options to work with JSON in Java.
For polymorphic deserialization, consider the following:
Using the #JsonTypeInfo annotation.
For situations where the #JsonTypeInfo annotation is not suitable, consider using a StdDeserializer. This answer will help you.
I am trying to write a custom deserializer in order to trim down a big set of data I receive from somewhere else. I return a List of custom objects from the deserializer.
My question is, how do I do that, if this is my custom deserializer :
public class MyCustomDeserializer extends JsonDeserializer<List<CustomClass>> { ... }
I certainly can't do this :
final SimpleModule module = new SimpleModule();
module.addDeserializer(List<CustomClass>.class, new MyCustomDeserializer());
Will something like this work ?
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
If this indeed works, I find it a bit confusing and "dangerous" ? Isn't the deserialization done inside the asList method invocation ? So it basically maps a List to an array[] ?
I learned about TypeReference so I can probably use that like so :
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
but I heard it is slower.
I also don't want to create a container for the list, and return that in the deserialization because that means it will be wrapped in another json object, and I simply want my endpoint to produce something like :
[{object1}, {object2}]
// instead of
{"Output" : [{object1}, {object2}]}
EDIT:
It seems that I have misinterpreted how jackson is using my deserializer in both cases :
final List<CustomClass> response = Arrays.asList(objectMapper.readValue(stringBean, CustomClass[].class));
// or
objectMapper.readValue(stringBean, new TypeReference<List<CustomClass>>(){});
It looks like the deserializer is called twice, once for each object in the array. I thought that the entire array would be considered as a whole. To clear the confusion, here is what I mean:
The json I receive and try to deserialize looks like so :
[
{
"Data" : {
"id" : "someId",
"otherThing" : "someOtherThing"
},
"Message" : "OK"
},
{
"Data" : null,
"Message" : "Object not found for id blabla"
}
]
and so I though this is what I would have inside my deserializer, but as I said before it seems that i actually get each "entry" from that array and call it multiple times.
First of all, If you registered your custom deserializer using annotation on the bean CustomClass then the deserializer should handle one instance of CustomClass and not a collection and thus should be defined:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
...
}
}
and now you can use Jackson's type factory to pass the mapper the required type information
JavaType customClassCollection = objectMapper.getTypeFactory().constructCollectionType(List.class, CustomClass.class);
List<CustomClass> beanList = (List<CustomClass>)objectMapper.readValue(stringBean, customClassCollection);
I worked it out by adding a custom deserializer to an attribute in my model class and using JsonDeserialize annotation's contentUsing() method, like so:
#JsonDeserialize(contentUsing = MyCustomDeserializer.class)
private List<CustomClass> customClassObjList;
where MyCustomDeserializer class is a custom Jackson JSON deserializer defined as:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
#Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
...
}
}
These two lines will just do enough.
ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(stringBean);
List<CustomClass> response = objectMapper.convertValue(arrayNode, List.class);
Thank me later!
I am trying to be able to define the following code:
public class MyObject {
private String name;
... // Other attributes
}
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(List<MyObject> myObjects) {
// Do some stuff there
}
}
I know that I need to use:
DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true
to setup correctly my object mapper to be able to accept single value as array on my rest resources. I succeed to setup that part.
My problem with this approach is that the following content is not differentiable:
{
"name": "a name",
... // other attributes
}
and
[{
"name": "a name",
... // other attributes
}]
will result into a list (List) of size one. Then, in the method create(List myObjects), I will not be able to do the difference between the List and the Single Object sent to the Rest Resource.
Then, my question is how to do something like that. The idea is to have only one #POST that accepts both Arrays and Single values?
Ideally, I will get rid of the configuration of the ObjectMapper to avoid letting the possibility to set Single Object into the other level of the JSON document. For example, I do not want to allow that:
{
...
"attributes": {
...
}
}
where normally this format should be mandatory:
{
...
"attributes": [{
...
}]
}
Based on that, I tried to put in place an object wrapper of my List to set if I am able to the difference between the list and the object. With something like that:
public class ObjectWrapper<T> {
private List<T> list;
private T object;
public boolean isObject() {
return list == null;
}
}
with the resource that becomes:
#Path(...)
#Stateless
public class MyRestResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response create(ObjectWrapper myObjects) {
// Do some stuff there
}
}
and trying to put in place the deserialization of my content through the JAX-RS/Jersey/Jackson mechanisms. If I let the solution as it is now, the deserialization fails due to the fact that the JSON format expected is the following:
{
"list": [{
"name": "a name",
... // other attributes
}]
}
Then I tried to write a custom deserializer but I am a bit lost in this task. I have something like that:
public class ObjectWrapperDeserializer<T> extends JsonDeserializer<T> {
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
... // What to put there to deserialize Array or Object
}
}
I just want to deserialize the root level to set the content deserialized into the object wrapper. I also want to keep the feature configured in a class annotated with #ApplicationPath when the configuraiton of the different #Provider are done.
I hope that all the info will give a sufficient picture of what I want to do and what I already tested.
Waiting for suggestion on how to do a resource that accept Arrays or Objects on the same path.
Thanks a lot in advance.
Ok, finally I succeed to put in place a mechanism that do exactly what I am looking for. But, I am not sure if there are negative consequences such the performance or such things.
First, I defined a class that can accept both List or Single Object:
public class RootWrapper<T> {
private List<T> list;
private T object;
}
Then, I need a custom deserializer that is able to know which kind of T type to deserialize and to handle the collection or the single object.
public class RootWrapperDeserializer extends JsonDeserializer<CollectionWrapper<?>> {
private Class contentType;
public RootWrapperDeserializer(Class contentType) {
this.contentType = contentType;
}
#Override
public RootWrapper deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// Retrieve the object mapper and read the tree.
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode root = mapper.readTree(jp);
RootWrapper wrapper = new RootWrapper();
// Check if the root received is an array.
if (root.isArray()) {
List list = new LinkedList();
// Deserialize each node of the array using the type expected.
Iterator<JsonNode> rootIterator = root.getElements();
while (rootIterator.hasNext()) {
list.add(mapper.readValue(rootIterator.next(), contentType));
}
wrapper.setList(list);
}
// Deserialize the single object.
else {
wrapper.setObject(mapper.readValue(root, contentType));
}
return wrapper;
}
}
As far as I know, I try to only deserialize the root level manually and then let Jackson take the next operations in charge. I only have to know which real type I expect to be present in the Wrapper.
At this stage, I need a way to tell Jersey/Jackson which deserializer to use. One way I found for that is to create a sort of deserializer registry where are stored the type to deserialize with the right deserializer. I extended the Deserializers.Base class for that.
public class CustomDeserializers extends Deserializers.Base {
// Deserializers caching
private Map<Class, RootWrapperDeserializer> deserializers = new HashMap<>();
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, DeserializerProvider provider,
BeanDescription beanDesc, BeanProperty property) throws JsonMappingException {
// Check if we have to provide a deserializer
if (type.getRawClass() == RootWrapper.class) {
// Check the deserializer cache
if (deserializers.containsKey(type.getRawClass())) {
return deserializers.get(type.getRawClass());
}
else {
// Create the new deserializer and cache it.
RootWrapperDeserializer deserializer =
new RootWrapperDeserializer(type.containedType(0).getRawClass());
deserializers.put(type.getRawClass(), deserializer);
return deserializer;
}
}
return null;
}
}
Ok, then I have my deserializers registry that create new deserializer only on demand and keep them once created. What I am not sure about that approach is if there is any concurrency issue. I know that Jackson do a lot of caching and do not call every time the findBeanDeserializer once it was called a first time on a specific deserialization context.
Now I have created my different classes, I need to do some plumbing to combine everything together. In a provider where I create the ObjectMapper, I can setup the deserializers registry to the created object mapper like below:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonObjectMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper jacksonObjectMapper;
public JsonObjectMapper() {
jacksonObjectMapper = new ObjectMapper();
// Do some custom configuration...
// Configure a new deserializer registry
jacksonObjectMapper.setDeserializerProvider(
jacksonObjectMapper.getDeserializerProvider().withAdditionalDeserializers(
new RootArrayObjectDeserializers()
)
);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return jacksonObjectMapper;
}
}
Then, I can also define my #ApplicationPath that is my REST application like following:
public abstract class AbstractRestApplication extends Application {
private Set<Class<?>> classes = new HashSet<>();
public AbstractRestApplication() {
classes.add(JacksonFeature.class);
classes.add(JsonObjectMapper.class);
addResources(classes);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
#Override
public Set<Object> getSingletons() {
final Set<Object> singletons = new HashSet<>(1);
singletons.add(new JacksonJsonProvider());
return singletons;
}
private void addResources(Set<Class<?>> classes) {
classes.add(SomeRestResource.class);
// ...
}
}
Now, everything is in place and I can write a REST resource method like that:
#POST
#Path("somePath")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response create(RootWrapper<SpecificClass> wrapper) {
if (wrapper.isObject()) {
// Do something for one single object
SpecificClass sc = wrapper.getObject();
// ...
return Response.ok(resultSingleObject).build();
}
else {
// Do something for list of objects
for (SpecificClass sc = wrapper.getList()) {
// ...
}
return Response.ok(resultList).build();
}
}
That's all. Do not hesitate to comment the solution. Feedbacks are really welcome especially around the way of deserialization process where I am really not sure that it is safe for performance and concurrency.
I need to deserialize the following json:
{
//...
"foo_id":1
//...
}
Into an object of class Foo with its id property set to the foo_id json property.
I need to do this within a custom deserializer.
What is the most easy way to accomplish this?
I was thinking to somehow "transform" the json to
{
//...
"foo_id":{
"id":1
}
//...
}
and then delegate this back to Jackson.
In this case, the object is of type Foo, but there are others which might not be of this class. Also, in this case, that json is a number, but I would like to support if it was a string as well.
So, I need a kind of generic way to do this, that's why I think delegating back to Jackson might be a good idea.
No annotations allowed. Suppose you're already writing the Deserializer for this property.
Take a look at this. Here is a code that I think might help you to get some ideas.
public class MyDeserializer extends JsonDeserializer< Message >
{
#Override
public Message deserialize( JsonParser jp, DeserializationContext arg1 ) throws IOException,
JsonProcessingException
{
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Message> subClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext())
{
Entry<String, JsonNode> element = elementsIterator.next();
String name = element.getKey();
if ("foo_id".equals(name))
{
if(element.getValue().isInt())
subClass = FooInteger.Class;
break;
}
}
if (subClass == null) return null;
return mapper.readValue(root, subClass);
}
}
Have you considered use of mix-in annotations? With Jackson 2.2, you could also use converters to do two-step processing (#JsonDeserialize(converter=MyConverter.class).