Custom annotation based ignore field from serialisation - java

Consider a case that I have 2 instance of Object Mapper.
I want one must exclude fields that are annotated with some custom annotation from serialization
While other mapper includes(ignores annotation)
Like class has 3 fields a,b,c and c is annotated with some annotation (say #IgnoreField)
(Their will n number of class, each will have their Fields that are not meant to be serialized)
Now 1st object mapper o1 must serialize only a and b.
While 2nd object mapper o2 can serialize a,b and c.
This can happen with any class having different fields some of which may be annotated.

You can always implement a custom JsonSerializer and register it with your ObjectMapper.
class Bean {
#Ignore
String a;
String b;
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface Ignore {
}
class BeanWithIgnoredFieldsSerializer extends JsonSerializer<Bean> {
#Override
public void serialize(final Bean value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
try {
for (final Field f : Bean.class.getFields()) {
if (f.isAnnotationPresent(Ignore.class)) {
gen.writeStringField(f.getName(), (String) f.get(value));
}
}
} catch (final Exception e) {
//
}
gen.writeEndObject();
}
}
class BeanModule extends SimpleModule {
BeanModule() {
addSerializer(Bean.class, new BeanWithIgnoredFieldsSerializer());
}
}
void configure(final ObjectMapper om) {
om.registerModule(new BeanModule());
}
Note I have not tested this code, but that is the general idea how you add custom serializers to the OM. Adjust the code within the serialize method however you want.

Try configure SimpleBeanPropertyFilter for different condition.
#JsonFilter("someBeanFilter")
public class SomeBean {
}
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("someBeanFilter",SimpleBeanPropertyFilter.serializeAllExcept("aFild"));
ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);

A distinct non-answer:
This is most likely a terrible idea.
You write code to communicate your intention. When you use that annotation, then you are telling "everybody" that these fields should be ignored.
A human reader looking at your code might spend half a day asking himself later "it says #IgnoreField for a and c , so why the heck are a, and c showing up serialized data?"
In other words, whatever problem you are trying to solve here, the answer is most likely not by hacking your way into ignoring annotations sometimes.
The next best "reasonable" solution might be: to rely on different custom annotations, like #IgnoreAlways and something like #OnlyIncludeForXyz. In other words: clearly express what might happen. Instead of using declarative programming, to then "lie" about what you declared.

Related

Java bean mapper expected capture but is provided object

Please note: even though I mention Dozer in this question, I do believe its really just a pure Java generics question at heart. There may be a Dozer-specific solution out there, but I think anyone with strong working knowledge of Java (11) generics/captures/erasures should be able to help me out!
Java 11 and Dozer here. Dozer is great for applying default bean mapping rules to field names, but anytime you have specialized, custom mapping logic you need to implement a Dozer CustomConverter and register it. That would be great, except the Dozer API for CustomConverter isn't genericized, is monolithic and leads to nasty code like this:
public class MyMonolithicConverter implements CustomConverter {
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
if (sourceClass.isAssignableFrom(Widget.class)) {
Widget widget = (Widget)source;
if (destinationClass.isAssignableFrom(Fizz.class)) {
Fizz fizz = (Fizz)destination;
// write code for mapping widget -> fizz here
} else if (destinationClass.isAssignableFrom(Foo.class)) {
// write code for mapping widget -> foo here
}
... etc.
} else if (sourceClass.isAssignableFrom(Baz.class)) {
// write all the if-else-ifs and mappings for baz -> ??? here
}
}
}
So again: monolithic, not genericized and leads to large, complex nested if-else-if blocks. Eek.
I'm trying to make this a wee bit more palatable:
public abstract class BeanMapper<SOURCE,TARGET> {
private Class<SOURCE> sourceClass;
private Class<TARGET> targetClass;
public abstract TARGET map(SOURCE source);
public boolean matches(Class<?> otherSourceClass, Class<?> otherTargetClass) {
return sourceClass.equals(otherSourceClass) && targetClass.equals(otherTargetClass);
}
}
Then, an example of it in action:
public class SignUpRequestToAccountMapper extends BeanMapper<SignUpRequest, Account> {
private PlaintextEncrypter encrypter;
public SignUpRequestToAccountMapper(PlaintextEncrypter encrypter) {
this.encrypter = encrypter;
}
#Override
public Account map(SignUpRequest signUpRequest) {
return Account.builder()
.username(signUpRequest.getRequestedName())
.email(signUpRequest.getEmailAddr())
.givenName(signUpRequest.getFirstName())
.surname(signUpRequest.getLastName()())
.dob(DateUtils.toDate(signUpRequest.getBirthDate()))
.passwordEnc(encrypter.saltPepperAndEncrypt(signUpRequest.getPasswordPlaintext()))
.build();
}
}
And now a way to invoke the correct source -> target mapper from inside my Dozer converter:
public class DozerConverter implements CustomConverter {
private Set<BeanMapper> beanMappers;
#Override
public Object convert(Object destination, Object source, Class<?> destinationClass, Class<?> sourceClass) {
BeanMapper<?,?> mapper = beanMappers.stream()
.filter(beanMapper -> beanMapper.matches(sourceClass, destinationClass))
.findFirst()
.orElseThrow();
// compiler error here:
return mapper.map(source);
}
}
I really like this design/API approach, however I get a compiler error on that mapper.map(source) line at the very end:
"Required type: capture of ?; Provided: Object"
What can I do to fix this compiler error? I'm not married to this API/approach, but I do like the simplicity it adds over the MyMonolithicConverter example above, which is the approach Dozer sort of forces on you. It is important to note that I am using Dozer elsewhere for simple bean mappings so I would prefer to use a CustomConverter impl and leverage Dozer for this instead of bringing in a whole other dependency/library for these custom/complex mappings. If Dozer offers a different solution I might be happy with that as well. Otherwise I just need to fix this capture issue. Thanks for any help here!
The issue seems to come from the beanMappers. You have a set of mappers of various types. The compiler cannot infer what types the found mapper will have.
You can make the compiler believe you by casting the result and suppress the warning it gives you.
Casting to a <?,?> isn't going to happen, so I've added symbols for the convert method. At least it can then be assumed that when you get a BeanMapper<S,T>, map will indeed return a T upon an S source.
class DozerConverter {
private Set<BeanMapper<Object,Object>> beanMappers;
public <S,T> T convert(S source,
Class<?> destinationClass,
Class<?> sourceClass) {
#SuppressWarnings("unchecked")
BeanMapper<S,T> mapper = (BeanMapper<S,T>) beanMappers.stream()
.filter(beanMapper -> beanMapper.matches(sourceClass, destinationClass))
.findFirst()
.orElseThrow();
return mapper.map(source);
}
}
I'm afraid you're going to have to call it like so:
TARGET-TYPE target = dozerConverter.<SOURCE-TYPE,TARGET-TYPE>convert(...);

Serialization Of Polymorphic Array Failing

I have enabled Polymorphic serialization support by adding annotations on the base class. I am able to seriazlize an individual object successfully and it is writing the type information as part of serialized data. However, the same is not happening if I store the objects in a list and serialize it.
It seems this issue was fixed in 1.6.3 (http://jira.codehaus.org/browse/JACKSON-362)
I am using Jackson 2.3.2 and still facing the issue.
Does somebody know how to fix this?
Code:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY,property = "type")
#JsonSubTypes({#Type(value = Derived.class, name = "derived")})
public abstract class Base {
}
public class Derived extends Base {
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Derived d = new Derived();
d.setField("Name");
Base b = d;
System.out.println(mapper.writeValueAsString(b));
List<Base> list = new ArrayList<Base>();
list.add(d);
System.out.println(mapper.writeValueAsString(list));
}
}
Output:
{"type":"derived","field":"Name"}
[{"field":"Name"}]
Thanks,
Praveen
The answer is at https://github.com/FasterXML/jackson-databind/issues/699
This is due to Java type erasure: when serializing a List, all Jackson see as a type is List (roughly equivalent to List). And since type Object does not have polymorphic type information (annotation), none will be written.
So this is not a bug in Jackson, but an unfortunate feature of Java Type Erasure.
It does not apply to arrays, since they retain element type information (arrays are not generic; arrays of different types are different classes, whereas generic typing is mostly compile-time syntactic sugar).
There are three main ways to deal with this:
pass full generic type using TypeReference (ObjectMapper has method like mapper.writerFor(new TypeReference<List<Base>>() { }).writeValue(....)
Sub-class List to something like public class BaseList extends ArrayList<Base>() { }, and pass that: this type WILL retain type information
Avoid using root-level List and Maps
I personally recommend doing (3), since this avoids all related problems with type erasure.
In my opinion JSON root value should always be a JSON Object, usually serialized to/from POJO.
Approach (2) will however work, and this is what most users do. It does require use of an additional helper class.
Approach (1) may or may not work; problem being that forcing type information does also affect actual value serialization. So while it will add type id, it may result in some properties not being serialized.
This problem can be solved by using arrays, instead of list (since list does type erasure):
for example, your above test-case could be written as:
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Derived d = new Derived();
d.setField("Name");
Base b = d;
System.out.println(mapper.writeValueAsString(b));
List<Base> list = new ArrayList<Base>();
list.add(d);
System.out.println(mapper.writeValueAsString(
list.toArray(new Base[list.size]) // <--This Part
));
}
}
I had the same issue with object array. Object[] doesn't carry type information but individual objects do. It's a shame that jackson doesn't handle that automatically.
Two possible solutions:
1. Typed array serialization works just fine:
Base[] myArray = Base[]{d};
mapper.writeValueAsString(myArray)
this will actually produce expected result as Base[] has type information.
I solved that my issue with custom serializer.
Serializer:
public class ObjectArraySerializer extends StdSerializer<Object[]> {
public ObjectArraySerializer(final Class<Object[]> vc) {
super(vc);
}
#Override
public void serialize(
final Object[] data,
final JsonGenerator gen,
final SerializerProvider provider) throws IOException {
gen.writeStartArray();
for (Object obj : data) {
gen.writeObject(obj);
}
gen.writeEndArray();
}
}
ObjectMapper configuration:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(
Object[].class,
new ObjectArraySerializer(Object[].class));
objectMapper.registerModule(module);

java jackson parse object containing a generic type object

i have the following problem.
I have to parse a json request into an object that contains a generic type field.
EDIT
i have made some tests using a regular class type (so i make it work before i replace it with generic). Now parsing for a single element works great.
The issue is when i need to parse out a list object out of that class.
So i have to inform jackson somehow that my T is of type list instead of just AlbumModel.
Here is what i have tried.
#Override
public ListResponseModel<AlbumModel> parse(String responseBody) throws Exception {
JavaType type = mapper.getTypeFactory().constructParametricType(ResponseModel.class,
AlbumModel.class);
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, type));
}
But the code above doesn't work. what is the solution for something like this?
my generic type in the ListResponseModel is defined like: List<T> data
succeeded like:
public class BaseResponseModel<T> {
#JsonProperty("data")
private T data;
#JsonProperty("paginations")
private PaginationModel pagination;
}
so far i have the following code but it always parses into a Hash.
public class ResponseParser extends BaseJacksonMapperResponseParser<ResponseModel<AlbumModel>> {
public static final String TAG = ResponseParser.class.getSimpleName();
#Override
public ResponseModel<AlbumModel> parse(String responseBody) throws Exception {
return mapper.readValue(responseBody,
mapper.getTypeFactory().constructParametricType(ResponseModel.class, AlbumModel.class));
}
}
public abstract class BaseJacksonMapperResponseParser<T> implements HttpResponseParser<T> {
public static final String TAG = BaseJacksonMapperResponseParser.class.getSimpleName();
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(SerializationConfig.Feature.WRAP_ROOT_VALUE, true);
}
}
I agree with eugen's answer but just wanted to expand on it a bit. The first step is to refactor your parse method so it takes a second argument. Instead of allocating the type reference in your method, you require the caller to pass in a TypeReference instance.
public BaseResponseModel<T> parse(String responseBody, TypeReference<T> ref) throws Exception {
return mapper.readValue(responseBody, ref);
}
Unfortunately your snippet does not show the code which calls parse - so I'll make something up:
BaseResponseParser<Collection<Person>> parser = new BaseResponseParser<Collection<Person>>();
BaseResponseModel<Collection<Person>> result = parser.parse(jsonText, new TypeReference<Collection<Person>>(){});
Notice that when the TypeReference instance is compiled in this case, it a type reference to the real concrete class that we expect.
You could do the same thing passing in a Class at runtime, however TypeReference is a bit more powerful because it even works when type T is a generic collection. There is some magic in the TypeReference implementation that allows it to hold onto type information that would normally be erased.
[update]
Updated to use Collection<Person>. Note - as far as I know as List<Whatever> should work also, but I double checked a project where I was using jackson to deserialize collections. Base class Collection definitely worked so I stayed with that.
Your type T will be "erased" at runtime, so Jackson does not know what is the real type of T and deserializes it to a Map. You need a second parameter to your parse method that will be Class<T> clazz or TypeReference<T> or java.lang.reflect.Type.
EDIT
Small explanation on the magic of TypeReference. When you do new XX() {} you are creating a anonymous class, so if it is a class with typevariables (parameterized if you prefer), new X<List<Y>>() {}, you will be able to retrieve List<Y> as a java Type at runtime. It is very similar as if you had done :
abstract class MyGenericClass<T> {}
class MySpecializedClass extends MyGenericClass<List<Y>> {}
Since you're using Jackson you probably need to create a custom JsonDeserializer or JsonSerializer depending on whether you're handing the response or request. I've done this with Dates because on my response I want a standard view. I'm not 100% positive it will work with a generic field though. Here is an example of what I'm doing:
public class DateSerializer extends JsonSerializer<Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String dateString = dateFormat.format(value);
jgen.writeString(dateString);
}
}
Then I just add it to my class like so:
#JsonSerialize(using = DateSerializer.class)
public Date getModifiedDate() {
return modifiedDate;
}

Serialize one class in two different ways with Jackson

In one of our projects we use a java webapp talking to a MongoDB instance. In the database, we use DBRefs to keep track of some object relations. We (de)serialize with POJO objects using jackson (using mongodb-jackson-mapper).
However, we use the same POJOs to then (de)serialize to the outside world, where our front end deals with presenting the JSON.
Now, we need a way for the serialization for the outside world to contain the referenced object from a DBRef (so that the UI can present the full object), while we obviously want to have the DBRef written to the database, and not the whole object.
Right now I wrote some untested static nested class code:
public static class FooReference {
public DBRef<Foo> foo;
// FIXME how to ensure that this doesn't go into the database?
public Foo getFoo() {
return foo.fetch();
}
}
Ideally I would like a way to annotate this so that I could (de)serialize it either with or without the getFoo() result, probably depending on some configuration object. Is this possible? Do you see a better way of going about doing this?
From looking at options, it seems you can annotate properties to only be shown if a given View is passed to the ObjectMapper used for serialization. You could thus edit the class:
public static class FooReference {
public DBRef<Foo> foo;
#JsonView(Views.WebView.class)
public Foo getFoo() {
return foo.fetch();
}
}
and provide:
class Views {
static class WebView { }
}
and then serialize after creating a configuration with the correct view:
SerializationConfig conf = objectMapper.getSerializationConfig().withView(Views.WebView.class);
objectMapper.setSerializationConfig(conf);
Which would then serialize it. Not specifying the view when serializing with the MongoDB wrapper would mean the method would be ignored. Properties without a JsonView annotation are serialized by default, a behaviour you can change by specifying:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
More info is available on the Jackson Wiki.
There are still other alternatives, too, it turns out: there are Jackson MixIns which would let you override (de)serialization behaviour of parts of a class without modifying the class itself, and as of Jackson 2.0 (very recent release) there are filters, too.
Use a custom JSONSerializer and apply your logic in the serialize method:
public static class FooReference {
public DBRef<Foo> foo;
#JsonSerialize(using = CustomSerializer.class)
public Foo getFoo() {
return foo.fetch();
}
}
public class CustomSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// jgen.writeObjectField ...
}
}

Helper method for #JsonProperty mapping in Jackson JSON

Does Jackson have a helper method to return the #JsonProperty annotation value (i.e., the JSON property key) given a bean field name?
Context:
I'm using Jackson to convert client-supplied JSON into a Bean and then using JSR-303 to validate the bean. When validation fails, I need to report a meaningful error message back to the client. The validation objects reference the bean property; the error message should reference the JSON property. Hence the need to map from one to the other.
You can get quite a bit of information via BeanDescription object, although getting one is pretty tricky (mostly since it's designed for Jackson's internal use mostly).
But this is used by a few Jackson extension modules, so it is supported use case. So:
ObjectMapper mapper = ...;
JavaType type = mapper.constructType(PojoType.class); // JavaType to allow for generics
// use SerializationConfig to know setup for serialization, DeserializationConfig for deser
BeanDescription desc = mapper.getSerializationConfig().introspect(type);
you can also safely upcast it to BasicBeanDescription if necessary.
This gives you access to lots of information; either list of logical properties (through which you can find getter/setter/field/ctor-argument that represents it), fully resolved methods (with annotations) and such. So hopefully that is enough.
Logical properties are useful since they contain both external name (one expected from JSON) and internal name derived from getter/setter.
I'm not aware of anything in Jackson to make this particularly easy. A reflections-based solution might suffice.
import java.lang.reflect.Field;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
// {"$food":"Green Eggs and Ham"}
String jsonInput = "{\"$food\":\"Green Eggs and Ham\"}";
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
Bar bar = mapper.readValue(jsonInput, Bar.class);
new Jsr303().validate(bar);
// output:
// I do not like $food=Green Eggs and Ham
}
}
class Bar
{
#JsonProperty("$food")
String food;
}
class Jsr303
{
void validate(Bar bar) throws Exception
{
Field field = Bar.class.getDeclaredField("food");
JsonProperty annotation = field.getAnnotation(JsonProperty.class);
System.out.printf("I do not like %s=%s", annotation.value(), bar.food);
}
}

Categories