I have a light wrapper class around a complex class for which I needed to write a custom Jackson JSON deserializer. The wrapper class is simple and only contains a String, a Date, and my complex object as properties. Does Jackson automatically apply a simple deserializer to the wrapper and my custom deserializer to my complex object? The custom deserializer works by itself. But when I try to serialize the wrapper Jackson throws a Nullpointer Exception. I must be missing something conceptual. Do I have to register another serializer with my module in addition to my custom deserializer?
java.lang.NullPointerException
at org.codehaus.jackson.impl.ReaderBasedParser._skipWSOrEnd(ReaderBasedParser.java:1477)
at org.codehaus.jackson.impl.ReaderBasedParser.nextToken(ReaderBasedParser.java:368)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:690)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2732)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1863)
at com.newoak.noc.curve.model.tests.ModelParamsTest.deserializeGraph(ModelParamsTest.java:100)
at com.newoak.noc.curve.model.tests.ModelParamsTest.testSerializationDeserialization(ModelParamsTest.java:113)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
Trying to deserialize
public ModelParamGraph deserializeGraph(String json) throws JsonGenerationException, JsonMappingException, IOException
{
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new SpaceJsonSerializer());
testModule.addDeserializer(Space.class, new SpaceJsonDeserializer());
mapper.registerModule(testModule);
ModelParamGraph space = mapper.readValue(json, ModelParamGraph.class);
return space;
}
Wrapper
public class ModelParamGraph
{
public String source;
public Date date;
#JsonSerialize(using=SpaceJsonSerializer.class)
#JsonDeserialize(using=SpaceJsonDeserializer.class)
public Space<TModelParam> paramSpace;
public ModelParamGraph()
{
}
public ModelParamGraph(String source, Date date)
{
setSource(source);
setDate(date);
setParamSpace(new Space<TModelParam>());
}
//getters and setters
}
If you can modify the class being wrapped, then you can use Jackson's #JsonView annotation, here is the full tutorial.
Step 1: create interfaces like so:
public class MyJsonViews {
public static class Small { }
public static class Medium extends Small { }
public static class Large extends Medium { }
}
Step 2: annotate properties (or methods) in your POJOs:
public class Wrapper {
#JsonView(MyJsonViews.Small.class)
private String name;
#JsonView(MyJsonViews.Medium.class)
private Wrapped wrapped;
// getters and setters
}
public class Wrapped {
#JsonView(MyJsonViews.Small.class)
private String someField;
#JsonView(MyJsonViews.Medium.class)
private String anotherField;
// getters and setters
}
Step 3: serialize using the new views:
ObjectMapper objectMapper = new ObjectMapper();
// important: this excludes all fields without #JsonView from being serialized
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
String json = objectMapper.writerWithView(SMyJsonViews.mall.class).writeValueAsString(wrapper);
Your JSON will now contain just the fields annotated with #JsonView(MyJsonViews.Small.class).
If you cannot modify the class being wrapped, then you could use the Filter approach, described here.
Related
I use external application which expects an Object that Serializable from me like his function:
externalFunction(Object input);
So I should give that function an input that will be correctly serialized into JSON when the method is invoked (not controlled by me).
But I don't know how data is structured since I receive input from another external application dynamically. So case like this:
1. Get data from 3rd party
2. MyApp should annotate data for Json Serialization
3. Send data to 3rd party as input
4. Response will be produced as JSON
How can I achieve this? How can I give input to the function that is correctly serialized when the function is invoked?
What I tried so far:
So first thing I try is wrap data with some Wrapper like:
public class JsonWrapper<T> implements Serializable
{
public T attributes;
public JsonWrapper( T attributes )
{
this.attributes = attributes;
}
#JsonValue
public T getAttributes( )
{
return attributes;
}
}
So I wrap data like ->
data = getFromThirdParty();
wrapped = new JsonWrapper<>(data);
externalFunction(wrapped);
But it produces a response with "attributes" field which I don't want. Also I tried to use #JsonUnwrapped public T attributes; but the result is same.
I don't want this:
{
"attributes": {
... some fields/values that I don't know, get from 3rd party
}
}
I want like this:
{
... some fields/values that I don't know, get from 3rd party
}
The #JsonUnwrapped annotation doesn't work when T is a Collection (see this answer from the Jackson's creator). But the #JsonValue annotation actually does the trick:
public class JsonWrapper<T> {
#JsonValue
private T value;
public JsonWrapper(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
If you use Lombok, you can have:
#Getter
#AllArgsConstructor
public class JsonWrapper<T> {
#JsonValue
private T value;
}
Example
Consider the following class:
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
}
When serializing an Person instance, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(new Person("John", "Doe"));
String json = mapper.writeValueAsString(wrapper);
{"firstName":"John","lastName":"Doe"}
When serializing a list of Person instances, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(
Arrays.asList(
new Person("John", "Doe"),
new Person("Jane", "Poe")
));
String json = mapper.writeValueAsString(wrapper);
[{"firstName":"John","lastName":"Doe"},{"firstName":"Jane","lastName":"Poe"}]
I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.
Ideally I'd have a single POJO like:
public class UserInfo {
#Redacted
String ssn;
String name;
}
Under "normal" conditions I want this object to be serialized the default way:
{"ssn":"123-45-6789", "name":"Bob Smith"}
but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:
{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}
I've also looked into using #JsonSerialize and come up with:
public class UserInfo {
#JsonSerialize(using = RedactedSerializer.class, as=String.class)
String firstName;
String lastName;
}
The problem with this is that it ALWAYS uses this rule. Can multiple #JsonSerializers be added and only the specified one be used within the runtime code?
I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.
The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use #JsonView and custom serializer, something like:
class Views {
public static class ShowSSN {}
}
private static class MyBean{
#JsonSerialize(using = MyBeanSerializer.class)
#JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}
private class MyBeanSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}
And use it like:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");
System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}
System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}
Also for example in spring it would be really easy to use
#Controller
public class MyController {
#GetMapping("/withView") // results in {"ssn":"123-45-6789"}
#JsonView(Views.ShowSSN.class)
public #ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}
#GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public #ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}
}
I think you could achieve that dynamically by coding not annotations,
inside your methods, you can set the proper Serializer and switch between them
(The code depends on your Jackson version)
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers
I have a set of abstract classes like this:
abstract class A {
public abstract B getB() {return this.b;}
public abstract void setB(B b) {this.b = b;}
}
abstract class B {
public abstract C getC() {return this.c;}
public abstract void setC(C c) {this.c = c;}
}
abstract class C {
private String foo;
public String getFoo() {return this.foo;}
public void setFoo(String foo) {this.foo = foo;}
}
In runtime, I create proxies for these classes using ByteBuddy. I can easily serialize objects of these proxy classes to XML. But when I attempt to deserialize an XML, JAXB throws javax.xml.bind.UnmarshalException: Unable to create an instance of A since it can't create instances of abstract classes. I want to show it how to create these instances in runtime in order to deserialize them (I have a special Spring bean, which does it - so I need to be able to inject it wherever I define creation logic) I looked at JAXB and Jackson, but couldn't find how to do it.
Is there a way to do it? I'm not bound to any serialization framework, though it would be preferable to stay with JAXB or Jackson.
I found that both JAXB and Jackson can do it.
JAXB provides two ways to solve it: factory methods and adapters.
Using JAXB factory methods, I need to create a factory which would be responsible for object creation:
public class MyFactory {
public static MyObject createMyObject() {
return SomeMagic.createMyObject();
}
}
Then I only need to mark my abstract class with #XmlType annotation:
#XmlType(factoryClass = MyFactory.class, factoryMethod = "createMyObject")
public abstract class MyObject {
...
}
If I wanted to use JAXB adapters, I would need to create Java classes which JAXB can instantiate and fill from the XML, and then I would need to convert objects of these classes to the ones I need:
public class MyAdapter extends XmlAdapter<MyAdapter.MyJaxbObject, MyObject> {
#Override
public MyObject unmarshal(MyJaxbObject src) throws Exception {
MyObject tgt = SomeMagic.createMyObject();
BeanUtils.copyProperties(tgt, src);
return tgt;
}
#Override
public MyObject marshal(MyObject src) throws Exception {
MyJaxbObject tgt = new MyJaxbObject();
BeanUtils.copyProperties(tgt, src);
return tgt;
}
public static class MyJaxbObject {
...
}
}
Then I would mark my abstract class with #XmlJavaAdapter annotation:
#XmlJavaAdapter(MyAdapter.class)
public abstract class MyObject {
...
}
Using Jackson I can create custom deserializer for my abstract class.
public class MyObjectDeserializer extends StdDeserializer<MyObject> {
public MyObjectDeserializer() {
super(MyObject.class);
}
#Override
public MyObject deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
MyObject myObject = SomeMagic.createMyObject();
return mapper.readerForUpdating(myObject).readValue(parser);
}
}
Later in my code I need to register my deserializers:
ObjectMapper mapper = new XmlMapper();
SimpleModule module = new SimpleModule("module", new Version(1, 0, 0, null, null, null));
module.addDeserializer(MyObject.class, new MyObjectDeserializer());
mapper.registerModule(module);
For my purposes I preferred Jackson custom deserializers, because:
I also need to perform additional operations on the nested objects after their fields are filled but before passing these objects to other objects' setters (JAXB doesn't seem to support it)
I can use custom logic when I fill object's fields (Also achievable with Adapters).
I can create deserializers by myself, so I can use dependency injection to configure them (Factories are static, and Adapters are created by JAXB).
Is there a way by which Jackson allows custom serialization for a specific type only in a particular class?
Here is my scenario:
I have ClassA.java which is something like:
public Class ClassA {
byte[] token;
String name;
public getToken() {
return token;
}
public setToken(byte[] newToken) {
token = newToken;
}
public getName() {
return name;
}
public setName(String newName) {
name = newName;
}
}
I do not have access to this class as it is in an external jar. However, I want to serialize the token byte array here in a particular manner. I have created a Custom serializer that does that and tries adding it to the mapper in all the ways mentioned in Jackson docs.
public class ByteArrayJacksonSerializer extends JsonSerializer<byte[]> {
public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String token = doMyThing(value);
jgen.writeString(token);
}
}
And in mapper, something like this:
public class MyObjectMapper extends ObjectMapper {
CustomSerializerFactory sf = new CustomSerializerFactory();
sf.addGenericMapping(byte[].class, new ByteArrayJacksonSerializer());
this.setSerializerFactory(sf);
and some more code here...
}
However I can do it only for byte[] in general, and not for ONLY byte[] in ClassA. Is there a way to let Jackson know that this custom serializer must be used ONLY for fields of byte[] type in ClassA, and to do serialization it's own way for all other classes?
You should use MixIn feature. In your example you have to create new interface:
interface ClassAMixIn {
#JsonSerialize(using = ByteArrayJacksonSerializer.class)
byte[] getToken();
}
which specifies custom serializer for given property. Now we have to configure ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(ClassA.class, ClassAMixIn.class);
Your custom serializer will be used only for serializing byte array property in ClassA.
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