v1.0.5-SNAPSHOT started json and kotlinx serialization support
This commit is contained in:
parent
5cfc15cf17
commit
171e413c5f
43
docs/json_and_kotlin_serialization.md
Normal file
43
docs/json_and_kotlin_serialization.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Json support
|
||||
|
||||
Since 1.0.5 we start adding JSON support.
|
||||
|
||||
Right now we only support basic types: maps, lists, strings, numbers, booleans. It is not yet capable of serializing classes. This functionality will be added in the 1.0.6 release.
|
||||
|
||||
## Serializae to kotlin string
|
||||
|
||||
// in lyng
|
||||
assertEquals("{\"a\":1}", {a: 1}.toJsonString())
|
||||
void
|
||||
>>> void
|
||||
|
||||
From the kotln side, you can use `Obj.toJson()` and deserialization helpers:
|
||||
|
||||
```kotlin
|
||||
/**
|
||||
* Decodes the current object into a deserialized form using the provided deserialization strategy.
|
||||
* It is based on [Obj.toJson] and uses existing Kotlin Json serialization, without string representation
|
||||
* (only `JsonElement` to carry information between Kotlin and Lyng serialization worlds), thus efficient.
|
||||
*
|
||||
* @param strategy The deserialization strategy that defines how the object should be decoded.
|
||||
* @param scope An optional scope used during deserialization to define the context. Defaults to a new instance of Scope.
|
||||
* @return The deserialized object of type T.
|
||||
*/
|
||||
suspend fun <T>Obj.decodeSerializableWith(strategy: DeserializationStrategy<T>, scope: Scope = Scope()): T =
|
||||
Json.decodeFromJsonElement(strategy,toJson(scope))
|
||||
|
||||
/**
|
||||
* Decodes a serializable object of type [T] using the provided decoding scope. The deserialization uses
|
||||
* [Obj.toJson] and existing Json based serialization ithout using actual string representation, thus
|
||||
* efficient.
|
||||
*
|
||||
* @param T The type of the object to be decoded. Must be a reified type.
|
||||
* @param scope The scope used during decoding. Defaults to a new instance of [Scope].
|
||||
*/
|
||||
suspend inline fun <reified T>Obj.decodeSerializable(scope: Scope= Scope()) =
|
||||
decodeSerializableWith<T>(serializer<T>(), scope)
|
||||
```
|
||||
|
||||
Note that lyng-2-kotlin deserialization with `kotlinx.serialization` is working based on JsonElement as information carrier, without formatting and parsing actual Json strings. This is why we use `Json.decodeFromJsonElement` instead of `Json.decodeFromString`. Such approach gives satisfactory performance without writing and supporting custom `kotlinx.serialization` codecs.
|
||||
|
||||
|
||||
@ -205,7 +205,7 @@ Much like let, but it does not alter returned value:
|
||||
>>> void
|
||||
|
||||
While it is not altering return value, the source object could be changed:
|
||||
|
||||
also
|
||||
class Point(x,y)
|
||||
val p = Point(1,2).also { it.x++ }
|
||||
assertEquals(p.x, 2)
|
||||
@ -1533,7 +1533,7 @@ assertEquals(null, (buzz as? Foo)?.runA())
|
||||
|
||||
Notes:
|
||||
- Resolution order uses C3 MRO (active): deterministic, monotonic order suitable for diamonds and complex hierarchies. Example: for `class D() : B(), C()` where both `B()` and `C()` derive from `A()`, the C3 order is `D → B → C → A`. The first visible match wins.
|
||||
- `private` is visible only inside the declaring class; `protected` is visible from the declaring class and any of its transitive subclasses. Qualification (`this@Type`) or casts do not bypass visibility.
|
||||
- `private` is visible only inside the declaring class; `protected` is visible from the declaring class and any of its transitive subclasses. Qualialsofication (`this@Type`) or casts do not bypass visibility.
|
||||
- Safe‑call `?.` works with `as?` for optional dispatch.
|
||||
|
||||
To get details on OOP in Lyng, see [OOP notes](oop.md).
|
||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "1.0.4-SNAPSHOT"
|
||||
version = "1.0.5-SNAPSHOT"
|
||||
|
||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||
|
||||
|
||||
@ -17,8 +17,13 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.serializer
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -376,6 +381,10 @@ open class Obj {
|
||||
return null
|
||||
}
|
||||
|
||||
open suspend fun toJson(scope: Scope = Scope()): JsonElement {
|
||||
scope.raiseNotImplemented("toJson for ${objClass.className}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val rootObjectType = ObjClass("Obj").apply {
|
||||
@ -418,7 +427,9 @@ open class Obj {
|
||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||
newValue
|
||||
}
|
||||
|
||||
addFn("toJsonString") {
|
||||
thisObj.toJson(this).toString().toObj()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -520,6 +531,10 @@ object ObjNull : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonNull
|
||||
}
|
||||
|
||||
override val objClass: ObjClass by lazy {
|
||||
object : ObjClass("Null") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
@ -572,4 +587,25 @@ data class ObjNamespace(val name: String) : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the current object into a deserialized form using the provided deserialization strategy.
|
||||
* It is based on [Obj.toJson] and uses existing Kotlin Json serialization, without string representation
|
||||
* (only `JsonElement` to carry information between Kotlin and Lyng serialization worlds), thus efficient.
|
||||
*
|
||||
* @param strategy The deserialization strategy that defines how the object should be decoded.
|
||||
* @param scope An optional scope used during deserialization to define the context. Defaults to a new instance of Scope.
|
||||
* @return The deserialized object of type T.
|
||||
*/
|
||||
suspend fun <T>Obj.decodeSerializableWith(strategy: DeserializationStrategy<T>, scope: Scope = Scope()): T =
|
||||
Json.decodeFromJsonElement(strategy,toJson(scope))
|
||||
|
||||
/**
|
||||
* Decodes a serializable object of type [T] using the provided decoding scope. The deserialization uses
|
||||
* [Obj.toJson] and existing Json based serialization ithout using actual string representation, thus
|
||||
* efficient.
|
||||
*
|
||||
* @param T The type of the object to be decoded. Must be a reified type.
|
||||
* @param scope The scope used during decoding. Defaults to a new instance of [Scope].
|
||||
*/
|
||||
suspend inline fun <reified T>Obj.decodeSerializable(scope: Scope= Scope()) =
|
||||
decodeSerializableWith<T>(serializer<T>(), scope)
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -62,6 +64,10 @@ data class ObjBool(val value: Boolean) : Obj() {
|
||||
return value == other.value
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonPrimitive(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("Bool") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder,lynonType: LynonType?): Obj {
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -161,6 +163,10 @@ class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonPrimitive(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Zero = ObjInt(0, true)
|
||||
val One = ObjInt(1, true)
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.statement
|
||||
@ -188,6 +190,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
|
||||
override suspend fun lynonType(): LynonType = LynonType.List
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonArray(list.map { it.toJson(scope) })
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("List", ObjArray) {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@ -141,6 +143,12 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
encoder.encodeAnyList(scope, values, fixedSize = true)
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonObject(
|
||||
map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap()
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.statement
|
||||
@ -100,6 +102,10 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
encoder.encodeReal(value)
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonPrimitive(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type: ObjClass = object : ObjClass("Real") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
|
||||
@ -19,6 +19,8 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
@ -116,6 +118,10 @@ data class ObjString(val value: String) : Obj() {
|
||||
encoder.encodeBinaryData(value.encodeToByteArray())
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonPrimitive(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("String") {
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
|
||||
@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
|
||||
@ -3797,8 +3798,21 @@ class ScriptTest {
|
||||
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class JSTest1(val foo: String,val one: Int, val ok: Boolean)
|
||||
|
||||
@Test
|
||||
fun testToJson() = runTest {
|
||||
val x = eval("""{ "foo": "bar", "one": 1, "ok": true }""")
|
||||
println(x.toJson())
|
||||
assertEquals(x.toJson().toString(), """{"foo":"bar","one":1,"ok":true}""")
|
||||
assertEquals(
|
||||
(eval("""{ "foo": "bar", "one": 1, "ok": true }.toJsonString()""") as ObjString).value,
|
||||
"""{"foo":"bar","one":1,"ok":true}""")
|
||||
println(x.decodeSerializable<JSTest1>())
|
||||
assertEquals(JSTest1("bar", 1, true), x.decodeSerializable<JSTest1>())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -352,4 +352,9 @@ class BookTest {
|
||||
fun testRegex() = runBlocking {
|
||||
runDocTests("../docs/Regex.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJson() = runBlocking {
|
||||
runDocTests("../docs/json_and_kotlin_serialization.md")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user