How do I use a custom Serializer with Jackson? - java

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

Related

Make Jackson serializer override specific ignored fields

I have Jackson annotated class like this :
public class MyClass {
String field1;
#JsonIgnore
String field2;
String field3;
#JsonIgnore
String field4;
}
Assume that I cannot change MyClass code. Then, how can I make ObjectMapper override the JsonIgnore for field2 only and serialize it to json ? I want it to ignore field4 though. Is this easy and few lines of code ?
My code for regular serialization :
public String toJson(SomeObject obj){
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = null;
try {
json = ow.writeValueAsString(obj);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return json;
}
You can use MixIn feature:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.addMixIn(MyClass.class, MyClassMixIn.class);
System.out.println(mapper.writeValueAsString(new MyClass()));
}
}
interface MyClassMixIn {
#JsonProperty
String getField2();
}
Above code prints:
{
"field1" : "F1",
"field2" : "F2",
"field3" : "F3"
}
You could use a custom serializer, like so (I leave the refactoring up to you):
private String getJson(final MyClass obj) {
final ObjectMapper om = new ObjectMapper();
om.registerModule(new SimpleModule(
"CustomModule",
Version.unknownVersion(),
Collections.emptyMap(),
Collections.singletonList(new StdSerializer<MyClass>(MyClass.class) {
#Override
public void serialize(final MyClass value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("field1", value.field1);
jgen.writeStringField("field2", value.field2);
jgen.writeStringField("field3", value.field3);
jgen.writeEndObject();
}
})));
try {
return om.writeValueAsString(obj);
} catch (final JsonProcessingException e) {
throw new RuntimeException(e);
}
}
You can find more information about custom serializers on google/stackoverflow, I think I answered something similar here

Jackson - Transform field value on serialization

In a Spring Boot applicaion with AngularJS frontend, a "Pin" field value has to be blackened on serialization, i.e., if the Pin field value is null in the POJO, the according JSON field has to remain blank; if the field value contains data, it has to be replaced with a "***" string.
Does Jackson provide a feature to get this done?
You can do it easily like following without any Custom Serializer
public class Pojo {
#JsonIgnore
private String pin;
#JsonProperty("pin")
public String getPin() {
if(pin == null) {
return "";
} else {
return "***";
}
}
#JsonProperty("pin")
public void setPin(String pin) {
this.pin = pin;
}
#JsonIgnore
public String getPinValue() {
return pin;
}
}
You can use Pojo.getPinValue() to get the exact value.
Try the following example.
public class Card {
public int id;
public String pin;
}
public class CardSerializer extends StdSerializer<Card> {
public CardSerializer() {
this(null);
}
public CardSerializer(Class<Card> t) {
super(t);
}
#Override
public void serialize(Card value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("pin", "****");
jgen.writeEndObject();
}
}
Then you need to register your customer serializer with the ObjectMapper
Card card = new Card(1, "12345");
ObjectMapper mapper = new ObjectMapper();
 
SimpleModule module = new SimpleModule();
module.addSerializer(Card.class, new CardSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(card);
There are some improvements you can do here like registering the serializer directly on the class, but you can read more about it here Section 4 - http://www.baeldung.com/jackson-custom-serialization

Generating nested JSON for incoming object

I have following class
public class Foo {
private String a;
private String b;
private String c;
private Bar d;
}
For this, I want to generate following JSON
{
"values":
{
"value_id":"<value_of_field_a>"
},
"bar":
{
"id":"<value_of_field_b>",
"object":
{
"<value_of_bar_object_d>"
}
}
"seq":"<value_of_field_c>"
}
Yes, I can create a pojo which will mimic this hierarchy but the example i posted here is way simpler than the actual object. is there a way i can generate custom hierarchy for JSON using Jackson
Write a custom serializer:
public class FooSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(Foo value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeObjectFieldStart("values");
jgen.writeStringField("value_id", value.getA());
jgen.writeEndObject();
// TODO: serialize the other fields
jgen.writeEndObject();
}
}
Register the custom serializer by annotating Foo with #JsonSerialize:
#JsonSerialize(using = FooSerializer.class)
public class Foo {
...
}

Customize jackson unmarshalling behavior

I am using Jackson fasterxml for unmarshalling JSON. In my object there are two kinds of properties:Input properties and Calculated properties. In the input JSON, I get only input values.
The calculated values are actually dependent on input values. I have to populate these values before the object gets referred. So I am just checking if there are any hooks provided by Jackson so that I can do my calculations there. For example JAXB provides afterUnmarshal method to customize the unmarshaling behavior:
void afterUnmarshal(Unmarshaller u, Object parent)
But I could not find similar information about customizing Jackson. Are any such framework hooks provided by Jackson to customize the unmarshaling behavior?
I'd rather recommend to keep your model objects immutable by using constructor creators. That is, all the JSON values are passed to a constructor which would initialize the other calculated properties.
Anyway, if you want to customize an object after deserialization (without writing a deserializer for every type) you can modify the deserializer in a way that at the end it calls a special method(s) of a newly constructed instance. Here is an example which would work for all the classes that implements a special interface (one can consider using an annotation to mark the post construct methods).
public class JacksonPostConstruct {
public static interface PostConstructor {
void postConstruct();
}
public static class Bean implements PostConstructor {
private final String field;
#JsonCreator
public Bean(#JsonProperty("field") String field) {
this.field = field;
}
public void postConstruct() {
System.out.println("Post construct: " + toString());
}
#Override
public String toString() {
return "Bean{" +
"field='" + field + '\'' +
'}';
}
}
private static class PostConstructDeserializer extends DelegatingDeserializer {
private final JsonDeserializer<?> deserializer;
public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
super(deserializer);
this.deserializer = deserializer;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return deserializer;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Object result = _delegatee.deserialize(jp, ctxt);
if (result instanceof PostConstructor) {
((PostConstructor) result).postConstruct();
}
return result;
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new PostConstructDeserializer(deserializer);
}
});
mapper.registerModule(module);
String json = "{\"field\":\"value\"}";
System.out.println(mapper.readValue(json, Bean.class));
}
}
Output:
Post construct: Bean{field='value'}
Bean{field='value'}
Let's assume that your JSON looks like this:
{
"input1" : "Input value",
"input2" : 3
}
And your POJO class looks like this:
class Entity {
private String input1;
private int input2;
private String calculated1;
private long calculated2;
...
}
In this case you can write a custom deserializer for your Entity class:
class EntityJsonDeserializer extends JsonDeserializer<Entity> {
#Override
public Entity deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
InnerEntity innerEntity = jp.readValueAs(InnerEntity.class);
Entity entity = new Entity();
entity.setInput1(innerEntity.input1);
entity.setInput2(innerEntity.input2);
entity.recalculate();
return entity;
}
public static class InnerEntity {
public String input1;
public int input2;
}
}
In above class you can see that Entity has a recalculate method. It could look like this:
public void recalculate() {
calculated1 = input1 + input2;
calculated2 = input1.length() + input2;
}
You can also move this logic to your deserializer class.
Now, you have to inform Jackson that you want to use your custom deserializer:
#JsonDeserialize(using = EntityJsonDeserializer.class)
class Entity {
...
}
The example below shows how to use these classes:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Entity.class));
This program prints:
Entity [input1=Input value, input2=3, calculated1=Input value3, calculated2=14]

Custom Jackson Serializer for Wrapper Object

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

Categories