2

I have a data class which has a property whose type is another data class, like this:

@Serializable
data class Vehicle (
  val color: String,
  val miles: Int,
  val year: Int,
  val garage: Garage
)

@Serializable
data class Garage (
  val latitude: Float,
  val longitude: Float,
  val name: String
)

Upon serializing, it produces output like this:

{ 
  "color" : "black" , 
  "miles" : 35000 , 
  "year" : 2017 , 
  "garage" : { "latitude" : 43.478342 , "longitude" : -91.337000 , "name" : "Paul's Garage" }
}

However I would like garage to be a literal string of its JSON representation, not an actual JSON object. In other words, the desired output is:

{ 
  "color" : "black" , 
  "miles" : 35000 , 
  "year" : 2017 , 
  "garage" : "{ \"latitude\" : 43.478342 , \"longitude\" : -91.337000 , \"name\" : \"Paul's Garage\" }"
}

How can I accomplish this in Kotlin? Can it be done with just kotlinx.serialization or is Jackson/Gson absolutely necessary?

Note that this output is for a specific usage. I cannot overwrite the base serializer because I still need to serialize/deserialize from normal JSON (the first example). In other words, the best scenario would be to convert the first JSON sample to the second, not necessarily to have the data class produce the 2nd sample directly.

Thanks!

2 Answers 2

1

Create a custom SerializationStrategy for Vehicle as follows:

val vehicleStrategy = object : SerializationStrategy<Vehicle> {
    override val descriptor: SerialDescriptor
        get() = buildClassSerialDescriptor("Vehicle") {
            element<String>("color")
            element<Int>("miles")
            element<Int>("year")
            element<String>("garage")
        }

    override fun serialize(encoder: Encoder, value: Vehicle) {
        encoder.encodeStructure(descriptor) {
            encodeStringElement(descriptor, 0, value.color)
            encodeIntElement(descriptor, 1, value.miles)
            encodeIntElement(descriptor, 2, value.year)
            encodeStringElement(descriptor, 3, Json.encodeToString(value.garage))
        }
    }
}

Then pass it to Json.encodeToString():

val string = Json.encodeToString(vehicleStrategy, vehicle)

Result:

{"color":"black","miles":35000,"year":2017,"garage":"{\"latitude\":43.47834,\"longitude\":-91.337,\"name\":\"Paul's Garage\"}"}

More info here

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks so much. I had read all of the documentation on Custom Serializers but was struggling trying to build a primitive KSerializer, it didn't click that I needed to create a SerializationStrategy until you laid it all out. Really terrific, thank you.
0

Here is a solution with a custom serializer for Garage and an additional class for Vehicle.

Garage to String serializer:

object GarageToStringSerializer : KSerializer<Garage> {
    override val descriptor = PrimitiveSerialDescriptor("GarageToString", PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: Garage) = encoder.encodeString(Json.encodeToString(value))
    override fun deserialize(decoder: Decoder): Garage = Json.decodeFromString(decoder.decodeString())
}

Auxiliary class:

@Serializable
data class VehicleDto(
    val color: String,
    val miles: Int,
    val year: Int,
    @Serializable(GarageToStringSerializer::class)
    val garage: Garage
) {
    constructor(v: Vehicle) : this(v.color, v.miles, v.year, v.garage)
}

The demanded result can be received with:

Json.encodeToString(VehicleDto(vehicle))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.