0

Consider the following json structure representing a basic check-in information

{
  "id": "53481198005",
  "date": "1995-01-01 00:00:00",
  "latitude": "50.765391",
  "longitude": "60.765391"
}

During deserialization, I want to consume latitude and longitude and create a single object called Location. In other words, I want to parse this json with POJO classes similar to:

@Serializable
data class CheckInRecord(val id: String, val date: String, val location: Location)

@Serializable
data class Location(val latitude: String, val longitude: String)

How can I achieve this?

2
  • 1
    Why not just let them deserialize into lat and long, and then provide a location property that composes a value from the lat and long properties? To the world outside CheckInRecord it would be indistinguishable. Commented Jan 26, 2024 at 21:21
  • Your suggestion makes sense and simple, works for my case but I guess Location is a good example from domain perspective and in a real world scenario CheckInRecord can be huge. Here it just contains 3-4 attributes but we may encounter a larger POJO where you may want to clean the mess for the CheckInRecord class Commented Jan 26, 2024 at 22:35

1 Answer 1

0

You can approach this with the surrogate serializer pattern, which is in exactly the same fashion as in the question I answered earlier today, but in reverse (as that was about encoding rather than decoding).

To apply the pattern, you start off writing a serializable surrrogate object to match the JSON you want:

@Serializable
class CheckInRecordSurrogate(
    private val id: String,
    private val date: String,
    private val latitude: String,
    private val longitude: String
) {
    companion object {
        fun fromCheckInRecord(checkInRecord: CheckInRecord) = with(checkInRecord) {
            CheckInRecordSurrogate(id, date, location.latitude, location.longitude)
        }
    }

    fun toCheckInRecord() = CheckInRecord(id, date, Location(latitude, longitude))
}

This gives you the tools to implement the KSerializer interface for your target class CheckInRecord by 'plugging in the gaps' to translate the surrogate to your desired end object (and back, for completeness):

class CheckInRecordSerializer : KSerializer<CheckInRecord> {
    private val surrogateSerializer get() =  CheckInRecordSurrogate.serializer()
    override val descriptor: SerialDescriptor get() = surrogateSerializer.descriptor

    override fun deserialize(decoder: Decoder) = surrogateSerializer.deserialize(decoder).toCheckInRecord()

    override fun serialize(encoder: Encoder, value: CheckInRecord) {
        surrogateSerializer.serialize(encoder, CheckInRecordSurrogate.fromCheckInRecord(value))
    }
}

Finally, you must tell the framework to use your new serializer on CheckInRecord (note we don't even need to make Location serializable with this approach):

@Serializable(with = CheckInRecordSerializer::class)
data class CheckInRecord(val id: String, val date: String, val location: Location)

Now we can try it all out:

println(
    Json.decodeFromString<CheckInRecord>("""
        {
             "id": "53481198005",
             "date": "1995-01-01 00:00:00",
             "latitude": "50.765391",
             "longitude": "60.765391"
        }
     """)
) 

Which prints what we expect, showing CheckInRecord was successfully instantiated:

CheckInRecord(id=53481198005, date=1995-01-01 00:00:00, location=Location(latitude=50.765391, longitude=60.765391))
Sign up to request clarification or add additional context in comments.

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.