Extension methods for libGDX JSON serialization API.
The libGDX JSON reader and writer methods often consume Class
parameters, which forces the
Type::class.java
syntax on Kotlin users. Fortunately, Kotlin brings reified generics which effectively
allow passing a Class
parameter through a generic type. This module mostly offers extension methods
with reified generics to avoid using ::class.java
in your code, as well as to allow type inference
and better type safety. Additionally, it provides a couple of classes to facilitate creation of custom
serializers.
KTX brings the following additions to libGDX Json
API:
- Extension methods and functions:
fromJson
addClassTag
getTag
setElementType
setSerializer
readValue
readOnlySerializer
- Classes:
JsonSerializer<T>
ReadOnlyJsonSerializer<T>
All of these extension methods are consistent with the official Json
API, but provide improved Kotlin typing
signatures and inlined reified type parameters to avoid passing Class
instances and improve code readability.
A comparison of the APIs when used from Kotlin:
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.utils.Json
import ktx.json.fromJson
class MyClass
fun deserialize(file: FileHandle): MyClass {
val json = Json()
// Using libGDX API designed for Java:
return json.fromJson(MyClass::class.java, file)
// Using KTX Kotlin extensions:
return json.fromJson<MyClass>(file)
}
Creating a new Json
serializer instance with custom parameters:
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Array as GdxArray
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import ktx.json.*
class Card
data class Player(var cards: GdxArray<Card>)
fun getSerializer(): Json {
val json = Json()
// Add shorthands for two classes:
json.addClassTag<Vector2>("vec2")
json.addClassTag<Color>("color")
// Set the type of elements in the "cards" collection of Player objects:
json.setElementType<Player, Card>("cards")
// Add a custom serializer for Vector2:
json.setSerializer(object : Json.Serializer<Vector2> {
override fun write(json: Json, vector: Vector2, knownType: Class<*>) {
json.writeObjectStart()
json.writeValue("x", vector.x)
json.writeValue("y", vector.y)
json.writeObjectEnd()
}
override fun read(json: Json, jsonData: JsonValue, type: Class<*>): Vector2 {
val vector2 = Vector2()
vector2.x = jsonData.getFloat("x")
vector2.y = jsonData.getFloat("y")
return vector2
}
})
return json
}
A class with custom serializable implementation:
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import ktx.json.*
class Card
class Player(
var position: Vector2 = Vector2(),
var cards: List<Card> = emptyList()
) : Json.Serializable {
override fun read(json: Json, jsonData: JsonValue) {
position = json.readValue(jsonData, "position") // Type inference.
cards = json.readArrayValue(jsonData, "cards") // Type inference, better type safety.
}
override fun write(json: Json) {
json.writeValue("position", position)
json.writeValue("cards", cards)
}
}
Parsing a JSON object:
import com.badlogic.gdx.utils.Json
import ktx.json.*
val json = Json()
val player: Player = json.fromJson("""{
"pos": {"x": 10, "y": 10},
"cards": [1, 2, 3, 5, 8, 13]
}""")
Using a custom serializer:
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue
import ktx.json.*
class Vector2AsArraySerializer: JsonSerializer<Vector2> {
override fun read(json: Json, jsonValue: JsonValue, type: Class<*>?): Vector2
= jsonValue.asFloatArray().let { (x, y) -> Vector2(x, y) }
override fun write(json: Json, value: Vector2, type: Class<*>?) {
json.writeArrayStart()
json.writeValue(value.x)
json.writeValue(value.y)
json.writeArrayEnd()
}
}
// A read-only serializer can be created with a simple lambda expression:
val vector2AsArraySerializer = readOnlySerializer<Vector2> { jsonValue ->
jsonValue.asFloatArray().let { (x, y) -> Vector2(x, y) }
}
val json = Json().apply {
setSerializer(vector2AsArraySerializer)
}
val player: Player = json.fromJson("""{
"pos": [10, 10]
"cards": [1, 2, 3, 5, 8, 13]
}""")
libGDX JSON is quite limited, verbose and poorly tested compared to some other JSON serialization libraries. It also accepts and produces corrupted JSON files by default, since it omits quotation marks, which might be problematic when integrating with external services.
However, if your project is simple enough and you want to avoid including additional JSON
serialization libraries in your game, official libGDX Json
can be enough. In most other
cases, you should probably look into other popular serialization libraries:
- kotlinx.serialization provides reflection-free serialization to JSON, CBOR and protobuf. Serialization code is produced at compile time for classes marked with an annotation.
- Many popular JSON serialization libraries for Java:
Gson
,Jackson
,Moshi
,org.json
to name a few.