Custom Jackson Serializer for Wrapper Object - java

I have the provider interface
interface IProvider<T> {
T locate();
}
and a class containing a field of type IProvider (can be another type for other fields).
class MyObject {
MyLocator<String> field;
}
I need to serialize instances of MyObject to JSON using Jackson 1.7. The output must be the same as if MyObject.field had been a String (i.e. no reference to ILocator).
I can't figure out how to build the custom serializer required to achieve this. Here is the structure I am trying to use for this task:
class MyLocatorSerializer extends SerializerBase<MyLocator<?>> {
public MyLocatorSerializer() {
super(MyLocator.class, false);
}
#Override
public void serialize(MyLocator<?> a_value, JsonGenerator a_jgen,
SerializerProvider a_provider) throws IOException, JsonGenerationException {
// Insert code here to serialize a_value.locate(), whatever its type
}
#Override
public JsonNode getSchema(SerializerProvider a_provider, Type a_typeHint)
throws JsonMappingException {
// What should I return here? I can't find documentation regarding the different schema types...
}
}
The custom serializer would be registered using
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addSerializer(new MyLocatorSerializer());
objectMapper.registerModule(module);

Another answer using mix-in annotations following the comment from Staxman.
static class JacksonCustomModule extends SimpleModule {
public JacksonCustomModule() {
super("JacksonCustomModule", new Version(1, 0, 0, null));
}
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(IProvider.class, IProviderMixIn.class);
super.setupModule(context);
}
interface IProviderMixIn<T> {
#JsonValue
T locate();
}
}
Activate the module with:
objectMapper.registerModule(new JacksonCustomModule());

Apologies if I misunderstand the question, but would this be as simple as just using #JsonValue on 'Locate' method, instead of writing a custom serializer?
What #JsonValue does is take value of a property as is, and use it instead of creating a JSON Object: often this is used for serializing a POJO as a simple String or number, like so:
public class StringWrapper {
#JsonValue public String value;
}
so that for class like:
public class POJO {
public StringWrapper wrapped;
}
we would get serialization like:
{
"wrapper" : "string value of 'value'"
}
instead of what would otherwise be seen:
{
"wrapper" : {
"value" : "... string value ... "
}
}
Annotation can be used for any types of values obviously.

Following StaxMan's answer, I inspected the workings of #JsonValue and got the following serializer:
// Based on JsonValueSerializer
private static class ProviderSerializer extends SerializerBase<IProvider<?>> {
public ProviderSerializer() {
super(IProvider.class, false);
}
#Override
public void serialize(IProvider<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException {
Object object = value.locate();
// and if we got null, can also just write it directly
if (object == null) {
provider.defaultSerializeNull(jgen);
return;
}
Class<?> c = object.getClass();
JsonSerializer<Object> ser = provider.findTypedValueSerializer(c, true, null);
// note: now we have bundled type serializer, so should NOT call with typed version
ser.serialize(object, jgen, provider);
}
#Override
public JsonNode getSchema(SerializerProvider provider, Type typeHint)
throws JsonMappingException {
// is this right??
return JsonSchema.getDefaultSchemaNode();
}
}
After some tests, this does what I need. However, I don't fully really understand the purpose of the getSchema method, so maybe I'm doing something wrong...

Related

Dynamically appending text to all property names of any object in Jackson

Consider the following object
#Value
public class Example {
#JsonProperty("StrValue")
String strValue;
#JsonProperty("Internal")
Internal internal;
#Value
public static class Internal {
#JsonProperty("InternalValue")
String internalValue;
}
}
I could get a serialization of this object like
"Example" : {
"StrValue": "v1",
"Internal": {
"InternalValue": "v2"
}
}
However, I need two different versions of this object, one would have the string First appended to the name of all properties and the other would have Second:
"ExampleFirst" : {
"StrValueFirst": "v1",
"InternalFirst": {
"InternalValueFirst": "v2"
}
}
(And similarly for Second)
Note that this should work for nested properties and for any object of any class. Is there an easy way to do this? I feel like this might be the work of BeanSerializerModifier or some other custom serialization mechanisms, however, the custom serialization APIs are pretty low-level and the documentation is scarce.
Answering my own question since I (kind of) solved it:
First I would make a subclass of BeanPropertyWriter such as
class AppendingPropertyWriter extends BeanPropertyWriter {
private final BeanPropertyWriter writer;
public AppendingPropertyWriter(BeanPropertyWriter writer) {
super(writer);
this.writer = writer;
}
#Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object value = writer.get(bean);
gen.writeObjectField(writer.getName() + "First", value);
}
}
Then add it to the BeanSerializerModifier like:
public class AppendingSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter writer = beanProperties.get(i);
beanProperties.set(i, new AppendingPropertyWriter(writer));
}
return beanProperties;
}
}
Then you'd have to put it into your ObjectMapper like:
var serializerModifier = new AppendingSerializerModifier();
var sf = BeanSerializerFactory.instance.withSerializerModifier(serializerModifier);
ObjectMapper mapper = new XmlMapper();
mapper.setSerializerFactory(sf);
Then this mapper achieves the intended effect. However this seems to bypass some other modules I added like JavaTimeModule and throw an exception with them. So if anyone has a solution to that I'd be grateful.
EDIT: Fixed it! Turns out I needed to serialize the object as
#Override
public void serializeAsField(Object bean,
JsonGenerator gen,
SerializerProvider prov) throws Exception {
Object value = writer.get(bean);
prov.defaultSerializeField(writer.getName() + "First", value, gen);
}
And then be sure to register the JavaTime module after setting the factory

What is the best way to do inline serialization of JSON data using JACKSON?

I've got a bean defined as such :
public static class TestBean {
private String a;
private Long b;
public TestBean(String a, Long b) {
this.a = a;
this.b = b;
}
public String getA() {
return a;
}
public Long getB() {
return b;
}
}
It models some business and I do not get to instantiate it (using JPA). Some of my API let the user retrieve a view of this bean serialize as JSON using Jackson (through JAX-RS) and I would like to add a list of related links to this view.
The normal Jackson JSON serialization would be (for a = "aa" and b = 2L) :
{"a":"aa","b":2}
And I would like to have the links appear as
{"a":"aa","b":2,
"links":[{"rel":"rel","href":"href://"},{"rel":"rel2","href":"href://2"}]}
Possible work-around
I would rather not add a getLinks() method to my bean, it's specific to this view.
Simply using a composite object would yield a serialization like :
{"data":{"a":"aa","b":2},"links":[{"rel":"rel","href":"href://"}]}
Which I could live with but is not what I was looking for ultimately.
Current solution
I would like to avoid manipulating the JSON string or having to reload it into a Map to insert my extra values. For now the solution I've come up with seem awfully convoluted:
Current scary solution :
//a composite view object
public abstract class AddedLinksView<K> {
private final K resource;
private final Link[] links;
public AddedLinksView(K resource) {
this.resource = resource;
links = buildLinks(resource);
}
public abstract Link[] buildLinks(K resource);
public K getResource() {
return resource;
}
public Link[] getLinks() {
return links;
}
}
//a specific bean serializer
private static class RawBeanSerializer extends BeanSerializer {
public RawBeanSerializer(BeanSerializerBase ser) {
super(ser);
}
//this is like the standard serialize but without the start and end tags
public void rawSerialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonGenerationException {
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
}
}
#Test
public void usingModule() throws Exception {
// basic module metadata just includes name and version (both for troubleshooting; but name needs to be unique)
SimpleModule module = new SimpleModule("EnhancedDatesModule", new Version(0, 1, 0, "alpha"));
//adding a serializer for the composite view
module.addSerializer(new JsonSerializer<AddedLinksView>() {
#Override
public Class<AddedLinksView> handledType() {
return AddedLinksView.class;
}
#Override
public void serialize(AddedLinksView value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
//looking up the bean serializer that will be used for my resource
JsonSerializer<Object> ser = provider.findTypedValueSerializer(value.getResource().getClass(), true,
null);
if (ser instanceof BeanSerializerBase) {
//cloning it in a sub class that makes it possible to 'inline' the serialization
RawBeanSerializer openSer = new RawBeanSerializer((BeanSerializerBase) ser);
openSer.rawSerialize(value.getResource(), jgen, provider);
}
//adding my links
jgen.writeArrayFieldStart("links");
for (Link link : value.getLinks()) {
jgen.writeObject(link);
}
jgen.writeEndArray();
jgen.writeEndObject();
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
AddedLinksView<TestBean> view = new AddedLinksView<TestBean>(new TestBean("aa", 2L)) {
#Override
public Link[] buildLinks(TestBean resource) {
return new Link[] { new Link("rel", "href://"), new Link("rel2", "href://2") };
}
};
System.out.println("useModule json output: " + mapper.writeValueAsString(view));
}
Did I miss something obvious in Jackson to achieve this? Or am I completely off the mark in my requirements already?
There is no real way to externally inject things into POJOs to serialize: but you might be interested in checking out #JsonAnyGetter, which at least allows just adding contents of a java.util.Map as extra properties for a POJO.
Would this answer your question: http://wiki.fasterxml.com/JacksonFeatureUpdateValue
I am not sure you can avoid mapping. You may use Dozer to help.
This should help you: Tools for merging java beans

Dynamic polymorphic type handling with Jackson

I have a class hierarchy similar to this one:
public static class BaseConfiguration {
}
public abstract class Base {
private BaseConfiguration configuration;
public String id;
public BaseConfiguration getConfiguration() { ... }
public void setConfiguration(BaseConfiguration configuration) { ... }
}
public class A extends Base {
public static class CustomConfigurationA extends BaseConfiguration {
String filename;
String encoding;
}
CustomConfigurationA getConfiguration() { ... }
}
class B extends Base {
public static class CustomConfigurationB extends BaseConfiguration {
/* ... */
}
CustomConfigurationB getConfiguration() { ... }
}
And json input like this one (which I cannot change myself)
{
"id":"File1",
"configuration":{
"filename":"...",
"encoding":"UTF-8"
}
}
I am parsing the JSON in Java with Jackson like this
ObjectMapper mapper = new ObjectMapper();
value = mapper.readValue(in, nodeType);
I want to deserialize classes A, B and others from JSON using JAVA/Jackson. There are no type information embedded in JSON (and can't be). I can't use annotations on the classes (I don't own them) and I (believe) I can't use mixins since there are potentially arbitrary numbers of classes like A & B (and mixins are not dynamic). Good thing is that the deserializing code knows which is the correct custom class to use for deserializing (basically there is a known mapping from class to configuration class), but I do not know how make Jackson recognize this information when deserializing the JSON.
In short: I want to be able to resolve the deserialization type of the configuration object depending on the surrounding class type by setting whatever is necessary on ObjectMapper. How can this be achieved?
Apparently the answer was to implement something similar to the sixth solution posted at http://programmerbruce.blogspot.com/2011/05/deserialize-json-with-jackson-into.html, which uses unique JSON element names to identify the target type to deserialize to.
Good answer provided by Programmer Bruce!
I have a case of polymorphism in which I want to keep the domain objects as POJOs and not use dependencies on Jackson annotations.
Therefore I preffer to use a custom deserializer and a Factory for decising the type or intantiating the concrete classes.
Here is my code ...
(be aware that I have an Annotation Hierarchy which are in fact "User Tags" and not Java Annotations )
Here is the deserialization Method
public class AnnotationDeserializer extends StdDeserializer<Annotation> {
AnnotationDeserializer() {
super(Annotation.class);
}
#Override
public Annotation deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Class<? extends Annotation> realClass = null;
Iterator<Entry<String, JsonNode>> elementsIterator = root.getFields();
while (elementsIterator.hasNext()) {
Entry<String, JsonNode> element = elementsIterator.next();
if ("type".equals(element.getKey())) {
realClass = AnnotationObjectFactory.getInstance()
.getAnnotationClass(element.getKey());
break;
}
}
if (realClass == null)
return null;
return mapper.readValue(root, realClass);
}
}
I had to do something similar, and ended up creating a generic polymorphic list serializer and deserialzer. Here is the deserialize that I think will work for you:
public class PolymorphicListDeserializer extends JsonDeserializer<List<?>> implements ContextualDeserializer {
private HashMap<String, Class> _typeMap = null;
private Class _elementType;
private static <T> List<T> getNewList(Class<T> clazz) {
return new ArrayList<T>();
}
#Override
public List<?> deserialize(final JsonParser jp, DeserializationContext ctxt) throws IOException {
final List list = getNewList(_elementType);
JsonToken nextToken = jp.getCurrentToken();
if (nextToken == JsonToken.START_OBJECT) {
if ( _typeMap.containsKey( currentFieldName )) {
list.add( _elementType.cast( ctxt.readValue( jp, _typeMap.get( currentFieldName ) ) ) );
}
nextToken = jp.nextToken();
} else if (currentFieldName != null && isEndToken(nextToken) && wrapperCount == 0) {
break;
} else {
nextToken = jp.nextToken();
}
}
return list;
}
public JsonDeserializer<List<?>> createContextual( DeserializationContext ctxt, BeanProperty property ) throws JsonMappingException {
//In Jackson 2.6.3, this method is called once per instance and the exception is never thrown
if ( _typeMap == null )
_typeMap = new HashMap<String, Class>();
else
throw new RuntimeException("Unsupported version of Jackson. Code assumes context is created once and only once.");
_elementType = property.getType().getContentType().getRawClass();
//For now, requiring XmlElements annotation to work. May add support for JsonElements (if needed) later.
for (XmlElement e : property.getAnnotation(XmlElements.class).value()) {
_typeMap.put(e.name(), e.type());
}
return this;
}
private static boolean isStartToken(JsonToken t) {
boolean result = false;
if (t == JsonToken.START_OBJECT) {
result = true;
} else if (t == JsonToken.START_ARRAY) {
result = true;
}
return result;
}
Above answers depicts a solution however lack what actually used annotations mean. If you are curious about what actually these annotation do, idea behind them & why they are required please go through the below link. Its explained very nicely in it. https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization

How do I use a custom Serializer with Jackson?

I have two Java classes that I want to serialize to JSON using Jackson:
public class User {
public final int id;
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
public class Item {
public final int id;
public final String itemNr;
public final User createdBy;
public Item(int id, String itemNr, User createdBy) {
this.id = id;
this.itemNr = itemNr;
this.createdBy = createdBy;
}
}
I want to serialize an Item to this JSON:
{"id":7, "itemNr":"TEST", "createdBy":3}
with User serialized to only include the id. I will also be able to serilize all user objects to JSON like:
{"id":3, "name": "Jonas", "email": "jonas#example.com"}
So I guess that I need to write a custom serializer for Item and tried with this:
public class ItemSerializer extends JsonSerializer<Item> {
#Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
}
I serialize the JSON with this code from Jackson How-to: Custom Serializers:
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
But I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
at com.example.JsonTest.main(JsonTest.java:54)
How can I use a custom Serializer with Jackson?
This is how I would do it with Gson:
public class UserAdapter implements JsonSerializer<User> {
#Override
public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
JsonSerializationContext context) {
return new JsonPrimitive(src.id);
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(User.class, new UserAdapter());
Gson gson = builder.create();
String json = gson.toJson(myItem);
System.out.println("JSON: "+json);
But I need to do it with Jackson now, since Gson doesn't have support for interfaces.
You can put #JsonSerialize(using = CustomDateSerializer.class) over any date field of object to be serialized.
public class CustomDateSerializer extends SerializerBase<Date> {
public CustomDateSerializer() {
super(Date.class, true);
}
#Override
public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)");
String format = formatter.format(value);
jgen.writeString(format);
}
}
As mentioned, #JsonValue is a good way. But if you don't mind a custom serializer, there's no need to write one for Item but rather one for User -- if so, it'd be as simple as:
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeNumber(id);
}
Yet another possibility is to implement JsonSerializable, in which case no registration is needed.
As to error; that is weird -- you probably want to upgrade to a later version. But it is also safer to extend org.codehaus.jackson.map.ser.SerializerBase as it will have standard implementations of non-essential methods (i.e. everything but actual serialization call).
I tried doing this too, and there is a mistake in the example code on the Jackson web page that fails to include the type (.class) in the call to addSerializer() method, which should read like this:
simpleModule.addSerializer(Item.class, new ItemSerializer());
In other words, these are the lines that instantiate the simpleModule and add the serializer (with the prior incorrect line commented out):
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule",
new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);
FYI: Here is the reference for the correct example code: http://wiki.fasterxml.com/JacksonFeatureModules
Use #JsonValue:
public class User {
int id;
String name;
#JsonValue
public int getId() {
return id;
}
}
#JsonValue only works on methods so you must add the getId method.
You should be able to skip your custom serializer altogether.
I wrote an example for a custom Timestamp.class serialization/deserialization, but you could use it for what ever you want.
When creating the object mapper do something like this:
public class JsonUtils {
public static ObjectMapper objectMapper = null;
static {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
}
for example in java ee you could initialize it with this:
import java.time.LocalDateTime;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
objectMapper.registerModule(s);
};
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
where the serializer should be something like this:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {
#Override
public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
String stringValue = value.toString();
if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
jgen.writeString(stringValue);
} else {
jgen.writeNull();
}
}
#Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
and deserializer something like this:
import java.io.IOException;
import java.sql.Timestamp;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {
#Override
public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
SqlTimestampConverter s = new SqlTimestampConverter();
String value = jp.getValueAsString();
if(value != null && !value.isEmpty() && !value.equals("null"))
return (Timestamp) s.convert(Timestamp.class, value);
return null;
}
#Override
public Class<Timestamp> handledType() {
return Timestamp.class;
}
}
These are behavior patterns I have noticed while trying to understand Jackson serialization.
1) Assume there is an object Classroom and a class Student. I've made everything public and final for ease.
public class Classroom {
public final double double1 = 1234.5678;
public final Double Double1 = 91011.1213;
public final Student student1 = new Student();
}
public class Student {
public final double double2 = 1920.2122;
public final Double Double2 = 2324.2526;
}
2) Assume that these are the serializers we use for serializing the objects into JSON. The writeObjectField uses the object's own serializer if it is registered with the object mapper; if not, then it serializes it as a POJO. The writeNumberField exclusively only accepts primitives as arguments.
public class ClassroomSerializer extends StdSerializer<Classroom> {
public ClassroomSerializer(Class<Classroom> t) {
super(t);
}
#Override
public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double1-Object", value.double1);
jgen.writeNumberField("double1-Number", value.double1);
jgen.writeObjectField("Double1-Object", value.Double1);
jgen.writeNumberField("Double1-Number", value.Double1);
jgen.writeObjectField("student1", value.student1);
jgen.writeEndObject();
}
}
public class StudentSerializer extends StdSerializer<Student> {
public StudentSerializer(Class<Student> t) {
super(t);
}
#Override
public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
jgen.writeStartObject();
jgen.writeObjectField("double2-Object", value.double2);
jgen.writeNumberField("double2-Number", value.double2);
jgen.writeObjectField("Double2-Object", value.Double2);
jgen.writeNumberField("Double2-Number", value.Double2);
jgen.writeEndObject();
}
}
3) Register only a DoubleSerializer with DecimalFormat output pattern ###,##0.000, in SimpleModule and the output is:
{
"double1" : 1234.5678,
"Double1" : {
"value" : "91,011.121"
},
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
You can see that the POJO serialization differentiates between double and Double, using the DoubleSerialzer for Doubles and using a regular String format for doubles.
4) Register DoubleSerializer and ClassroomSerializer, without the StudentSerializer. We expect that the output is such that if we write a double as an object, it behaves like a Double, and if we write a Double as a number, it behaves like a double. The Student instance variable should be written as a POJO and follow the pattern above since it does not register.
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2" : 1920.2122,
"Double2" : {
"value" : "2,324.253"
}
}
}
5) Register all serializers. The output is:
{
"double1-Object" : {
"value" : "1,234.568"
},
"double1-Number" : 1234.5678,
"Double1-Object" : {
"value" : "91,011.121"
},
"Double1-Number" : 91011.1213,
"student1" : {
"double2-Object" : {
"value" : "1,920.212"
},
"double2-Number" : 1920.2122,
"Double2-Object" : {
"value" : "2,324.253"
},
"Double2-Number" : 2324.2526
}
}
exactly as expected.
Another important note: If you have multiple serializers for the same class registered with the same Module, then the Module will select the serializer for that class that is most recently added to the list. This should not be used - it's confusing and I am not sure how consistent this is
Moral: if you want to customize serialization of primitives in your object, you must write your own serializer for the object. You cannot rely on the POJO Jackson serialization.
Jackson's JSON Views might be a simpler way of achieving your requirements, especially if you have some flexibility in your JSON format.
If {"id":7, "itemNr":"TEST", "createdBy":{id:3}} is an acceptable representation then this will be very easy to achieve with very little code.
You would just annotate the name field of User as being part of a view, and specify a different view in your serialisation request (the un-annotated fields would be included by default)
For example:
Define the views:
public class Views {
public static class BasicView{}
public static class CompleteUserView{}
}
Annotate the User:
public class User {
public final int id;
#JsonView(Views.CompleteUserView.class)
public final String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
}
And serialise requesting a view which doesn't contain the field you want to hide (non-annotated fields are serialised by default):
objectMapper.getSerializationConfig().withView(Views.BasicView.class);
In my case (Spring 3.2.4 and Jackson 2.3.1), XML configuration for custom serializer:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="false">
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="serializers">
<array>
<bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
</array>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
was in unexplained way overwritten back to default by something.
This worked for me:
CustomObject.java
#JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {
private Long value;
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
CustomObjectSerializer.java
public class CustomObjectSerializer extends JsonSerializer<CustomObject> {
#Override
public void serialize(CustomObject value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("y", value.getValue());
jgen.writeEndObject();
}
#Override
public Class<CustomObject> handledType() {
return CustomObject.class;
}
}
No XML configuration (<mvc:message-converters>(...)</mvc:message-converters>) is needed in my solution.
The problem in your case is the ItemSerializer is missing the method handledType() which needs to be overridden from JsonSerializer
public class ItemSerializer extends JsonSerializer<Item> {
#Override
public void serialize(Item value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeNumberField("itemNr", value.itemNr);
jgen.writeNumberField("createdBy", value.user.id);
jgen.writeEndObject();
}
#Override
public Class<Item> handledType()
{
return Item.class;
}
}
Hence you are getting the explicit error that handledType() is not defined
Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType()
Hope it helps someone. Thanks for reading my answer.
If your only requirement in your custom serializer is to skip serializing the name field of User, mark it as transient. Jackson will not serialize or deserialize transient fields.
[ see also: Why does Java have transient fields? ]
You have to override method handledType and everything will work
#Override
public Class<Item> handledType()
{
return Item.class;
}

Suppress wrapper object when serializing Java object into JSON using Jackson

I have a web service that returns a list as JSON. It uses Jackson to map a List of Java POJOs into JSON. The problem is that the JSON representation has a wrapper object around the array, and I just want the array. I.e., I'm getting this:
{"optionDtoList":[{...}, ..., {...}]}
when what I really want is this:
[{...}, ..., {...}]
I am serializing the Java List directly; I'm not wrapping the List with a wrapper object and serializing a wrapper object. It's Jackson that seems to be adding the JavaScript wrapper object.
I assume there's some annotation I can use on the POJO to suppress the wrapper object but I'm not seeing it.
Constraints on solution
I'd like to fix this on the service side rather than peeling off the wrapper on the client. The client is a jQuery UI widget (the autocomplete widget, not that it matters) that expects a simple array and I don't want to modify the widget itself.
What I've tried
I tried replacing the List of Java POJOs with an array of Java POJOs and the result is the same.
I tried #JsonTypeInfo(use = Id.NONE) thinking that that might suppress the wrapper, but it didn't.
In a test mode when I run:
org.codehaus.jackson.map.ObjectMapper mapper = new org.codehaus.jackson.map.ObjectMapper();
String json = mapper.writeValueAsString( Arrays.asList("one","two","three","four","five") );
System.out.println(json);
returns:
["one","two","three","four","five"]
which is the behavior you are expecting right?
I could see that when I return this list via a Spring controller and let MappingJacksonJsonView handle transforming the list to a json, then yes there is a wrapper around it, which tells me that the MappingJacksonJsonView is the one adding the wrapper. One solution then would be to explicitly return the json from your controller, say:
#RequestMapping(value = "/listnowrapper")
public #ResponseBody String listNoWrapper() throws Exception{
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(Arrays.asList("one","two","three","four","five"));
}
I get the same problem as you.
After add #ResponseBody in front of my list in my method declaration, the problem was solved.
eg :
public #ResponseBody List<MyObject> getObject
You could write custom serializer:
public class UnwrappingSerializer extends JsonSerializer<Object>
{
#Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException
{
JavaType type = TypeFactory.type(value.getClass());
getBeanSerializer(type, provider).serialize(value, new UnwrappingJsonGenerator(jgen), provider);
}
private synchronized JsonSerializer<Object> getBeanSerializer(JavaType type, SerializerProvider provider)
{
JsonSerializer<Object> result = cache.get(type);
if (result == null) {
BasicBeanDescription beanDesc = provider.getConfig().introspect(type);
result = BeanSerializerFactory.instance.findBeanSerializer(type, provider.getConfig(), beanDesc);
cache.put(type, result);
}
return result;
}
private Map<JavaType, JsonSerializer<Object>> cache = new HashMap<JavaType, JsonSerializer<Object>>();
private static class UnwrappingJsonGenerator extends JsonGeneratorDelegate
{
UnwrappingJsonGenerator(JsonGenerator d)
{
super(d);
}
#Override
public void writeEndObject() throws IOException, JsonGenerationException
{
if (depth-- >= yieldDepth) {
super.writeEndObject();
}
}
#Override
public void writeFieldName(SerializedString name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
#Override
public void writeFieldName(String name) throws IOException, JsonGenerationException
{
if (depth >= yieldDepth) {
super.writeFieldName(name);
}
}
#Override
public void writeStartObject() throws IOException, JsonGenerationException
{
if (++depth >= yieldDepth) {
super.writeStartObject();
}
}
private int depth;
private final int yieldDepth = 2;
}
}
It will ignore outer objects on depth lower than specified (2 by default).
Then use it as follows:
public class UnwrappingSerializerTest
{
public static class BaseT1
{
public List<String> getTest()
{
return test;
}
public void setTest(List<String> test)
{
this.test = test;
}
private List<String> test;
}
#JsonSerialize(using = UnwrappingSerializer.class)
public static class T1 extends BaseT1
{
}
#JsonSerialize(using = UnwrappingSerializer.class)
public static class T2
{
public BaseT1 getT1()
{
return t1;
}
public void setT1(BaseT1 t1)
{
this.t1 = t1;
}
private BaseT1 t1;
}
#Test
public void test() throws IOException
{
ObjectMapper om = new ObjectMapper();
T1 t1 = new T1();
t1.setTest(Arrays.asList("foo", "bar"));
assertEquals("[\"foo\",\"bar\"]", om.writeValueAsString(t1));
BaseT1 baseT1 = new BaseT1();
baseT1.setTest(Arrays.asList("foo", "bar"));
T2 t2 = new T2();
t2.setT1(baseT1);
assertEquals("{\"test\":[\"foo\",\"bar\"]}", om.writeValueAsString(t2));
}
}
Notes:
It expects only single field wrapper and will generate invalid JSON on something like {{field1: {...}, field2: {...}}
If you use custom SerializerFactory you probably will need to pass it to the serializer.
It uses separate serializer cache so this also can be an issue.
Honestly, I wouldn't be too quick to try to fix this problem as having the wrapper does create a situation where your code is more extensible. Should you expand this in the future to return other objects, your clients consuming this web service most likely won't need to change the implementation.
However, if all clients expect an array that is unnamed, adding more properties in the future outside of that array may break the uniform interface.
With that said, everyone has their reasons for wanting to do something a certain way. What does the object look like that you are serializing? Are you serializing an object that contains an array, or are you serializing the actual array itself? If your POJO contains an array, then maybe the solution is to pull the array out of the POJO and serialize the array instead.
I stumbled upon this question while trying to solve the same problem, but was not using this with a #ResponseBody method, but was still encountering the "wrapper" in my serialized JSON. My solution was to add #JsonAnyGetter to the method/field, and then the wrapper would disappear from the JSON.
Apparently this is a known Jackson bug/workaround: http://jira.codehaus.org/browse/JACKSON-765.

Categories