Helper method for #JsonProperty mapping in Jackson JSON - java

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

Related

Can the JsonProperty required value be conditional?

Is it possible to have the #JsonProperty required dynamically set or set at call?
The reason behind this...
I'm generating json files which describes a schema and defines
What are the required fields for a new item
What are the required fields for an update to an item.
So, a creation requires only foo
and an update requires foo and bar
Can I make things so I can pass in something to say bar is now required?
or would I need to duplicate this code in order to have different settings for JsonProperty?
#JsonInclude(Include.NON_NULL)
public class Bean {
#JsonProperty(value="foo", required=false)
private FooProperty fooProperty;
#JsonProperty(value="bar", required=false)
private BarProperty barProperty;
//
public FooProperty getFooProperty() { return fooProperty; }
public void setFooProperty(FooProperty argFooProperty) {
this.fooProperty = argFooProperty
}
public BarProperty getBarProperty() { return barProperty; }
public void setFooProperty(BarProperty argBarProperty) {
this.barProperty = argBarProperty
}
}
You could solve this issue in couple of ways. First one would be as Franjavi suggested you could use a mixin.
Have a mixin class which will mark your foo as one of the ignored properties. In your main class mark both the fiends required and you can inject this mixin whenever this is an option field.
#JsonIgnoreProperties("foo")
public abstract class mixinClass {
}
You configure your mixin into your mapper as follows.
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Bean.class, mixinClass.class);
This is not always a working option whenever you are getting this response from a third party API where you might be serializing/deserializing using the default jackson mapper which are provided by them like Resttemplate.
In this case, instead of having the foo property, you can just included whatever the properties that are present in all the responses and handle the rest of the properties using #JsonAnyGetter and #JsonAnySetter. You can capture this in a map with key being your object name which is in this case foo. You need to have the following part in your parent node or whichever node that will encapsulates these optional properties.
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
Let me know if you need any further clarification!!
You can play wiht Mixins and the mapper:
public interface BarRequiredMixIn {
#JsonProperty(value="bar", required=true)
private BarProperty barProperty;
}
ObjectMapper mapper = new ObjectMapper();
// When you want that it is required
mapper.addMixInAnnotations(Bean.class, BarRequiredMixIn.class);
But I strongly recommend to avoid to use this kind of conditional restrictions with Jackson, since it is not designed for it, and it looks kind of confusing.
Based on the example you have given, setting the following in your POJO would be sufficient & Jackson would be able to figure out whether to deserialize it/not . All #JsonProperty are required by default, so with the code below, you would be able to achieve optional bar value in your Create/Update scenarios based on payload in the request
#JsonProperty(value="bar", required=false)
private Bar bar;

Jackson - Custom TypeId Resolver

I've been using a custom typeId resolver for one of my classes, so far I've been leaning on the annotation support:
#JsonTypeInfo(
use = JsonTypeInfo.Id.CUSTOM,
include = JsonTypeInfo.As.PROPERTY,
property = "#type")
#JsonTypeIdResolver(ColumnDefinitionTypeResolver.class)
But now I need to customize the creation of the type resolver by passing some other dependencies to it via constructor or setters, and since Jackson is the one who instantiate it I can't find a way around it.
Is there a way to configure the ObjectMapper to use a TypeIdResolver instead of relying on annotations?
Regards
So you have two options:
1) If you're set on using the #JsonTypeIdResolver your stuck using static state in your TypeIdResolver. This probably isn't what you want.
The default JacksonAnnotationIntrospector will try to create an instance of the type you provide using JsonTypeIdResolver per its default constructor. There is currently no way to configure it to do otherwise.
public final class ColumnDefinitionTypeResolver implements TypeIdResolver {
// You could rely on static state.
public static String SOME_ACCESSIBLE_OBJECT = null;
public ColumnDefinitionTypeResolver() {
// This is what gets called.
}
}
ColumnDefinitionTypeResolver.SOME_ACCESSIBLE_OBJECT = "I can affect the implementation from here, but using static state ... be careful";
2) Is create a module to handle deserialization of your type and subtypes.
SimpleModule columnDefinitionModule = new SimpleModule("colDefMod", new Version(1, 0, 0, null))
.addDeserializer(ColumnDefinition.class, new JsonDeserializer() {
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
// Need to read the type out and then use ObjectMapper to deserialize using the correct token
}
})
.registerSubtypes(...); // add your subtypes here.
(new ObjectMapper()).registerModule(columnDefinitionModule);
For more detailed examples, see Jackson documentation How-To: Custom Deserializers.
You can also set a custom type id resolver programmatically. Look at the top answer here.
Look for this line:
typeResolver.init(JsonTypeInfo.Id.CLASS, null);
Replace null with your type id resolver.

How to trigger calls to .serializeWithType() of a class implementing JsonSerializable in Jackson?

This is Jackson 2.2.x.
I have a class implementing JsonSerializable; there are two methods to implement for this interface, serialize() and serializeWithType().
I want to test {de,}serialization of this class, and I can trigger calls to serialize() easily; not, however, serializeWithType().
The javadoc for this latter method says that this method is called
[...] when additional type information is expected to be included in serialization, for deserialization to use.
I just don't understand what this means...
How do I set up a test environment so that this method be called? Note that the JSON to be serialized can be of any type except object (ie, boolean, number, string, array are all valid types).
This method is used when you want to use polymorphism
public class A {
...
}
public class B extends A {
...
}
public class C extends A {
...
}
If you serialize an instance of C and then try to deserialize the resulting json but only knowing that its a sub-type of A :
final ObjectMapper objectMapper = new ObjectMapper();
final String json = objectMapper.writeValueAsString(new C());
final A deserialized = objectMapper.readValue(json, A.class);
You need something to be stored within the resulting JSON to keep the real type of the serialized object.
This can be enabled either using #JsonTypeInfo on your class, or by calling enableDefaultTyping on your ObjectMapper.
This is a sample test case using JUnit & Mockito
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class SerializeWithTypeTest {
private JsonSerializable serializable = mock(JsonSerializable.class);
#Test
public void shouldCallSerializeWithType() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.writeValueAsString(serializable);
// make sure serializeWithType is called once
verify(serializable, times(1)).serializeWithType(any(), any(), any());
}
}
Jackson 2 is completely incompatible with Jackson 1, and JsonSerializableWithType (Now deprecated and unusable) is an interface from Jackson 1 which led to the presence of serializeWithType() in Jackson 2.
serializeWithType() is called when additional type information is expected to be included, which means that an annotation (JsonTypeInfo) is specifying the class property for deserialization delegation, when polymorphism is used. This method will then be called with the additional type information within a TypeSerializer, which may be written with a type prefix:
/* (.., .., TypeSerializer typeSer) */ {
typeSer.writeTypePrefixForScalar(.., .., ThisClass.class);
}
By annotating the class with #JsonTypeInfo, you will be able to specify serializing with the type information:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = As.WRAPPER_OBJECT,
property = "type")
#JsonSubTypes({
#Type(name = "typeint", value = MyInt.class),
#Type(name = "typefloat", value = MyFloat.class)
})
public interface MyNumber {}
#JsonTypeName("typeint")
public MyInt implements MyNumber {}
#JsonTypeName("typefloat")
public MyFloat implements MyNumber {}
Then the values typeint and typefloat will be set in the property named type. When you deserialize a MyNumber, it will be based on polymorphism. Thomas Maurel's answer demonstrates a straightforward approach to test by serializing the object as string and deseralizing it.
Try to use JsonTypeInfo annotation on your class. It should trigger calling serializeWithType(). It is used to store info about type which is required for polymorphic types or to link abstract type and matching concrete implementation.

Jackson: how to prevent field serialization (while keeping deserialization)

I'd like to go further on what this question was about, I've been roaming SO for a solid hour now without finding anything.
Basically, what I'm trying to do is having a property properly instanciated through Jackson internal reflection algorithm during deserialization but having this same property not serialized when it comes to serialization.
I know about #JsonIgnore and #JsonIgnoreProperties but apparently I can't seem to use them right : either my property is correctly deserialized when I feed Jackson a proper map of properties but it also appears in the serialized results, either (when using #JsonIgnore) it is not serialized (which is wanted) but also not deserialized (not wanted).
Example :
public class Foo {
/* This is the property I want to be instanciated by Jackson upon deserialization
* but not serialized upon serialization
*/
private final Object bar = null;
public Object getBar() {
return bar;
}
}
To make things worse, as you can see, the property is final (this is why I'm keen on using Jackson reflection ability upon Foo instanciation through deserialization). I've read on potential solution about annotating the setter and the getter differently but I'd like to keep this property final if possible. If not possible, I'd settle for a non-final property.
I would appreciate answers not suggesting custom serializer/deserializer, my code base is currently free of such and if the solution could be of minimal impact, that would be perfect. Again, I'm no Jackson expert so if what I'm asking is not possible I'll obviously accept alternative answers.
I've also read this thread on github but none of the suggested ways of implementation have actually been implemented at the moment.
Thanks
EDIT : to make things clearer
public class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
public void testMethod() throws IOException {
String json = "{\"bar\":\"Value\"}";
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(json, Foo.class);
System.out.println(foo); // should have a bar property set to "Value"
System.out.println(mapper.writeValueAsString(foo)); // should return an empty JSON object
}
I am not sure whether it is elegant solution but you can use MixIn feature. You have to create new interface which could look like below:
interface FooMixIn {
#JsonIgnore
String getBar();
}
Assume that your POJO looks like this:
class Foo {
private final String bar = null;
public String getBar() {
return bar;
}
#Override
public String toString() {
return bar;
}
}
Now you have to tell Jackson that you want to ignore this property:
String json = "{\"bar\":\"Value\"}";
System.out.println(json);
ObjectMapper deserializeMapper = new ObjectMapper();
deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println(deserializeMapper.readValue(json, Foo.class));
Above example prints:
{"bar":"Value"}
null
Without deserializeMapper.addMixInAnnotations(Foo.class, FooMixIn.class); line above program prints:
{"bar":"Value"}
Value
EDIT 1
If you want to achieve result like you showed you have to create two ObjectMappers and customize them. See below example:
String json = "{\"bar\":\"Value\"}";
ObjectMapper deserializerMapper = new ObjectMapper();
Foo foo = deserializerMapper.readValue(json, Foo.class);
System.out.println("Foo object: " + foo);
ObjectMapper serializerMapper = new ObjectMapper();
serializerMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
serializerMapper.addMixInAnnotations(Foo.class, FooMixIn.class);
System.out.println("JSON: " + serializerMapper.writeValueAsString(foo));
For serialization you have to use one instance and for deserialization you have to use another instance.
Starting with Jackson 2.6, a property can be marked as read- or write-only. It's simpler than hacking the annotations on both accessors (for non-final fields) and keeps all the information in one place. It's important to note that a final field is considered writable by default by Jackson.
However, it's not enough for a final field to allow deserialization, because you can't have a setter on that field: it needs to be set via the constructor, either directly or using a builder or another type that can be deserialized by Jackson. When using the constructor with the properties as parameters, you need to specify which parameter corresponds to which property, using #JsonProperty:
public class Foo {
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private final String bar;
public Foo(#JsonProperty("bar") String bar) {
this.bar = bar;
}
public String getBar() {
return prop;
}
}

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 ...
}
}

Categories