Overriding #JsonDeserialize and #JsonSerialize Behaviour - java

I have a Spring(4.1.6) MVC project, properties annotated with #JsonDeserialize and #JsonSerialize in class Foo are working fine. Foo is used within a RestController hence managed by Rest calls.
Foo is packed within a common module hence need to be reused in other modules.
Something like:
-Web
--Common
-Services
--Common(Common is used in both)
we will be using ObjectMapper for conversion in Services module.But some how we need to override behavior so that #JsonDeserialize and #JsonSerialize are ignored in Services module and we get values as is.
One option I can think of is creating new bean extends Foo and overriding annotated properties.
Any pointers to other simple way of doing the same?

I got a solution within Mixin provided by jackson.
We can override Annotations using mixin as shown below for Deserialization(Serialization would be similar):
Step 1. Create a DummyDateDeSerializer, where we can write custom logic for date parsing.In my case I returned date as received.
Step 2. Create a Mixin Class defining properties for which anootations should be overriden.
public abstract class DateMixin {
#JsonDeserialize(using=com.test.jackson.DummyDateDeSerializer.class)
public abstract Date getLastModifiedDate() ;
#JsonDeserialize(using=com.test.jackson.DummyDateDeSerializer.class)
public abstract Date getCreatedDate() ;
}
Step 3.Create a DummyDateModule
public class DummyDateModule extends SimpleModule {
public DummyDateModule() {
super("DummyDateModule", new Version(0, 0, 1, null));
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Foo.class, DateMixin.class);
}
}
Step 4. Register mdoule
private static void updateMapper(ObjectMapper mapper){
mapper.registerModule(new DummyDateModule());
}
This will override any #JsonDeserialize defined in Foo or its super class(es) with DummyDateDesrializer for properties createdDate and lastModifiedDate.

Related

Use a custom mapper inside another custom mapper with mapstruct (in default method)

I want to use MapperB inside MapperA's default method
Similar to this question:
How can I use another mapping from different class in mapstruct
However afaik this question did not ask for 'custom mappers', i.e. mappers that exist as their own interface.
How would I be able to do that?
I have an interface of MapperA and an interface of MapperB
How would I use the MapperB inside MapperA?
like so:
#Mapper
public interface MapperA {
#Autowired
MapperB mapperB;
default ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
The above code didn't work. Now I tried adding #Component(to MapperA & MapperB) to be able to autowire it, but it's still giving me:
#Autowired <- Field injection is not recommended
MapperB mapperB; <- Variable 'audioMapper' might not have been initialized
even after maven-cleaning the project to get rid of the MapperAImpl.
You should define the MapperA as an abstract class instead of an interface, and use setter injection to inject MapperB as follows:
#Mapper(componentModel="spring")
public abstract class MapperA {
private MapperB mapperB;
#Autowired
public final void setMapperB(MapperB mapperB) {
this.mapperB = mapperB;
}
public ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
}

How to implements polymorphic serialization with out annotation and mixin

In jackson, we can uses the annotations
#JsonTypeInfo
#JsonSubTypes
#JsonSubTypes.Type
to implement polymorphic serialization.
We can choose to
Use these annotations on data model directly, this is the simplest way.
Use these annotations on mixin. Here is a link about it Polymorphic deserialization in Jackson without annotations.
Both of these two solutions have a problem: All the sub classes must be known when writing code.
In GraphQL
The discriminator field is fixed: "__typename"
The sub type names are fixed too: Simple name of java classes
All the requirements are fixed, that means it unnecessary to configure sub types one by one, it's possible to create a jackson module to handle them automatically.
// An empty interface
// Developers need not to configure polymorphic metadata for any class of its subtypes
public interface GraphQLObject {}
public class BookStore implements GraphQLObject {
public List<Book> getBooks() {...}
...other gettes/setters...
}
public abstract class Book implements GraphQLObject {
... some properties ...
}
public class ElectronicBook extends Book {
... some properties ...
}
public class PaperBook extends Book {
... some properties ...
}
The usage code looks like this
BookStore store = ...;
ObjectMapper mapper = new ObjectMapper();
mapper.addModule(new GraphQLModule());
System.out.println(mapper.writeValueAsString(store));
Here, we need to create "GraphQLModule", it can handle all the sub types implement the empty interface "GraphQLObject", and tell jackson how to use the simple class name of each subtype to be the value of discriminator field "__typename"
The result should looks like:
{
name: "store",
books: [
{ __typename: "ElectronicBook", name: "book-1" },
{ __typename: "PaperBook", name: "book-2" }
]
}
Is it possible to implement the "GraphQLModule"?
Note:
Like the default polymorphic behavior of jackson, discriminator field only need to be added when the object runtime type is different with the generic type argument of list which is known when compile.
I found the reason.
I try to defined customer serializer, but I found "serializeWithType" is never called.
In my project, data type is interface. I use ASM to generate its bytecode. I only generated the simplest bytecode and ignored the signature for generic.
So, in the inteface, it's List<Book>
But, in my bytecode implementation, it's List
It is possible to implement the "GraphQLModule" module extending the SimpleModule class:
public class GraphQLModule extends SimpleModule {
public GraphQLModule() {
this.addSerializer(new GraphQLSerializer());
}
}
I added inside the module a new serializer that extends the StdSerializer class:
public class GraphQLSerializer extends StdSerializer<GraphQLObject> {
public GraphQLSerializer() {
super(GraphQLObject.class);
}
#Override
public void serialize(GraphQLObject obj, JsonGenerator jg, SerializerProvider sp) throws IOException {
jg.writeStartObject();
jg.writeStringField("__typename", obj.getClass().getSimpleName());
jg.writeEndObject();
}
}
The GraphQLSerializer serializer simply takes your object implementing your GraphQLObject interface and serialize it including in the json just the classname string of the object as a __typename.
So you can add register this module to your objectMapper and use it like in this example :
public interface GraphQLObject {}
public abstract class Book implements GraphQLObject {}
public class ElectronicBook extends Book {}
public class PaperBook extends Book {}
ObjectMapper objectMapper = new ObjectMapper();
mapper.registerModule(new GraphQLModule());
List<Book> books = List.of(new ElectronicBook(), new PaperBook());
//it will print [{"__typename":"ElectronicBook"},{"__typename":"PaperBook"}]
System.out.println(mapper.writeValueAsString(books));

Use JsonIgnore for an attribute in an other class

I meet an issue with a class contained in a library that I use.
This issue comes when I want deserialize it.
Indeed, this class has a method names "getCopy" which returns a new instance of himself which contains this same method and call it still a StackOverFlowException on the following cycle :
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:723)
public class Object {
...
ObjectAttribute objectAttribute;
...
public ObjectAttribute getObjectAttribute(){
return this.objectAttribute
}
...
}
public class ObjectAttribute{
...
public ObjectAttribute getCopy{
return copy(this) //return a new instance of himself
}
...
}
Is there a way to ignore the method getCopy() like #JsonIgnoreAttribute("objectProperty.copy")?
For this specific use case, when you have a class in a third party library that you are not able to modify, Jackson provides the Mix-in annotations.
The idea behind this concept is to provide a class that indicates how the serialization of another class should be accomplished.
For instance, consider the following mix-in class definition for your use case:
public abstract class ObjectAttributeMixIn{
// You need to provide definitions for every property you need
// to serialize, and the proper constructor if necessary
...
// Ignore the getCopy method
#JsonIgnore
public abstract ObjectAttribute getCopy();
...
}
You can use the full set of Jackson annotations in the mix-in definitions.
Then, associate the mix-in with the ObjectAttribute class. You can use the instance of ObjectMapper you are using for serialization for this purpose:
objectMapper.addMixInAnnotations(ObjectAttribute.class, ObjectAttributeMixIn.class);
Yon can also register a custom module instead; please, see the relevant documentation.
for ignore method getCopy, just enough rename this method , e.g copy
every method start with get then serialized ,e.g if method name is getSomething then serialized to something: (return value by method))
so if you change method name to copy or copyInstance or every name without start by get then method not serialized
You can override JsonSerializer and do specific logic for class
public class CustomSerializerForC extends JsonSerializer<C> {
#Override
public Class<C> handledType() {
return C.class;
}
#Override
public void serialize(C c, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
String upperCase = c.getValue().toUpperCase();
jsonGenerator.writeString(upperCase);
}
}
And use Serializer in moudle used in ObjectMapper:
SimpleModule module = new SimpleModule("MyCustomModule", new Version(1, 0, 0, null));
module.addSerializer(new CustomSerializerForC());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
There are 2 ways I see how to figure out your issue:
Write custom deserializer for you specific class and register it in Jackson mapper.
Tune up global Jackson mapper to ignore class getters in auto-detection and use only fields.
Please try 2 way with following config:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(JsonMethod.ALL, Visibility.NONE);
mapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
If you decide to move forward with 1 way, please write here if you need help.
You can register serializer and choose the fields you would like
/**
* We can not change source code so we are adding serializer for a specific type.
*
*/
public static class JsonSpecificTypeSerializer extends JsonSerializer<SpecificType> {
#Override
public void serialize(SpecificType t, JsonGenerator jsonGen, SerializerProvider serializerProvider) throws IOException {
jsonGen.writeStartObject();
jsonGen.writeFieldName("field1");
jsonGen.writeNumber(t.getield1());
.......
jsonGen.writeEndObject();
}
}
/**
* Customize jackson.
*
* adding configuration to jackson without overriding spring boot default conf.
*/
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizeJackson() {
return jacksonObjectMapperBuilder -> {
jacksonObjectMapperBuilder.serializerByType(SpecificType.class,
new JsonSpecificTypeSerializer());
};
}

Allowing static method to access DAO

I'm building a web application using Spring MVC which parses JSON requests into POJOs using #RequestBody/Jackson.
When Jackson creates a POJO, I cannot autowire my DAO service so instead I have created a way to access the DAO statically via a utility method.
private static DAOService daoService;
public static User getUserById(int id)
{
return daoService.getUserDao().getById(id);
}
I have spring populate the daoService on application startup which is just a holder for my DAOs.
I do this because my entities that Jackson creates need to retrieve other child entities from the database to complete itself.
This seems to be working but I'm concerned as to whether or not this is safe. Can anyone foresee any issues with this?
I'm assuming it's safe since daoService is never mutated, and the getById method only acts on its own arguments.
Thanks
Edit:
public void setSlot(int id) {
this.slot = EntityUtils.getSlotById(id);
}
You proposal is valid and safe.
If you want to keep your bean clean of the deserialization process you may create a Jackson converter to convert from Long to your Bean. It requires a bit of plumber but it may worth it:
First annotate your field with a custom converter:
public class Foo {
#JsonDeserialize(converter = SlotConverter.class)
public void setSlot(Slot slot) {
this.slot = slot;
}
}
Then define the converter with the SlotDao annotated with #Autowired. The converter converts from Long to Slot:
public class SlotConverter extends StdConverter<Long, Slot> {
#Autowired
private SlotDao slotDao;
#Override
public Slot convert(Long id) {
return slotDao.getSlotById(id);
}
}
Finally, jackson has to be configured with a custom Spring instanciator. Thus SlotConverter will be instanciated and configured by Spring:
ObjectMapper mapper = new ObjectMapper();
mapper.setConfig(mapper.getDeserializationConfig().with(new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory())));
The following code will deserialize Foo using the slot id:
Foo foo = mapper.readValue("{\"slot\":10}", Foo.class);
Hope it helps!

How to use Spring MVC #JsonView when returning an object hierarchy from a Rest Controller

I'm building an application which uses Spring MVC 4.10 with jackson 2.3.2.
I have a Project class which has children Proposal objects and a Customer object. These Proposal objects are complex and I want to return a summarized JSON view of them. A similar situation happens with the Customer object. I'm trying to implement this with #JsonView annotations.
I wanted to ask if extending the views of the member object classes in the container object class view is the way to do this or, if not, if there is a cleaner way to implement this that I am unaware of.
Context
Before today, I was under the false impression that you could annotate your controller with multiple views and that the resulting JSON representation would be filtered accordingly.
#JsonView({Project.Extended.class, Proposal.Summary.class, Customer.Summary.class})
#Transactional
#RequestMapping(value="/project", method=RequestMethod.GET)
public #ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
Where each class had its own JsonView annotations and interfaces
e.g.:
public class Customer {
...
public interface Summary {}
public interface Normal extends Summary {}
public interface Extended extends Normal {}
}
Nevertheless, it is only the first view in the array that gets taken into account. According to https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
Only one class or interface can be specified with the #JsonView
annotation, but you can use inheritance to represent JSON View
hierarchies (if a field is part of a JSON View, it will be also part
of parent view). For example, this handler method will serialize
fields annotated with #JsonView(View.Summary.class) and
#JsonView(View.SummaryWithRecipients.class):
and the official documentation in http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-jsonview
To use it with an #ResponseBody controller method or controller
methods that return ResponseEntity, simply add the #JsonView
annotation with a class argument specifying the view class or
interface to be used:
So, I ended up extending the views of the members in the view of the container object, like this
#Entity
public class Project {
...
public static interface Extended extends Normal, Proposal.Extended {}
public static interface Normal extends Summary, Customer.Normal {}
public static interface Summary {}
}
and changed my controller to this
#JsonView(Project.Extended.class)
#Transactional
#RequestMapping(value="/project", method=RequestMethod.GET)
public #ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
This seems to do the trick, but I couldn't find documentation or discussion about this situation. Is this the intended use of JsonViews or is it kind of hackish?
Thank you in advance
-Patricio Marrone
I believe you have configured your views as necessary. The root of the issue is not Spring's #JsonView, but rather Jackson's implementation of views. As stated in Jackson's view documentation:
Only single active view per serialization; but due to inheritance of Views, can combine Views via aggregation.
So, it appears that Spring is simply passing on and adhering to the limitation set in place by Jackson 2.
I use Jersey+Jackson but issued just the same problem.
That's a trick that I'm doing for my application to let me require for several JSON Views during serialization. I bet it is also possible with Spring MVC instead of Jersey, but not 100% sure. It also does not seem to have performance issues. Maybe it is a bit complicated for your case, but if you have large object with big amount of possible views, maybe it's better than doing a lot of inheritance.
So I use the Jackson Filter approach to require several views in serialization. However, I haven't found the way to overcome the issue of putting #JsonFilter("name") above the classes to map, which does not make it so clean. But I mask it in custom annotation at least:
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonFilter(JSONUtils.JACKSON_MULTIPLE_VIEWS_FILTER_NAME)
public #interface JsonMultipleViews {}
The filter itself looks like this:
public class JsonMultipleViewsFilter extends SimpleBeanPropertyFilter {
private Collection<Class<?>> wantedViews;
public JsonMultipleViewsFilter(Collection<Class<?>> wantedViews) {
this.wantedViews = wantedViews;
}
#Override
public void serializeAsField( Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer ) throws Exception {
if( include( writer ) ) {
JsonView jsonViewAnnotation = writer.getAnnotation(JsonView.class);
// serialize the field only if there is no #JsonView annotation or, if there is one, check that at least one
// of view classes above the field fits one of required classes. if yes, serialize the field, if no - skip the field
if( jsonViewAnnotation == null || containsJsonViews(jsonViewAnnotation.value()) ) {
writer.serializeAsField( pojo, jgen, provider );
}
}
else if( !jgen.canOmitFields() ) {
// since 2.3
writer.serializeAsOmittedField( pojo, jgen, provider );
}
}
private boolean containsJsonViews(Class<?>[] viewsOfProperty) {
for (Class<?> viewOfProperty : viewsOfProperty) {
for (Class<?> wantedView : wantedViews) {
// check also subclasses of required view class
if (viewOfProperty.isAssignableFrom(wantedView)) {
return true;
}
}
}
return false;
}
#Override
protected boolean include( BeanPropertyWriter writer ) {
return true;
}
#Override
protected boolean include( PropertyWriter writer ) {
return true;
}
}
I can use this filter like this:
public static String toJson( Object object, Collection<Class<?>> jsonViewClasses) throws JsonProcessingException {
// if no json view class is provided, just map without view approach
if (jsonViewClasses.isEmpty()) {
return mapper.writeValueAsString(object);
}
// if only one json view class is provided, use out of the box jackson mechanism for handling json views
if (jsonViewClasses.size() == 1) {
return mapper.writerWithView(jsonViewClasses.iterator().next()).writeValueAsString(object);
}
// if more than one json view class is provided, uses custom filter to serialize with multiple views
JsonMultipleViewsFilter jsonMultipleViewsFilter = new JsonMultipleViewsFilter(jsonViewClasses);
return mapper.writer(new SimpleFilterProvider() // use filter approach when serializing
.setDefaultFilter(jsonMultipleViewsFilter) // set it as default filter in case of error in writing filter name
.addFilter(JACKSON_MULTIPLE_VIEWS_FILTER_NAME, jsonMultipleViewsFilter) // set custom filter for multiple views with name
.setFailOnUnknownId(false)) // if filter is unknown, don't fail, use default one
.writeValueAsString(object);
}
After that, Jersey allows us to add Jersey Filters on the point of running the application (it goes through each endpoint in each Controller in start of application and we can easily bind the Jersey filters at this moment if there is is multiple value in #JsonView annotation above the endpoint).
In Jersey filter for #JsonView annotation with multiple value above endpoint, once it's bint on startup to correct endpoints depending on annotations, we can easily override the response entity with calling that utils method
toJson(previousResponeObjectReturned, viewClassesFromAnnoation);
No reason to provide the code of Jersey Filter here since you're using Spring MVC. I just hope that it's easy to do it the same way in Spring MVC.
The Domain Object would look like this:
#JsonMultipleViews
public class Example
{
private int id;
private String name;
#JsonView(JsonViews.Extended.class)
private String extendedInfo;
#JsonView(JsonViews.Meta.class)
private Date updateDate;
public static class JsonViews {
public interface Min {}
public interface Extended extends Min {}
public interface Meta extends Min {}
//...
public interface All extends Extended, Meta {} // interfaces are needed for multiple inheritence of views
}
}
We can ommit putting Min.class in my case on those fields that are always required not depending on view. We just put Min in required views and it will serialize all fields without #JsonView annotation.
View All.class is required for me since if we have, for example, a specific set of views for each domain class (like in my case) and then we need to map a complex model consisting of several domain objects that both use views approach - some view for object one, but all views for object two, it's easier to put it above endpoint like this:
#JsonView({ObjectOneViews.SomeView.class, ObjectTwoViews.All.class})
because if we ommit ObjectTwoViews.All.class here and require for only ObjectOneViews.SomeView.class, those fields that are marked with annotation in Object Two will not be serialized.

Categories

Resources