Reputation: 690
I have a sealed class WebSocketMessage
which has some subclasses. The WebSocketMessage
has a field named type
which is used for differentiating between subclasses.
All of the subclasses have their own field named payload
which is of different type for each subclass.
Currently I am using Moshi's PolymorphicJsonAdapterFactory
so that these classes can be parsed from JSON and encoded to JSON.
This all works, but what I need is to encode the the payload
field to stringified JSON instead of JSON object.
Is there any possibility to write a custom adapter class to help me with this problem? Or is there any other solution so that I will not have to do this stringification manually?
I have tried looking into custom adapters but I can't find how I could pass moshi instance to adapter so that I can encode the given field to JSON and then stringify it, nor did I find anything else that could help me.
The WebSocketMessage
class with its subclasses:
sealed class WebSocketMessage(
val type: Type
) {
enum class Type(val type: String) {
AUTH("AUTH"),
PING("PING"),
FLOW_INITIALIZATION("FLOW_INITIALIZATION")
}
class Ping : WebSocketMessage(Type.PING)
class InitFlow(payload: InitFlowMessage) : WebSocketMessage(Type.FLOW_INITIALIZATION)
class Auth(payload: Token) : WebSocketMessage(Type.AUTH)
}
The Moshi instance with PolymorphicJsonAdapterFactory
:
val moshi = Moshi.Builder().add(
PolymorphicJsonAdapterFactory.of(WebSocketMessage::class.java, "type")
.withSubtype(WebSocketMessage.Ping::class.java, WebSocketMessage.Type.PING.type)
.withSubtype(
WebSocketMessage.InitFlow::class.java,
WebSocketMessage.Type.FLOW_INITIALIZATION.type
)
.withSubtype(WebSocketMessage.Auth::class.java, WebSocketMessage.Type.AUTH.type)
)
// Must be added last
.add(KotlinJsonAdapterFactory())
.build()
How I encode to JSON:
moshi.adapter(WebSocketMessage::class.java).toJson(WebSocketMessage.Auth(fetchToken()))
I currently get the JSON in the next format:
{
"type":"AUTH",
"payload":{
"jwt":"some_token"
}
}
What I would like to get:
{
"type":"AUTH",
"payload":"{\"jwt\":\"some_token\"}"
}
In the second example the payload is a stringified JSON object, which is exactly what I need.
Upvotes: 1
Views: 913
Reputation: 2672
You can create your own custom JsonAdapter
:
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class AsString
/////////////////////
class AsStringAdapter<T>(
private val originAdapter: JsonAdapter<T>,
private val stringAdapter: JsonAdapter<String>
) : JsonAdapter<T>() {
companion object {
var FACTORY: JsonAdapter.Factory = object : Factory {
override fun create(
type: Type,
annotations: MutableSet<out Annotation>,
moshi: Moshi
): JsonAdapter<*>? {
val nextAnnotations = Types.nextAnnotations(annotations, AsString::class.java)
return if (nextAnnotations == null || !nextAnnotations.isEmpty())
null else {
AsStringAdapter(
moshi.nextAdapter<Any>(this, type, nextAnnotations),
moshi.nextAdapter<String>(this, String::class.java, Util.NO_ANNOTATIONS)
)
}
}
}
}
override fun toJson(writer: JsonWriter, value: T?) {
val jsonValue = originAdapter.toJsonValue(value)
val jsonStr = JSONObject(jsonValue as Map<*, *>).toString()
stringAdapter.toJson(writer, jsonStr)
}
override fun fromJson(reader: JsonReader): T? {
throw UnsupportedOperationException()
}
}
/////////////////////
class Auth(@AsString val payload: Token)
/////////////////////
.add(AsStringAdapter.FACTORY)
.add(KotlinJsonAdapterFactory())
.build()
Upvotes: 1