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
|
>>> void
|
||||||
|
|
||||||
While it is not altering return value, the source object could be changed:
|
While it is not altering return value, the source object could be changed:
|
||||||
|
also
|
||||||
class Point(x,y)
|
class Point(x,y)
|
||||||
val p = Point(1,2).also { it.x++ }
|
val p = Point(1,2).also { it.x++ }
|
||||||
assertEquals(p.x, 2)
|
assertEquals(p.x, 2)
|
||||||
@ -1533,7 +1533,7 @@ assertEquals(null, (buzz as? Foo)?.runA())
|
|||||||
|
|
||||||
Notes:
|
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.
|
- 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.
|
- Safe‑call `?.` works with `as?` for optional dispatch.
|
||||||
|
|
||||||
To get details on OOP in Lyng, see [OOP notes](oop.md).
|
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
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
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
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,13 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
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.lyng.*
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -376,6 +381,10 @@ open class Obj {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open suspend fun toJson(scope: Scope = Scope()): JsonElement {
|
||||||
|
scope.raiseNotImplemented("toJson for ${objClass.className}")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val rootObjectType = ObjClass("Obj").apply {
|
val rootObjectType = ObjClass("Obj").apply {
|
||||||
@ -418,7 +427,9 @@ open class Obj {
|
|||||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||||
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 {
|
override val objClass: ObjClass by lazy {
|
||||||
object : ObjClass("Null") {
|
object : ObjClass("Null") {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
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
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
import net.sergeych.lynon.LynonEncoder
|
||||||
@ -62,6 +64,10 @@ data class ObjBool(val value: Boolean) : Obj() {
|
|||||||
return value == other.value
|
return value == other.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toJson(scope: Scope): JsonElement {
|
||||||
|
return JsonPrimitive(value)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = object : ObjClass("Bool") {
|
val type = object : ObjClass("Bool") {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder,lynonType: LynonType?): Obj {
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder,lynonType: LynonType?): Obj {
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonEncoder
|
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 {
|
companion object {
|
||||||
val Zero = ObjInt(0, true)
|
val Zero = ObjInt(0, true)
|
||||||
val One = ObjInt(1, true)
|
val One = ObjInt(1, true)
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
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 lynonType(): LynonType = LynonType.List
|
||||||
|
|
||||||
|
override suspend fun toJson(scope: Scope): JsonElement {
|
||||||
|
return JsonArray(list.map { it.toJson(scope) })
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = object : ObjClass("List", ObjArray) {
|
val type = object : ObjClass("List", ObjArray) {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
@ -141,6 +143,12 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
encoder.encodeAnyList(scope, values, fixedSize = true)
|
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 {
|
companion object {
|
||||||
|
|
||||||
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.statement
|
import net.sergeych.lyng.statement
|
||||||
@ -100,6 +102,10 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
encoder.encodeReal(value)
|
encoder.encodeReal(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toJson(scope: Scope): JsonElement {
|
||||||
|
return JsonPrimitive(value)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type: ObjClass = object : ObjClass("Real") {
|
val type: ObjClass = object : ObjClass("Real") {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
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.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
@ -116,6 +118,10 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
encoder.encodeBinaryData(value.encodeToByteArray())
|
encoder.encodeBinaryData(value.encodeToByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toJson(scope: Scope): JsonElement {
|
||||||
|
return JsonPrimitive(value)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = object : ObjClass("String") {
|
val type = object : ObjClass("String") {
|
||||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
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.test.runTest
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
|
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 {
|
fun testRegex() = runBlocking {
|
||||||
runDocTests("../docs/Regex.md")
|
runDocTests("../docs/Regex.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJson() = runBlocking {
|
||||||
|
runDocTests("../docs/json_and_kotlin_serialization.md")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user