XmlElement ignored by Jackson during serialization - java

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!

Related

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 object mapper how to ignore JsonProperty annotation?

I have the following scenario:
public class A {
#JsonProperty("member")
private Integer Member;
}
public class B {
private Integer Member;
}
Now, I wish to do the following:
ObjectMapper mapper = new ObjectMapper();
B b = new B(); b.setMember(1);
A a = mapper.converValue(b, A.class);
Ordinarily, this would work. However, since the objectMapper takes annotations such as #JsonProperty into account, I get the following result:
A.getMember(); // Member = NULL
There is a workaround, where all fields that are expected to be null due to this are set manually, i.e. A.setMember(b.getMember());, but this defeats the purpose of using the objectMapper in the first place and is potentially error-prone.
Is there a way to configure the objectMapper to ignore the #JsonProperty fields of a given class (or globally)?
You can configure the ObjectMapper to ignore annotations like #JsonProperty by doing:
ObjectMapper objectMapper = new ObjectMapper().configure(
org.codehaus.jackson.map.DeserializationConfig.Feature.USE_ANNOTATIONS, false)
.configure(org.codehaus.jackson.map.SerializationConfig.Feature.USE_ANNOTATIONS, false)
But this will cause it to also ignore things like #JsonIgnore etc. I'm not aware of any way to make the ObjectMapper ignore only specific annotations.
To ignore all annotations the syntax in Jackson version 2.x is:
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false)
Just ignoring a subset seems to be not possible with this approach.
But a much better solution can be found in this answer: https://stackoverflow.com/a/55064740/3351474
For your needs it should be then:
public static class IgnoreJacksonPropertyName extends JacksonAnnotationIntrospector {
#Override
protected <A extends Annotation> A _findAnnotation(Annotated annotated, Class<A> annoClass) {
if (annoClass == JsonProperty.class) {
return null;
}
return super._findAnnotation(annotated, annoClass);
}
}
...
mapper.setAnnotationIntrospector(new IgnoreJacksonPropertyName());

Unable to serialize Object to Json using Jackson

I'm trying to serialize an object in Java using Jackson, but when I'm trying to serialize it, it gives me this error:
No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer
I tried this post, but it didn't help.
Here is the class I'm trying to serialize:
public class Repository {
public String name;
#JsonIgnore // to avoid recursive calls
public ArrayList<UserRole> contributors = new ArrayList<UserRole>();
public User self;
public ArrayList<FileInfo> files;
public RepositoryType repositoryType;
public String path;
}
I also tried to create getters/setters for each field but still nothing.
Here is my serialization method:
public static String convertObjectToJson(Object object) throws IOException {
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = objectWriter.writeValueAsString(object); //error on this line
return json;
}
Looks like your one of your classes has java.io.FileDescriptor reference.
By default, Jackson will only work with with fields that are either public, or have a public getter methods – serializing an entity that has all fields private or package private will fail
If you look at the source code of java.io.FileDescriptor you can see
there are private fields without public getters.
You should configure your objectMapper visibility to allow access to private fields also.
// For jackson 2.*
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
// For jackson lower than 2
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
I was facing problems to send objects to Thymeleaf template with ResponseEntity it was giving me exception "StackOverFlowError" while serializing and your note " #JsonIgnore // to avoid recursive calls" solved my problem. Thanks

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);

How to (De)serialize field from object based on annotation using Jackson?

I need to configure Jackson in a specific way which I'll describe below.
Requirements
Annotated fields are serialized with only their id:
If the field is a normal object, serialize its id
If the field is a collection of objects, serialize an array of id
Annotated fields get their property names serialized differently:
If the field is a normal object, add "_id" suffix to property name
If the field is a collection of objects, add "_ids" suffix to property name
For the annotation I was thinking something like a custom #JsonId, ideally with an optional value to override the name just like #JsonProperty does
The id property should be defined by the user, either using:
The already existing Jackson's #JsonIdentityInfo
Or by creating another class or field annotation
Or by deciding which annotation to inspect for id property discoverability (useful for JPA scenarios, for example)
Objects should be serialized with a wrapped root value
Camel case naming should be converted to lower case with underscores
All of this should be deserializable (by constructing an instance with just the id setted)
An example
Considering these POJO's:
//Inform Jackson which property is the id
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public abstract class BaseResource{
protected Long id;
//getters and setters
}
public class Resource extends BaseResource{
private String name;
#JsonId
private SubResource subResource;
#JsonId
private List<SubResource> subResources;
//getters and setters
}
public class SubResource extends BaseResource{
private String value;
//getters and setters
}
A possible serialization of a Resource instance could be:
{
"resource":{
"id": 1,
"name": "bla",
"sub_resource_id": 2,
"sub_resource_ids": [
1,
2,
3
]
}
}
So far...
Requirement #5 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
And then using #JsonRootName("example_root_name_here") in my POJO's.
Requirement #6 can be accomplished by configuring ObjectMapper in the following way:
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
As you can see there are still lots of requirements to fulfill. For those wondering why I need such a configuration, it's because I'm developing a REST webservice for ember.js (more specifically Ember Data).
You would appreciate very much if you could help with any of the requirements.
Thanks!
Most (all?) of your requirements can be accomplished through the use of a contextual serializer. Taking one answer from ContextualDeserializer for mapping JSON to different types of maps with Jackson and Jackson's wiki (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) I was able to come up with the following.
You need to start with the #JsonId annotation, which is the key indicating a property needs to only use the Id property.
import com.fasterxml.jackson.annotation.*;
import java.lang.annotation.*;
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation // important so that it will get included!
public #interface JsonId {
}
Next is the actual ContextualSerializer, which does the heavy lifting.
import com.fasterxml.jackson.databind.ser.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.core.*;
import java.io.*;
public class ContextualJsonIdSerializer
extends JsonSerializer<BaseResource>
implements ContextualSerializer/*<BaseResource>*/
{
private ObjectMapper mapper;
private boolean useJsonId;
public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); }
public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) {
this.mapper = mapper;
this.useJsonId = useJsonId;
}
#Override
public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException
{
if ( useJsonId ) {
jgen.writeString(br.getId().toString());
} else {
mapper.writeValue(jgen, br);
}
}
#Override
public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property)
throws JsonMappingException
{
// First find annotation used for getter or field:
System.out.println("Finding annotations for "+property);
if ( null == property ) {
return new ContextualJsonIdSerializer(mapper, false);
}
JsonId ann = property.getAnnotation(JsonId.class);
if (ann == null) { // but if missing, default one from class
ann = property.getContextAnnotation(JsonId.class);
}
if (ann == null ) {//|| ann.length() == 0) {
return this;//new ContextualJsonIdSerializer(false);
}
return new ContextualJsonIdSerializer(mapper, true);
}
}
This class looks at BaseResource properties and inspects them to see if the #JsonId annotation is present. If it is then only the Id property is used, otherwise a passed in ObjectMapper is used to serialize the value. This is important because if you try to use the mapper that is (basically) in the context of the ContextualSerializer then you will get a stack overflow since it will eventually call these methods over and over.
You're resource should look something like the following. I used the #JsonProperty annotation instead of wrapping the functionality in the ContextualSerializer because it seemed silly to reinvent the wheel.
import java.util.*;
import com.fasterxml.jackson.annotation.*;
public class Resource extends BaseResource{
private String name;
#JsonProperty("sub_resource_id")
#JsonId
private SubResource subResource;
#JsonProperty("sub_resource_ids")
#JsonId
private List<SubResource> subResources;
//getters and setters
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public SubResource getSubResource() {return subResource;}
public void setSubResource(SubResource subResource) {this.subResource = subResource;}
public List<SubResource> getSubResources() {return subResources;}
public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;}
}
Finally the method that performs the serialization just creates an additional ObjectMapper and registers a module in the original ObjectMapper.
// Create the original ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a clone of the original ObjectMapper
ObjectMapper objectMapper2 = new ObjectMapper();
objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
// Create a module that references the Contextual Serializer
SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null));
// All references to SubResource should be run through this serializer
module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2));
objectMapper.registerModule(module);
// Now just use the original objectMapper to serialize

Categories