Custom Serializer with MongoDB, Jackson, and MongoJack - java

I have a class that, when serialized, should serialize one of its members in its place. My class is:
#JsonSerialize(using = MyClassSerializer.class)
public class MyClass implements Serializable {
/**
* A default ID for this class for serialization.
*/
private static final long serialVersionUID = 1L;
/**
* A member of this object.
*/
private final OtherClass otherClass;
...
/**
* Returns the instance of the member object.
*
* #return The instance of the member object.
*/
public OtherClass getOtherClass() {
return otherClass;
}
}
To accomplish this, I created a very simple custom serializer:
public class MyClassSerializer extends JsonSerializer<MyClass> {
/**
* Serializes only the OtherClass field.
*/
#Override
public void serialize(
final MyClass myClass,
final JsonGenerator generator,
final SerializerProvider provider)
throws IOException, JsonProcessingException {
// Write the schema.
generator.writeObject(myClass.getOtherClass());
}
}
This is the easiest (and, I believe, most correct) way to do something like this. Even if I wanted to, writing a custom serializer for OtherClass would be extremely complex because it is an abstract root class. This can be accomplished through Jackson, however, with a few annotations:
#JsonTypeInfo(
use = Id.NAME,
include = As.PROPERTY,
property = OtherClass.JSON_KEY_TYPE,
defaultImpl = OtherClassDefault.class)
#JsonSubTypes({
#JsonSubTypes.Type(
value = SubOtherClass1.class,
name = SubOtherClass1.TYPE_ID),
#JsonSubTypes.Type(
value = SubOtherClass2.class,
name = SubOtherClass2.TYPE_ID),
#JsonSubTypes.Type(
value = SubOtherClass3.class,
name = SubOtherClass3.TYPE_ID),
#JsonSubTypes.Type(
value = SubOtherClass4.class,
name = SubOtherClass4.TYPE_ID),
#JsonSubTypes.Type(
value = SubOtherClass5.class,
name = SubOtherClass5.TYPE_ID) })
#JsonAutoDetect(
fieldVisibility = Visibility.DEFAULT,
getterVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE,
creatorVisibility = Visibility.DEFAULT)
public abstract class OtherClass implements Serializable {
...
}
I have tested this using Jackson to serialize and deserialize instances of MyClass, and it works exactly as intended.
My application code is a little more complicated. I have a ContainerClass that has a member of type MyClass. I attempt to serialize an instance of ContainerClass through MongoJack:
// Get the authentication token collection.
JacksonDBCollection<ContainerClass, Object> collection =
JacksonDBCollection
.wrap(
MongoBinController
.getInstance()
.getDb()
.getCollection(COLLECTION_NAME),
ContainerClass.class);
// Save it.
collection.insert(container);
However, I receive the following error:
...
Caused by: java.lang.IllegalArgumentException: can't serialize class my.package.MyClass
at org.bson.BasicBSONEncoder._putObjectField(BasicBSONEncoder.java:270)
...
I can get it to work if I remove the #JsonSerialize from MyClass, however this results in the entire MyClass instance being serialized, which is not what I want. This makes me almost sure that the problem lies in my custom serializer, but I am not sure how else I am supposed to write it.
Thank you in advance.

One quick note that may help: when using polymorphic types, method called will be:
serializeWithType(...)
and not serialize(...). So you will need to implement that method; usually it will be something as simple as:
typeSer.writeTypePrefixForObject(value, jgen);
// implement actual content serialization, or delegate here:
this.serialize(...);
typeSer.writeTypeSuffixForObject(value, jgen);
but you may want to have a look at standard Jackson serializers. The only real distinction is that whereas serialize needs to output START_OBJECT, END_OBJECT directly, here we have to ask TypeSerializer to add those. This is necessary because type id inclusion may actually change these (I can elaborate on this, but for now that should be enough).
However: there may be much easier solution here. If you can add #JsonValue annotation on member you want to use instead of whole object (either directly, or via mix-in), that should do the trick:
#JsonValue
public OtherClass getOtherClass()...
and if you need to deserialize, you can use #JsonCreator like:
#JsonCreator
public MyClass(OtherClass surrogate) { ... }

Related

Write generic static method in enum base class that can extract subclass instance to do a Jackson conversion

I have some enum types that look like this:
public static enum Thingie {
ABC("abc"), DEF("def");
private String messageValue;
#JsonValue
public String getMessageValue() { return messageValue; }
private Thingie(String messageValue) { this.messageValue = messageValue; }
}
This will allow Jackson to properly marshal and unmarshal between string values and the enum type.
There may be times when I'd like to directly convert a string value to the enum value. This would be like the internal "fromValue()" method, but not quite the same:
public static Thingie messageValueOf(String messageValue) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(messageValue, Thingie.class);
}
I would like to convert this into a generic method AND put it into a base class, along with the "messageValue" property and accessor. The constructor would change to just call "super(messageValue)". Obviously, if I could do that, I would move the "mapper" to class level.
At this point, I've only attempted to write this as a generic method in the single enum type. I can't even get that working. I can't figure out how to extract the class from the template parameter. I've seen this particular question before, and there have been some answers, but I couldn't quite get it to work, and I imagine trying to do this in the base class would add additional complexity.
Let's assume I understood your problem (correct me if I am wrong).
The constructor would change to just call "super(messageValue)"
An enum can not extend a class, so you can't do that. But you can create an interface/class which you will delegate to for such queries (very simplistic code):
interface Test {
ObjectMapper MAPPER = new ObjectMapper();
static <T extends Enum<T>> T getIt(String s, Class<T> clazz) {
return MAPPER.convertValue(s, clazz);
}
}
public static void main(String[] args) {
Thingie abc = Test.getIt("abc", Thingie.class);
System.out.println(abc.ordinal());
}

Jackson: passing exta objects during deserialization

During deserialization, how can I pass in an extra object that's needed to initialize some class member? If I were doing deserialization "manually," the implementation might look like:
public class MyClass {
private MyDocumentObject do;
private String food;
public MyClass(JsonNode node, MyDocument document) {
this.do = document.createMyDocumentObject();
this.food = node.get("food").asText();
}
public String getFood() {
return this.food;
}
}
But I'd like to use Jackson's automatic mapping facilities and use a decorated constructor or custom deserializer, etc. and avoid implementing the deserialization within the class itself. Looking at example implementations using #JsonCreator or extending StdDeserializer, I can't see a way of saying "hey, please use this MyDocument object when you call the constructor." I'd like to avoid implementing and exposing a separate method that accepts a MyDocument that I have to invoke on every object that gets deserialized, e.g.
public createDocumentObject(MyDocument document) {
this.do = document.createMyDocumentObject();
}
I don't want to have this method at all, but if I had to, I'd want Jackson to call this method for me right after deserialization. That means I'd still have to somehow tell Jackson which MyDocument to use.

GWT-Jackson-APT not ignoring interface object from serialization

I have a class object that contains an interface variable used for callbacks that I don't want serialized into the JSON. I have attempted to use the #JsonIgnoreProperties() annotation to make it ignore the interface variable, but so far no luck. The pre-processor is choking with a IllegalArgumentException Couldn't make a guess for CallbackRun...
The interface looks generally like:
public interface callbackRun {
void runOnFinish();
}
With the broad strokes shape of my class defined as:
#JSONMapper
#JsonIgnoreProperties(ignoreUnknown=true)
public class itemInventory {
public static itemInventory_MapperImpl MAPPER = new itemInventory_MapperImpl();
private static final List<item> itemList = new ArrayList<>();
private callbackRun responseHandler = null;
/ * other variables, getters setters here */
}
What is the best method of getting GWT-jackson-APT to ignore this interface? Or do I have to completely redefine all my objects to remove my callback function references?
You can use #JsonIgnore by annotating the field
#JSONMapper
public class itemInventory {
public static itemInventory_MapperImpl MAPPER = new itemInventory_MapperImpl();
private static final List<item> itemList = new ArrayList<>();
#JsonIgnore
private callbackRun responseHandler = null;
/ * other variables, getters setters here */
}
The field will not be serialized when writing object to JSON and it will be ignored when reading object from JSON. You can always check the generated mappers and you will see a method initIgnoredProperties in the generated deserializer, also the ignored field will not be included in the generated serializer.

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

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

Gson add field during serialization

I can't find a simple way to add a custom field during serialization in Gson and I was hoping someone else may be able to help.
Here is a sample class to show my issue:
public class A {
String id;
String name;
...
}
When I serialize class A I would like to return something like:
{ "id":"123", "name":"John Doe", "url_to_user":"http://www.example.com/123" }
where url_to_user is not stored in my instance of class A, but can be generated with data in the instance of class A.
Is there a simple way of doing this? I would prefer to avoid writing an entire serializer just to add one field.
Use Gson.toJsonTree to get a JsonElement, with which you can interact dynamically.
A a = getYourAInstanceHere();
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(a);
jsonElement.getAsJsonObject().addProperty("url_to_user", url);
return gson.toJson(jsonElement);
Well, the top rated answer is quite a quick one and not essentially bad when you are lacking much time but here is the problem: There is no proper separation of concern
You are modifying the serialized JSON at the same place where you are writing your business logic. You should be doing all the serialization inside of a TypeAdapter or a JsonSerializer.
How can we maintain a proper separation of concern?
The answer wraps around a bit of additional complexity but the architecture demands it. Here we go(taken from my other answer):
First, we would be using a custom serializer for the type. Second, we would have to create a copy constructor inside the base class and a wrapper subclass as follows:
Note: The custom serializer might seem like an overkill but trust me, it pays off in long run for maintainability.
.
// Lets say the base class is named Cat
public class Cat {
public String name;
public Cat(String name) {
super();
this.name = name;
}
// COPY CONSTRUCTOR
public Cat(Cat cat) {
this.name = cat.name;
}
#Override
public String sound() {
return name + " : \"meaow\"";
};
}
// The wrapper subclass for serialization
public class CatWrapper extends Cat{
public CatWrapper(String name) {
super(name);
}
public CatWrapper(Cat cat) {
super(cat);
}
}
And the serializer for the type Cat:
public class CatSerializer implements JsonSerializer<Cat> {
#Override
public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {
// Essentially the same as the type Cat
JsonElement catWrapped = context.serialize(new CatWrapper(src));
// Here, we can customize the generated JSON from the wrapper as we want.
// We can add a field, remove a field, etc.
// The main logic from the top rated answer now here instead of *spilling* around(Kindly ignore the cat having a url for the sake of example)
return catWrapped.getAsJsonObject().addProperty("url_to_user", url);
}
}
So, why a copy constructor?
Well, once you define the copy constructor, no matter how much the base class changes, your wrapper will continue with the same role. Secondly, if we don't define a copy constructor and simply subclass the base class then we would have to "talk" in terms of the extended class, i.e, CatWrapper. It is quite possible that your components talk in terms of the base class and not the wrapper type.

Categories