How to do custom serialization/deserialization using JACKSON? - java

I am trying to convert the following gson serialization to JACKSON serialization. Please let me know what i need to change to make it work for JACKSON
public class AbstractElementAdapter
implements JsonSerializer<AbstractElement>, JsonDeserializer<AbstractElement>
{
#Override
public JsonElement serialize(AbstractElement src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
JsonObject properties = context.serialize(src, src.getClass()).getAsJsonObject();
if (src instanceof TruncatedElement) {
result.add("type", new JsonPrimitive(((TruncatedElement) src).getClassName()));
properties.remove("className");
} else {
result.add("type", new JsonPrimitive(src.getClass().getSimpleName()));
}
result.add("properties", properties);
return result;
}
#Override
public AbstractElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String type = jsonObject.get("type").getAsString();
JsonElement element = jsonObject.get("properties");
try {
return context.deserialize(element, Class.forName("com.zreflect.emyed.whiteboard.model.element." + type));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("Unknown element type: " + type, cnfe);
}
}
}

You can create a custom serializer, something like this:
public class ItemSerializer extends JsonSerializer<AbstractElement> {
#Override
public void serialize(AbstractElement src, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
if (src instanceof TruncatedElement) {
jgen.writeStringField("type",((TruncatedElement) src).getClassName());
jgen.writeObjectFieldStart("properties");
//use jgen.writeStringField();
//jgen.writeNumberField();
//etc to every one of the values,
//but skipping className
jgen.writeEndObject();
} else {
jgen.writeStringField("type", src.getClass().getSimpleName() );
//write everythin
jgen.writeObjectField("properties", src);
}
jgen.writeEndObject();
}
}
And register it with the ObjectMapper and then do the serialization:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(yourObject.class, new ItemSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(yourObject);
To the trick of skipping className, you could also want to use a custom field filter, you have a great example here:
http://www.baeldung.com/jackson-ignore-properties-on-serialization

Jackson allows you to specify serializers through annotations. For example, see the trivial example below:
#JsonSerialize(using FooToStringSerializer)
public class Foo implements Serializable {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
Then, if all I wanted to see when the object was serialized was bar, I would create the serializer like so:
public class FooToStringSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(final Foo value, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException
{
jgen.writeObject(value.getBar());
}
For deserialization, you can create a deserializer and register it with the ObjectMapper that will be doing the deserialization.
To register a deserializer with an object mapper, do the below:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new FooDeserializer());
mapper.registerModule(module);
For a really easy to follow example of custom deserialization, see this link:
http://www.baeldung.com/jackson-deserialization

Related

Jackson JsonDeserializer delegate deserialization of a field back to default deserializer of that field type

Given
public class ConstraintMatch {
protected String constraintName;
protected Score score;
...
}
I have the following serializer in Jackson:
public class ConstraintMatchJacksonJsonSerializer extends JsonSerializer<ConstraintMatch> {
#Override
public void serialize(ConstraintMatch constraintMatch, JsonGenerator generator, SerializerProvider serializers)
throws IOException {
generator.writeStartObject();
generator.writeStringField("constraintName", constraintMatch.getConstraintName());
generator.writeFieldName("score");
// Delegate to serialization to the default Score serializer
serializers.findValueSerializer(Score.class)
.serialize(constraintMatch.getScore(), generator, serializers);
generator.writeEndObject();
}
}
How do I write a deserializer that also delegates to the default deserializer?
public class ConstraintMatchJacksonJsonDeserializer extends JsonDeserializer<ConstraintMatch> {
#Override
public ConstraintMatch deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode tree = parser.readValueAsTree();
String constraintName = tree.get("constraintName").asText();
JsonNode scoreNode = tree.get("score");
Score score = ...; // How do I delegate to the default deserializer?
return new ConstraintMatch(constraintName, score);
}
}
I've looked at findContextualValueDeserializer() etc, but I can't create a BeanProperty instance.
In a similar situation, I actually found there were two problems to solve. Firstly, as you say, the need to delegate back to the normal deserializer. But the other problem I encountered was how to feed the JsonNode (TreeNode below) into that next deserialize(JsonParser, ...).
The following is a working sample from that situation, where I wanted to do a lookahead to figure out the subclass.
Hopefully the node here is your scoreNode. And it sounds like objectClass is just Score.class for you.
#Override
public T deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
TreeNode node = parser.readValueAsTree();
// Select the subclass to deserialize as
Class<? extends T> objectClass = deduceClass(node);
// This based on ObjectMapper._convert()
// - the problem here was the JsonParser (parser) had gone past the current node
TokenBuffer buf = new TokenBuffer(mapper, false);
SerializationConfig config = mapper.getSerializationConfig()
.without(SerializationFeature.WRAP_ROOT_VALUE);
DefaultSerializerProvider serializerProvider = ((DefaultSerializerProvider) mapper
.getSerializerProvider()).createInstance(config,
mapper.getSerializerFactory());
serializerProvider.serializeValue(buf, node);
JsonParser nestedParser = buf.asParser();
nestedParser.nextToken();
JsonDeserializer<Object> deserializer = ctxt
.findRootValueDeserializer(
mapper.getTypeFactory().constructType(objectClass));
#SuppressWarnings("unchecked")
T obj = (T) deserializer.deserialize(nestedParser, ctxt);
return obj;
}
(Just in case, this was with Jackson 2.7.9)
I'd be pleased to hear about a simpler way to create a JsonParser from a node.
Serializing this:
constraintMatch.getConstraintPackage());
generator.writeStringField("constraintName", constraintMatch.getConstraintName());
generator.writeFieldName("score");
// Delegate to PolymorphicScoreJacksonJsonSerializer
JsonSerializer<Object> scoreSerializer = serializers.findValueSerializer(Score.class);
scoreSerializer.serialize(constraintMatch.getScore(), generator, serializers);
generator.writeEndObject();
Can be deserialized with this:
parser.nextToken();
if (!"constraintName".equals(parser.getCurrentName())) {
throw new IllegalStateException(...);
}
parser.nextToken();
String constraintName = parser.getValueAsString();
parser.nextToken();
if (!"score".equals(parser.getCurrentName())) {
throw new IllegalStateException(...);
}
parser.nextToken();
JsonDeserializer<Object> scoreDeserializer = context.findNonContextualValueDeserializer(context.constructType(Score.class));
Score score = (Score) scoreDeserializer.deserialize(parser, context);

Unable to deserialize java.awt.color using jackson deserializer

public class TestJacksonColor {
public static void main(String [] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Color black = new Color(0, 0, 0);
String json = objectMapper.writeValueAsString(black);
Color backToObject = objectMapper.readValue(json, Color.class);
}
}
The code attempts to take an java.awt.color class serialize it using jackson objectmapper. Take the resulting json string and deserialize it back to an java.awt.color class. However when doing the deserialization the following error occurs.
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class java.awt.Color]: can not instantiate from JSON object
You'll need a custom serializer and deserializer. There are some pre-built modules around, but I'm unaware of one that handles java.awt.Color.
Here's an example that defines the serialiser/deserializer pair and registers a module to handle Color objects:
public class JacksonColorTest {
public static class ColorSerializer extends JsonSerializer<Color> {
#Override
public void serialize(Color value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeFieldName("argb");
gen.writeString(Integer.toHexString(value.getRGB()));
gen.writeEndObject();
}
}
public static class ColorDeserializer extends JsonDeserializer<Color> {
#Override
public Color deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TreeNode root = p.getCodec().readTree(p);
TextNode rgba = (TextNode) root.get("argb");
return new Color(Integer.parseUnsignedInt(rgba.textValue(), 16), true);
}
}
public static void main(String [] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Color.class, new ColorSerializer());
module.addDeserializer(Color.class, new ColorDeserializer());
objectMapper.registerModule(module);
Color testColor = new Color(1, 2, 3, 4);
String json = objectMapper.writeValueAsString(testColor);
Color backToObject = objectMapper.readValue(json, Color.class);
if (!testColor.equals(backToObject)) {
throw new AssertionError("round trip failed");
}
}
}

How do I make Jackson ObjectMapper use my custom deserializer (applied with contentUsing)?

I am having trouble getting jackson to respect my custom JsonDeserializer. The situation is, I have a class MyClass that contains a list of another class, OtherClass, that is outside of my control (so I can't annotate it). This OtherClass class is an interface with multiple implementations. I don't care what the original OtherClass was, I want them to always deserialize as BasicOtherClass.
Here is what I have:
#Getter
public class MyClass {
#JsonProperty("otherclasses")
#JsonSerialize(contentUsing=OtherClassSerializer.class)
#JsonDeserialize(contentUsing=OtherClassDeserializer.class)
private List<OtherClass> otherClasses;
public MyClass(
#JsonProperty("otherclasses")
#JsonDeserialize(contentUsing=OtherClassDeserializer.class)
List<OtherClass> otherClasses) {
this.otherClass = otherClass;
}
}
public static class OtherClassSerializer extends JsonSerializer<OtherClass> {
#Override
public void serialize(OtherClass otherClass, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeStringField("name", otherClass.getName());
gen.writeStringField("value", otherClass.getValue());
gen.writeEndObject();
}
/** This method is required when default typing is enabled */
#Override
public void serializeWithType(
OtherClass otherClass, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer)
throws JsonProcessingException, IOException {
typeSer.writeTypePrefixForScalar(value, gen, OtherClass.class);
serialize(value, gen, serializers);
typeSer.writeTypeSuffixForScalar(value, gen);
}
}
public static class OtherClassDeserializer extends JsonDeserializer<Header> {
#Override
public Header deserialize(JsonParser p, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
if (p.getCurrentToken() != JsonToken.START_OBJECT) {
throw new IllegalStateException("Failed to parse OtherClass from json");
}
String name = null;
String value = null;
while (p.nextToken() != JsonToken.END_OBJECT) {
String key = p.getText();
p.nextToken();
String val = p.getText();
if (key.equals("name")) {
name = val;
} else if (key.equals("value")) {
value = val;
}
}
return new BasicOtherClass(name, value);
}
}
This is what I am trying to get to work:
ObjectMapper mapper = new ObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
OtherClass otherClass = new BufferedOtherClass("name value");
MyClass myClass = new MyClass(Lists.newArrayList(otherClass));
String json = mapper.writeValueAsString(myClass);
// json == ["com.bschlenk.MyClass", {"otherclass": ["java.util.ArrayList", [["com.other.OtherClass", {"name": "name", "value", "value"}]]]}]
But when I try to read that json back into MyClass, it fails:
MyClass parsed = mapper.readValue(json, MyClass.class);
// com.fasterxml.jackson.databind.JsonMappingException:
// Can not construct instance of org.apache.http.Header, problem:
// abstract types either need to be mapped to concrete types,
// have custom deserializer,
// or be instantiated with additional type information
This works when I don't have type information enabled. However, it is other code that is serializing MyClass that I don't have control of, and it has type info on.
Is what I am trying to do even possible? Why doesn't mapper.readValue use my custom JsonDeserializer class? Is this by design?

Customize JSON serialization with JaxRS

In a webservice call, I would like to return my objects with this JSON structure.
{
"date" : "30/06/2014",
"price" : {
"val" : "12.50",
"curr" : "EUR"
}
}
I'd like to map this JSON code to this Java structure (with joda-time and joda-money):
public class MyResponse {
LocalDate date;
Money price;
}
My webservice currently looks like this:
#javax.ws.rs.POST
#javax.ws.rs.Path("test")
#javax.ws.rs.Produces({MediaType.APPLICATION_JSON})
#javax.ws.rs.Consumes({MediaType.APPLICATION_JSON})
public MyResponse test(MyRequest request) {
MyResponse response = new MyResponse();
response.setDate(LocalDate.now());
response.setMoney(Money.parse("EUR 12.50"));
return response;
}
So my question is: where do I register a custom handler to format dates as I want as well as money representations?
If you are using Jackson (which should be the default for JBoss EAP 6) you can use custom JsonSerializers
For the LocalDate:
public class DateSerializer extends JsonSerializer<LocalDate> {
#Override
public void serialize(LocalDate date, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(date.toString("dd/MM/yyyy"));
}
}
For the Money:
public class MoneySerializer extends JsonSerializer<Money> {
#Override
public void serialize(Money money, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeStringField("val", money.getAmount().toString());
jgen.writeStringField("curr", money.getCurrencyUnit().getCurrencyCode());
jgen.writeEndObject();
}
}
Both Serializers can be registered globally:
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("MyModule", new Version(1, 0, 0, null));
module.addSerializer(Money.class, new MoneySerializer());
module.addSerializer(LocalDate.class, new DateSerializer());
objectMapper.registerModule(module);
}
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
For parsing JSON in this custom format you need to implement custom JsonDeserializers.
If you are using Jettison you can do the same thing with custom XmlAdapters.

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

Categories