How do you implement in Jackson a conversion from json to Java objects, based on class types specified in the json.
Example Java Types:
public class Car{
public String make;
public String model;
}
public class Spoon {
public String material;
}
public class Ownership {
public List<Object> items;
public User owner;
}
Example Json:
{
"items": [
{
"#class": "com.example.Car",
"make": "Mercedes-Benz",
"model": "S500"
},
{
"#class": "com.example.Spoon",
"material": "silver"
}
],
"owner": {
"name": "John"
}
}
Since the number of classes is unknown (users can add any class) it is not possible to use the annotation #JsonSubTypes.
In addition, the json may contain known strongly types classes, like the object User in the example which is serialized using the standard Jackson implementation.
Most of the examples I can find, such as http://www.baeldung.com/jackson-inheritance assume the number of subclasses is known, but in my case it is not, users of the framework will add their own.
Ideally the implementation will just resolve types and let Jackson do the rest of the serialization without repeating that code.
Can be solved using an annotation on the collection:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
public List<Object> items;
Related
I want to use ExtendedScalars.Json from the graphql-java-extended-scalars package in my application which utilizes the quarkus-smallrye-graphql however I am struggling doing so.
Given a Test model class like
public class Test {
public String name;
public Object description;
}
with following GraphQLApi
#GraphQLApi
public class API {
#Inject
SomeService someService;
#Query
public Test getTest() {
return someService.getTest();
}
}
where Test might be described by
{
"name": "Test",
"description": {
"hello": "world"
}
}
the field description should be treated as ExtendedScalars.Json, so the result of this GraphQL query
{
test {
description
}
}
should be exactly
{
"data": {
"test": {
"description": {
"hello": "world"
}
}
}
}
However the #ToScalar annotation does not take ExtendedScalars.Json and falling back to graphql-java with something like public GraphQLSchema.Builder addScalar(#Observes GraphQLSchema.Builder builder) { ... } to manually replace the Type and QueryType leads to a AssertException like
graphql.AssertException: All types within a GraphQL schema must have unique names. No two provided types may have the same name.
No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).
You have redefined the type 'Query' from being a 'GraphQLObjectType' to a 'GraphQLObjectType'
Any clue how to handle this?
In a Spring Boot application, I have a DTO class called Resource.java containing a list of strings. Inside a GET REST webservice when I return the Resource object if the list has the size equal to 1, I want it to be deserialized directly as the String itself and not as a list with one element.
I have already taken care of the serialization for this case by using #JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
Is there anything which does a similar job for deserialization?
#Data
public class Resource {
private String id;
private String name;
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
#JsonAlias({"foo", "foos"})
private List<String> foos;
}
Current:
{
"id": "22",
"name": "tom",
"hookUrls": [
"http:blabla"
]
}
Desired:
{
"id": "22",
"name": "tom",
"hookUrls": "http:blabla"
}
Enable the WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED feature which behaves exactly like you described. See here.
You will need to create your own ObjectMapper for this. To override Spring's default ObjectMapper just define a #Bean in a #Configuration class that returns the ObjectMapperwith the given feature enabled.
I am consuming the public API for crypto currencies in Mexico: https://api.bitso.com/v3/available_books/ that returns a json like this one:
"success": true,
"payload": [
{
"book": "btc_mxn",
"minimum_price": "500.00",
"maximum_price": "16000000.00",
"minimum_amount": "0.000075",
"maximum_amount": "500.00000000",
"minimum_value": "5",
"maximum_value": "10000000.00"
},
{
"book": "eth_btc",
"minimum_price": "0.00000100",
"maximum_price": "5000.00000000",
"minimum_amount": "0.00000100",
"maximum_amount": "1000.00000000",
"minimum_value": "0.00000100",
"maximum_value": "2000.00000000"
},
and the code that consumes it with Webclient is:
#Override
public Mono<Coins> getCoins() {
return webClient.get().uri("https://api.bitso.com/v3/available_books/")
.accept(MediaType.APPLICATION_JSON)
.retrieve().bodyToMono(Coins.class);
}
The POJOs that are trying to bind it are:
#Data
public class Coins {
#JsonProperty("success")
private String success;
#JsonProperty("playload")
private List<Coin> playload;
and
#Data
public class Coin {
#JsonProperty("book")
private String book;
#JsonProperty("minimum_amount")
private String minimumAmount;
#JsonProperty("maximum_amount")
private String maximumAmount;
#JsonProperty("minimum_price")
private String minimumPrice;
#JsonProperty("maximum_price")
private String maximumPrice;
#JsonProperty("minimum_value")
private String minimumValue;
#JsonProperty("maximum_value")
private String maximumValue;
So far, it only maps like this
"success": true,
"payload": null
You need to have no-args construct and change the word playload to payload :)
I don't think this is a WebFlux issue, but rather a Jackson + Lombok issue.
What happens if you try to deserialize that payload with raw ObjectMapper?
I think Jackson requires an all args constructor annotated with #JsonCreator or ask Lombok to create a #NoArgConstructor for that class. In any case, rewriting your Coin class as a regular Java class should work.
Also, your Coins class has a typo since it's trying to get playload instead of payload.
FIXED: Typo at property name playload instead of payload
I've a parent DAO:
#XmlRootElement//(name="metadata")
public class FolderAttributes {
private Map nameValueForListValue;
Child DAO:
#XmlAccessorType(XmlAccessType.FIELD)
public class ListWrapper {
#XmlElementWrapper(name = "attrValue")
private List<Object> list;
JSON request that works (if I use "metadata" name as root element):
"metadata": {
"nameValueForListValue": {
"signed": {
"attrValue": [
"ahsdfhgakjf"
]
},
"id": {
"attrValue": [
"12345678",
"87654321"
]
},
.......... continues
I don't want the tag "nameValueForListValue" in request, instead it should be smart enough to read rest of the values without that tag. Looks like it always needs to have the param name "nameValueForListValue" on the request. Is there any annotations that will do my job easier? I'm using Java 6 & jackson 1.9.
What about using #JsonAnySetter Jackson annotation
It would be something like:
#XmlRootElement//(name="metadata")
public class FolderAttributes {
private Map nameValueForListValue;
#JsonAnySetter
public void genericSetter(String key, Object value){
nameValueForListValue.put(key, value);
}
}
That whay any unknown field could be handle by this setter.
More info:#JsonAnySetter example
#JsonInclude(JsonInclude.Include.NON_NULL)
So I have a class that I want jackson to serialize.
public class MyClass<T extends MyInterface> {
private T myGeneric;
private String desc;
....
}
public interface MyInterface {
String getSomeString();
}
public class MySubClass implements MyInterface {
private String info;
....
#Override
public getSomeString() {
return "";
}
}
MyClass can have many types of other classes under it's myGeneric field.
The problem is that when I pass my JSON to the server, then Jackson throws an error: problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
I investigated around and mostly only found examples of how to solve jackson problems with abstract classes but none for this kind of problem. I also tried using the #JsonTypeInfo and #JsonSubTypes annotations to list what kind of classes can go under MyClass but I am not sure if what I did was just wrong or not because it is hard to find any similar examples with them and the documentation in here: was not really helpful also.
Is this kind of problem solvable with Jackson annotations or I need to write a custom serializer for my class?
I am testing the serialization like this:
String json = "myJson";
ObjectMapper mapper = new ObjectMapper();
MyClass myClass = mapper.readValue(json, MyClass.class);
Jackson can't deserialize abstract types without additional info: when you have JSON with field
"myGeneric" : { "field1" : 1, "field2" : 2}
you have no idea what is the class of the myGeneric object.
So you have two options: use #JsonTypeInfo annotation or to create custom deserializer. Example:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "#class")
private T myGeneric ;
After that, serialized myGeneric field will look something like that:
"myGeneric" : { "field1" : 1, "field2" : 2, "#class" : "com.project.MySubClass"}
Deserializer will use this info to instantiate an object of correct type