Search code examples
jsonkotlingsonktorjson-serialization

Specify class fields to be serialized to JSON in Ktor


Serving JSON content in Ktor as described in HTTP API - Quick Start - Ktor, as shown in the examples, works for common collections (lists, maps, etc.) and data classes. However, if I want to serialize a class that is not a data class and has fields that I want to exclude, how do I specify the fields to be serialized and their serialized names? Assume that I am using Gson, can I do it in the same way as serializing a class object using Gson directly?


Solution

  • Using Gson, you have a couple of options to the best of my knowledge.

    1. Using Transient

    If you mark a field with @Transient (transient in Java) this will be excluded from serialization:

    data class Foo(
        @Transient val a: Int,
        val b: Int)
    

    Here, b will be serialized and a will not.

    This comes with a huge downside - almost every framework in java takes @Transient into account and sometimes you don't want it to be serialized by Gson, but you might want to persist it to the database for example (if you'd be using the same class for both). To account for this, there's another option, using @Expose.

    2. Using Expose

    You need to create the gson instance using the builder:

    val gson = GsonBuilder()
        .excludeFieldsWithoutExposeAnnotation()
        .create();
    

    Now, fields without @Expose won't be serialized:

    data class Foo(
        val a: Int,
        @Expose val b: Int)
    

    Again, a will not be serialized, but b will.

    3. Using exclusion strategies

    A more advanced method is the usage of exclusion strategies. This allows for loads of introspections on the fields. From custom annotations to the field name or type.

    Again, you need to create a gson with a builder:

    val gson = GsonBuilder()
        .addSerializationExclusionStrategy(strategyInstance)
        .create();
    

    And you define a strategy like:

    object : ExclusionStrategy() {
      override fun shouldSkipField(field: FieldAttributes): Boolean {
      }
    
      override fun shouldSkipClass(clazz: Class<*>): Boolean {
      }
    }
    

    inside shouldSkipField you return true when you don't want to serialize the field and false when you do. Because it receives a FieldAttributes you can get a lot of properties from the field such as name and annotations. This allows for very fine-grained control.

    Lastly, you can set this strategy for deserialization as well and for both - addDeserializationExclusionStrategy and setExclusionStrategies.