Convert Json to Pojo implementing marker interface - java

I would need to convert JSON to POJO, which implements following marker interface:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.io.Serializable;
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class"
)
public interface MyInterface extends Serializable {
}
this is pojo:
public class MyPojo implements MyInterface
{
private static final long serialVersionUID = 1L;
private String phonetype;
private String cat;
//getters + setters
}
and this is snippet code where I have to use it, but I don't know what I have to do:
String json = "{\"phonetype\":\"N95\",\"cat\":\"WP\"}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);//here I get exception: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class path.to.MyPojo]: missing type id property '#class'
// at [Source: (String)"{"phonetype":"N95","cat":"WP"}"; line: 1, column: 30]
When I don't implement interface into MyPojo, everything works correctly without exception, but I would need implement it.
I use library jackson-databind-2.9.8
I would like to asking you, if you have any suggestion how can I solve it or if does it exist another approach how parse Pojo implementing interface showed above. I mean something missing in my interface but I don't know what.
Thank you so much

You may try to use a custom deserializer as shown in the following link How arbitrary JSON string can be deserialized to java POJO?

Related

#Jacksonized deserialization doesn't work with mixins - "no String-argument constructor/factory method to deserialize from String value"

I'm trying to deserialize an array of MyObject (which uses the builder pattern via Lombok with #Jacksonized) from a csv String containing a non-standard representation of a map as one of the columns.
MyObject:
#JsonPropertyOrder({
"strField",
"mapField",
})
#Getter
#Jacksonized
#Builder(toBuilder = true)
#EqualsAndHashCode
#ToString
public class MyObject {
private final String strField;
#Builder.Default
private final Map<String, Float> mapField = new HashMap<>();
}
Example csv with non-standard mapField representation:
strField,mapField
abc,"key1=2.0;key2=3.0"
I'm using a mixin to try to achieve this without rewriting the entire object:
#JsonPropertyOrder({
"strField",
"mapField",
})
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
private Map<String, Float> mapField;
}
..which, as you can see above, points to a custom deserializer:
public class StringToMapDeserializer extends JsonDeserializer<Map<String, Float>> {
#Override
public Map<String, Float> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String csvFormattedMap = jsonParser.getText().trim();
return Arrays.stream(csvFormattedMap.split(";"))
.map(keyValue -> keyValue.split("="))
.collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> Float.parseFloat(keyValue[1])));
}
}
And, to wrap it all up, I'm configuring and using my CsvMapper like so:
CsvMapper csvMapper = new CsvMapper();
csvMapper.addMixIn(MyObject.class, MyObjectDeserializerMixin.class);
CsvSchema csvSchema = csvMapper
.schemaFor(MyObject.class)
.withHeader();
ObjectReader csvReader = csvMapper.readerFor(MyObject.class).with(csvSchema);
List<MyObject> myObjects = csvReader.<MyObject>readValues(theCsvString).readAll();
However, I'm getting the following exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of java.util.LinkedHashMap (although at least one
Creator exists): no String-argument constructor/factory method to
deserialize from String value ('key1=2.0;key2=3.0') at [Source:
(StringReader); line: 2, column: 53] (through reference chain:
myPackage.MyObject$MyObjectBuilder["mapField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromString(ValueInstantiator.java:258)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:357)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:193)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1261)
...
The stack trace appears to be trying to use a BuilderBasedDeserializer which tries to use a MapDeserializer.java for the map so it doesn't appear to be aware of my custom deserializer. I've used a very similar workflow with a custom serializer to write the same csv, so I'm confused as to why this doesn't work. What is the next step to troubleshooting this?
When using a builder to deserialize, Jackson only considers the annotations on the builder class, not those on the actual class to deserialize. Lombok's #Jacksonized helps you by automatically copying all relevant annotations to the builder class and its setter methods.
However, Lombok can only do that with annotations statically present on the class. Any dynamic annotation coming from a mixin cannot be copied, because Lombok doesn't know about them.
You could put #JsonDeserialize onto the mapField of the actual class, so that Lombok is able to copy it to the builder. But that obviously runs contrary to the purpose of mixins.
Luckily, there is a better way. You can also add the mixin to the builder class as follows:
csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);
Strictly speaking, you do not need the mixin on MyObject any more. But if you also serialize and there are annotations relevant to serialization in the mixin, you should add the mixin to both.
However, in your case, that's not sufficient, as you are using #Builder.Default. With that annotation, Lombok creates a field named mapField$value in the builder. Jackson will not match the field mapField (and its annotation) from your mixin to that field, as they are named differently.
You can work around that by defining and annotating the setter method in your mixin:
public abstract class MyObjectDeserializerMixin {
#JsonDeserialize(using = StringToMapDeserializer.class)
public abstract void mapField(Map<String, Float> mapField);
}
You can use the actual return type of the builder here, but void is also sufficient. As #JsonDeserialize is only for deserialization purposes, you can safely remove the mapField and its annotation from the mixin class.
Tested with Lombok 1.18.20 and Jackson 2.12.2.

Kafka JSON Deserializer for interfaces

I've got problem similar to this:
Kafka Deserialize Nested Generic Types
In my kafka producer I am sending object that looks like this:
public class ExternalTO implements Serializable
{
private static final long serialVersionUID = 7949808917892350503L;
private List<IExternalData> externalDatas;
public ExternalTO()
{}
}
The cornerstone is this: List<IExternalData> externalDatas.
This interface looks like:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public interface IExternalData
{
String getOne();
}
In my application there can by generated multiple types of IExternalBetData interface implementations (about 10 different). In this case, for instance, my producer generated ExternalTO with inner list of ConcreteExternalData objects. Sent JSON looks like:
{
"externalDatas":
[{"#class":"com.api.external.to.ConcreteExternalData",
"one":false,
"two":false}]
}
Field #class was added because of #JsonTypeInfo annotation, and I thought that this is enough for deserializer to "understend" what type of IExternalData to use in deserialization.
Unfortunately, on the side of kafka listener I am getting the exception:
Cannot construct instance of com.api.external.to.IExternalData (no
Creators, like default construct, exist): abstract types either need
to be mapped to concrete types, have custom deserializer, or contain
additional type information
Consumer looks similar to:
#Service
public class Consumer
{
private final ObjectMapper objectMapper;
public Consumer(ObjectMapper objectMapper)
{
this.objectMapper = objectMapper;
}
#KafkaListener(topics = {"${kafka.topic}"})
public void listen(ConsumerRecord<String, String> record)
{
objectMapper.readValue(record.value(), ExternalTO.class)
}
Please, help to solve this issue with deseriatization.
The deserializer doesn't know, out of all the implementations of IExternalData, to which it should deserialize the consumer record data to. We must resolve that ambiguity.
I was able to resolve this using #JsonDeserialize annotation.
#JsonDeserialize(as = <Implementation>.class
above the declaration of the List
The solution for me was to set property to objectMapper.
ObjectMapper mapper = new ObjectMapper();
// deserializes IExternalData into certain implementation.
mapper.enableDefaultTyping();

Jackson serialization error when class has generics in it

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

XmlElement ignored by Jackson during serialization

i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!

Jackson JSON library: how to instantiate a class with abstract fields that can't access its concrete representation?

This is the same questions than :
Jackson JSON library: how to instantiate a class that contains abstract fields
Nevertheless its solution is not possible since my abstract class is in another project than the concrete one.
Is there a way then ?
EDIT
My architecture is as follows:
public class UserDTO {
...
private LanguageDTO lang;
}
I send that object user :
restTemplate.postForObject(this.getHttpCore().trim() + "admin/user/save/1/" + idUser, userEntity, UserDTO.class);
Then I am supposed to receive it in the function :
#RequestMapping(value = "/save/{admin}/{idUser}", method = RequestMethod.POST)
public String saveUserById(#RequestBody final UserEntity user, #PathVariable Integer idUser, #PathVariable boolean admin)
with UserEntity defined as :
public class UserEntity extends AbstractUserEntity {
...
}
public abstract class AbstractUserEntity {
...
private AbstractLanguageEntity lang;
}
I would like to know how I can specify that lang should be instantiate as LanguageEntity whereas abstract classes are in another project.
This could work assuming you can configure how the object get serialized. See the example here. Look under "1.1. Global default typing" to set the defaults to include extra information in your JSON string, basically the concrete Java type that must be used when deserializing.
Since it seems you need to do this for your Spring servlet, you would have to pass a Spring message converter as mentioned here
Then inside your custom objectMapper, you can do the necessary configuration:
public class JSONMapper extends ObjectMapper {
public JSONMapper() {
this.enableDefaultTyping();
}
}
You could probably also make it work with Mix-ins, which allow you to add annotations to classes already defined. You can see and example here. This will also need to be configured inside the objectMapper.
If you need the same functionality on your client side (REST template), you can pass the object mapper as shown here.
The easiest way to solve that issue is to add getters et setters in UserEntity but specifying a concrete class :
public LanguageEntity getLang() {
return (LanguageEntity) lang;
}
public void setLang(LanguageEntity language){
this.lang = language
}
If all that you want to achieve is to note that LanguageEntity is the implementation of AbstractLanguageEntity, you can register this mapping via module:
SimpleModule myModule = new SimpleModule())
.addAbstractTypeMapping(AbstractLanguageEntity.class,
LanguageEntity.class);
ObjectMapper mapper = new ObjectMapper()
.registerMdoule(myModule);

Categories