简体   繁体   中英

Custom Serializer with MongoDB, Jackson, and MongoJack

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) { ... }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM