Updated serialization utils

This commit is contained in:
Alexander Nozik 2019-11-03 20:39:08 +03:00
parent b72300bfaa
commit 58e396f50c
14 changed files with 252 additions and 27 deletions

View File

@ -0,0 +1,169 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta
import hep.dataforge.vis.spatial.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.modules.SerialModuleCollector
import kotlin.reflect.KClass
internal val SerialDescriptor.jsonType
get() = when (this.kind) {
StructureKind.LIST -> "array"
PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG,
PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number"
PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string"
PrimitiveKind.BOOLEAN -> "boolean"
else -> "object"
}
private fun SerialModule.enumerate(type: KClass<*>): Sequence<SerialDescriptor> {
val list = ArrayList<SerialDescriptor>()
fun send(descriptor: SerialDescriptor) = list.add(descriptor)
val enumerator = object : SerialModuleCollector {
override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
if (kClass == type) {
send(serializer.descriptor)
}
}
override fun <Base : Any, Sub : Base> polymorphic(
baseClass: KClass<Base>,
actualClass: KClass<Sub>,
actualSerializer: KSerializer<Sub>
) {
if (baseClass == type) {
send(actualSerializer.descriptor)
}
}
}
dumpTo(enumerator)
return list.asSequence()
}
/**
* Creates an [JsonObject] which contains Json Schema of given [descriptor].
*
* Schema can contain following fields:
* `description`, `type` for all descriptors;
* `properties` and `required` for objects;
* `enum` for enums;
* `items` for arrays.
*
* User can modify this schema to add additional validation keywords
* (as per [https://json-schema.org/latest/json-schema-validation.html])
* if they want.
*/
private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject {
if (descriptor.name in arrayOf(
"hep.dataforge.vis.spatial.Point3D",
"hep.dataforge.vis.spatial.Point2D",
Meta::class.qualifiedName
)
) return json {
"\$ref" to "#/definitions/${descriptor.name}"
}
val properties: MutableMap<String, JsonObject> = mutableMapOf()
val requiredProperties: MutableSet<String> = mutableSetOf()
val isEnum = descriptor.kind == UnionKind.ENUM_KIND
val isPolymorphic = descriptor.kind == UnionKind.POLYMORPHIC
if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child ->
val elementName = descriptor.getElementName(index)
properties[elementName] = when (elementName) {
"templates" -> json {
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
}
"properties" -> json {
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
}
"first", "second" -> json{
"\$ref" to "#/definitions/children"
}
"styleSheet" -> json {
"type" to "object"
"additionalProperties" to json {
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
}
}
in arrayOf("children") -> json {
"type" to "object"
"additionalProperties" to json {
"\$ref" to "#/definitions/children"
}
}
else -> jsonSchema(child, context)
}
if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName)
}
val jsonType = descriptor.jsonType
val objectData: MutableMap<String, JsonElement> = mutableMapOf(
"description" to JsonLiteral(descriptor.name),
"type" to JsonLiteral(jsonType)
)
if (isEnum) {
val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName)
objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral))
}
when (jsonType) {
"object" -> {
objectData["properties"] = JsonObject(properties)
val required = requiredProperties.map { JsonLiteral(it) }
if (required.isNotEmpty()) {
objectData["required"] = JsonArray(required)
}
}
"array" -> objectData["items"] = properties.values.let {
check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" }
it.first()
}
else -> { /* no-op */
}
}
return JsonObject(objectData)
}
fun main() {
val context = Visual3DPlugin.serialModule
val definitions = json {
"children" to json {
"anyOf" to jsonArray {
context.enumerate(VisualObject3D::class).forEach {
if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") {
+json {
"\$ref" to "#/definitions/${it.name}"
}
} else {
+jsonSchema(it, context)
}
}
}
}
"hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context)
"hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context)
"hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context)
}
println(
Json.indented.stringify(
JsonObjectSerializer,
json {
"definitions" to definitions
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
}
)
)
}

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.context.Context
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.*
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.VisualFactory

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.update

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.vis.common.AbstractVisualObject

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.number

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.*
@ -15,6 +15,7 @@ import hep.dataforge.vis.common.MutableVisualGroup
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
import kotlin.collections.component1
import kotlin.collections.component2
@ -72,6 +73,7 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
ProxyChild(it.key.asName())
} ?: emptyMap()
@Transient
private val propertyCache: HashMap<Name, Config> = HashMap()
fun childPropertyName(childName: Name, propertyName: Name): Name {

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable

View File

@ -4,8 +4,8 @@ import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.MetaSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.*
import hep.dataforge.names.Name

View File

@ -8,8 +8,8 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.MetaSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta

View File

@ -1,45 +1,97 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.descriptor
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
import kotlinx.serialization.*
import kotlinx.serialization.internal.DoubleSerializer
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.internal.nullable
@Serializable
private data class Point2DSerial(val x: Double, val y: Double)
inline fun <R> Decoder.decodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
crossinline block: CompositeDecoder.() -> R
): R {
val decoder = beginStructure(desc, *typeParams)
val res = decoder.block()
decoder.endStructure(desc)
return res
}
@Serializable
private data class Point3DSerial(val x: Double, val y: Double, val z: Double)
inline fun Encoder.encodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeEncoder.() -> Unit
) {
val encoder = beginStructure(desc, *typeParams)
encoder.block()
encoder.endStructure(desc)
}
@Serializer(Point3D::class)
object Point3DSerializer : KSerializer<Point3D> {
private val serializer = Point3DSerial.serializer()
override val descriptor: SerialDescriptor get() = serializer.descriptor
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") {
double("x", true)
double("y", true)
double("z", true)
}
override fun deserialize(decoder: Decoder): Point3D {
return serializer.deserialize(decoder).let {
Point3D(it.x, it.y, it.z)
var x: Double? = null
var y: Double? = null
var z: Double? = null
decoder.decodeStructure(descriptor) {
loop@ while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
else -> throw SerializationException("Unknown index $i")
}
}
}
return Point3D(x?:0.0, y?:0.0, z?:0.0)
}
override fun serialize(encoder: Encoder, obj: Point3D) {
serializer.serialize(encoder, Point3DSerial(obj.x, obj.y, obj.z))
encoder.encodeStructure(descriptor) {
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
if (obj.z != 0.0) encodeDoubleElement(descriptor, 2, obj.z)
}
}
}
@Serializer(Point2D::class)
object Point2DSerializer : KSerializer<Point2D> {
private val serializer = Point2DSerial.serializer()
override val descriptor: SerialDescriptor get() = serializer.descriptor
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") {
double("x", true)
double("y", true)
}
override fun deserialize(decoder: Decoder): Point2D {
return serializer.deserialize(decoder).let {
Point2D(it.x, it.y)
var x: Double? = null
var y: Double? = null
decoder.decodeStructure(descriptor) {
loop@ while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
else -> throw SerializationException("Unknown index $i")
}
}
}
return Point2D(x?:0.0, y?:0.0)
}
override fun serialize(encoder: Encoder, obj: Point2D) {
serializer.serialize(encoder, Point2DSerial(obj.x, obj.y))
encoder.encodeStructure(descriptor) {
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
}
}
}

View File

@ -11,6 +11,8 @@ class SerializationTest {
fun testCubeSerialization(){
val cube = Box(100f,100f,100f).apply{
color(222)
x = 100
z = -100
}
val string = json.stringify(Box.serializer(),cube)
println(string)