I am struggling with come up with idea how to properly parse JSON like this:
{
"generic_key": { "version":1, "ttl":42 }
}
where expected kotlin class should look like this:
@Serializable
data class Config(val version: Int, val ttl: Long) {
@Transient
var key: String? = null // <== here comes generic_key
}
UPDATE
What I want to achieve is to get a kotlin class from string JSON and I don't know what key will be used as "generic_key".
UPDATE 2
Even something like this is okey for me:
@Serializable
data class ConfigWrapper(val map: Map<String, Config>)
Where there would be map with single item with key from jsonObject (e.g. generic_key) and with rest parsed with standard/generated Config.serializer.
CodePudding user response:
Use the following data classes
data class Config(
@SerializedName("generic_key" ) var genericKey : GenericKey? = GenericKey()
)
data class GenericKey (
@SerializedName("version" ) var version : Int? = null,
@SerializedName("ttl" ) var ttl : Int? = null
)
CodePudding user response:
If the key is dynamic and different, the map structure should be fine
@Serializable
data class Config(val version: Int, val ttl: Long)
val result = JsonObject(mapOf("generic_key" to Config(1, 42)))
CodePudding user response:
You can GSON or Jackson library
For GSON
implementation 'com.google.code.gson:gson:2.8.6'
interface JSONConvertable { fun toJSON(): String = Gson().toJson(this) } inline fun <reified T: JSONConvertable> String.toObject(): T = Gson().fromJson(this, T::class.java)For Jackson
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0"
val mapper = jacksonObjectMapper() tokenResultArray = resultData.toString().let { mapper.readValue(it) }
CodePudding user response:
At the end this works for me, but if there is more straight forward solution let me know.
private val jsonDecoder = Json { ignoreUnknownKeys = true }
private val jsonConfig = "...."
val result = jsonDecoder.parseToJsonElement(jsonConfig)
result.jsonObject.firstNonNullOf { (key, value) ->
config = jsonDecoder.decodeFromJsonElement<Config>(value).also {
it.key = key // this is generic_key (whatever string)
}
}
CodePudding user response:
Option 1. Define a custom deserializer, which will use plugin-generated serializer for Config class:
object ConfigDeserializer : DeserializationStrategy<Config> {
private val delegateSerializer = MapSerializer(String.serializer(), Config.serializer())
override val descriptor = delegateSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
val map = decoder.decodeSerializableValue(delegateSerializer)
val (k, v) = map.entries.first()
return v.apply { key = k }
}
}
To use it, you'll need to manually pass it to the decodeFromString method:
val result: Config = Json.decodeFromString(ConfigDeserializer, jsonString)
Option 2. Define a surrogate for Config class and a custom serializer, which will use plugin-generated serializer for ConfigSurrogate class, so that you could reject plugin-generated serializer for Config class and wire this custom serializer to Config class:
@Serializable
@SerialName("Config")
data class ConfigSurrogate(val version: Int, val ttl: Long)
object ConfigSerializer : KSerializer<Config> {
private val surrogateSerializer = ConfigSurrogate.serializer()
private val delegateSerializer = MapSerializer(String.serializer(), surrogateSerializer)
override val descriptor = delegateSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
val map = decoder.decodeSerializableValue(delegateSerializer)
val (k, v) = map.entries.first()
return Config(v.version, v.ttl).apply { key = k }
}
override fun serialize(encoder: Encoder, value: Config) {
surrogateSerializer.serialize(encoder, ConfigSurrogate(value.version, value.ttl))
}
}
@Serializable(with = ConfigSerializer::class)
data class Config(val version: Int, val ttl: Long) {
// actually, now there is no need for @Transient annotation
var key: String? = null // <== here comes generic_key
}
Now, custom serializer will be used by default:
val result: Config = Json.decodeFromString(jsonString)
