Skip to content

Creating Custom (De)serializers

Suppose you have an object that's not straightforward like a POJO, and you want to serialize it in a special way.

In this example, Class A may contain information about either an array or an instance of Class B. We only want to serialize one of them at a time. The other will always be null.

ClassA.java

class ClassA {
    int[] arr = null;
    ClassB classB = null;
    boolean isClassB = false;
}

ClassB.java

class ClassB {
    boolean[] some;
    int[] data;
    String[] structure;
}

Attempting to serialize this as a POJO would result in a larger structure containing a null value, a filled value representing the correct structure, and the boolean that tells us which structure to pick.

But we obviously only want it to only result in the correct structure.

Incorrect

{
  "array": null,
  "classB": {
    "some": [
      true,
      false
    ],
    "data": [
      1,
      2,
      3
    ],
    "structure": [
      "this",
      "is",
      "wrong"
    ]
  },
  "isClassB": true
}

Correct

{
  "some": [
    true,
    false
  ],
  "data": [
    1,
    2,
    3
  ],
  "structure": [
    "this",
    "is",
    "right"
  ]
}

Attempting to deserialize either type would result in an error in both cases, because the structure of Class A matches neither an array nor Class B.

To get the correct results, we have to implement a custom deserializer:

import blue.endless.jankson.Jankson;
import blue.endless.jankson.annotation.Deserializer;
import blue.endless.jankson.annotation.Serializer;
import blue.endless.jankson.api.SyntaxError;

public class ClassA {
    private static final Jankson JANKSON = Jankson.builder().build();

    // Variables described above

    // Use the Serializer annotation to create a serializer
    @Serializer
    public JsonElement toJson() {
        // The boolean isClassB is a stand-in for your logic that decides what type to parse
        // You might use an enum if you have more than two types of data
        if (isClassB && classB != null) return JANKSON.toJson(classB);
        // If we can't do that, it might be the other type.
        else if (!isClassB && arr != null) return JANKSON.toJson(arr);
        // This object is invalid. Return an empty JSON object.
        else return JANKSON.toJson(new Object());
    }

    // Use the Deserializer annotation to create a deserializer
    @Deserializer
    // Take a JsonArray as an argument when you want to parse an array
    public static ClassA fromArray(JsonArray array) throws SyntaxError {
        var value = new ClassA();
        // Convert from a JsonArray to an int array
        value.arr = JANKSON.fromJson(array.toJson(), int[].class);
        // Set the boolean so we can re-serialize the data later
        value.isClassB = false;
        return value;
    }

    @Deserializer
    // Take a JsonObject when you want to parse a POJO
    public static ClassA fromObject(JsonObject object) throws SyntaxError {
        var value = new ClassA();
        // Convert from an object to your POJO instance
        value.classB = JANKSON.fromJson(object.toJson(), ClassB.class);
        // Set the boolean so we can re-serialize the data later
        value.isClassB = true;
        return value;
    }
}
import blue.endless.jankson.Jankson
import blue.endless.jankson.annotation.Deserializer
import blue.endless.jankson.annotation.Serializer
import blue.endless.jankson.api.SyntaxError as JanksonSyntaxError

class ClassA {
    // Variables described above

    // Use the Serializer annotation to create a serializer
    @Serializer
    fun toJson(): JsonElement {
        // The boolean isClassB is a stand-in for your logic that decides what type to parse
        // You might use an enum if you have more than two types of data
        return if (isClassB && classB != null) JANKSON.toJson(classB)
        // If we can't do that, it might be the other type.
        else if (!isClassB && arr != null) JANKSON.toJson(arr)
        // This object is invalid. Return an empty JSON object.
        else JANKSON.toJson(Any())
    }

    companion object {
        private val JANKSON: Jankson = Jankson.builder().build()

        // Use the Deserializer annotation to create a deserializer
        @Deserializer // Take a JsonArray as an argument when you want to parse an array
        @Throws(JanksonSyntaxError::class)
        fun fromArray(array: JsonArray): ClassA {
            val value = ClassA()
            // Convert from a JsonArray to an int array
            value.arr = JANKSON.fromJson(array.toJson(), IntArray::class.java)
            // Set the boolean so we can re-serialize the data later
            value.isClassB = false
            return value
        }

        @Deserializer // Take a JsonObject when you want to parse a POJO
        @Throws(JanksonSyntaxError::class)
        fun fromObject(`object`: JsonObject): ClassA {
            val value = ClassA()
            // Convert from an object to your POJO instance
            value.classB = JANKSON.fromJson(`object`.toJson(), ClassB::class.java)
            // Set the boolean so we can re-serialize the data later
            value.isClassB = true
            return value
        }
    }
}
import blue.endless.jankson.Jankson
import blue.endless.jankson.annotation.Deserializer
import blue.endless.jankson.annotation.Serializer
import blue.endless.jankson.api.SyntaxError

class ClassA {
    def JANKSON = Jankson.builder().build()

    // Variables described above

    // Use the Serializer annotation to create a serializer
    @Serializer
    JsonElement toJson() {
        // The boolean isClassB is a stand-in for your logic that decides what type to parse
        // You might use an enum if you have more than two types of data
        if (isClassB && classB != null) return JANKSON.toJson(classB)
        // If we can't do that, it might be the other type.
        else if (!isClassB && arr != null) return JANKSON.toJson(arr)
        // This object is invalid. Return an empty JSON object.
        else return JANKSON.toJson(new Object())
    }

    // Use the Deserializer annotation to create a deserializer
    @Deserializer
    // Take a JsonArray as an argument when you want to parse an array
    static ClassA fromArray(JsonArray array) throws SyntaxError {
        def value = new ClassA()
        // Convert from a JsonArray to an int array
        value.arr = JANKSON.fromJson(array.toJson(), int[].class)
        // Set the boolean so we can re-serialize the data later
        value.isClassB = false
        return value
    }

    @Deserializer
    // Take a JsonObject when you want to parse a POJO
    static ClassA fromObject(JsonObject object) throws SyntaxError {
        def value = new ClassA()
        // Convert from an object to your POJO instance
        value.classB = JANKSON.fromJson(object.toJson(), ClassB.class)
        // Set the boolean so we can re-serialize the data later
        value.isClassB = true
        return value
    }
}