commonMain.maryk.core.models.ValueDataModel.kt Maven / Gradle / Ivy
package maryk.core.models
import maryk.core.definitions.MarykPrimitive
import maryk.core.exceptions.ContextNotFoundException
import maryk.core.exceptions.DefNotFoundException
import maryk.core.exceptions.RequestException
import maryk.core.models.definitions.ValueDataModelDefinition
import maryk.core.models.serializers.ObjectDataModelSerializer
import maryk.core.models.serializers.ValueDataModelSerializer
import maryk.core.properties.IsPropertyContext
import maryk.core.properties.PropertiesCollectionDefinition
import maryk.core.properties.PropertiesCollectionDefinitionWrapper
import maryk.core.properties.definitions.EmbeddedObjectDefinition
import maryk.core.properties.definitions.wrapper.AnyDefinitionWrapper
import maryk.core.properties.definitions.wrapper.AnyTypedDefinitionWrapper
import maryk.core.properties.definitions.wrapper.EmbeddedObjectDefinitionWrapper
import maryk.core.properties.definitions.wrapper.IsDefinitionWrapper
import maryk.core.properties.types.ValueDataObject
import maryk.core.properties.types.ValueDataObjectWithValues
import maryk.core.query.ContainsDefinitionsContext
import maryk.core.query.DefinitionsConversionContext
import maryk.core.values.MutableValueItems
import maryk.core.values.ObjectValues
import maryk.core.values.ValueItem
import maryk.core.values.ValueItems
import maryk.json.IsJsonLikeReader
import maryk.json.IsJsonLikeWriter
import maryk.json.JsonToken
import maryk.lib.exceptions.ParseException
import maryk.yaml.YamlWriter
import kotlin.reflect.KClass
/**
* DataModel for objects which should be treated as a Value in total.
* They will all together be serialized as a single byte value or string and can
* be used as the key for a map.
*/
abstract class ValueDataModel> internal constructor(
name: String,
): TypedObjectDataModel(), IsValueDataModel, MarykPrimitive {
constructor(
objClass: KClass,
): this(
objClass.simpleName ?: throw DefNotFoundException("RootDataModel should have a name. Please define in a class instead of anonymous object or pass name as property."),
)
@Suppress("UNCHECKED_CAST", "LeakingThis")
private val typedThis: DM = this as DM
override val Serializer = object: ValueDataModelSerializer(typedThis) {}
override val Meta = ValueDataModelDefinition(name = name)
abstract override fun invoke(values: ObjectValues): DO
override fun equals(other: Any?) =
super.equals(other) && other is ValueDataModel<*, *> && this.Meta == other.Meta
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + Meta.hashCode()
return result
}
fun toBytes(vararg inputs: Any) =
Serializer.toBytes(*inputs)
internal object Model: DefinitionModel>() {
val properties = PropertiesCollectionDefinitionWrapper>(
1u,
"properties",
PropertiesCollectionDefinition(
capturer = { context, propDefs ->
context?.apply {
this.propertyDefinitions = propDefs
} ?: throw ContextNotFoundException()
}
),
getter = {
@Suppress("UNCHECKED_CAST")
it as IsTypedDataModel>
}
).also(this::addSingle)
val meta = EmbeddedObjectDefinitionWrapper(
2u,
"meta",
EmbeddedObjectDefinition(required = true, final = true, dataModel = { ValueDataModelDefinition.Model }),
getter = ValueDataModel<*, *>::Meta,
).also(this::addSingle)
override fun invoke(values: ObjectValues, IsObjectDataModel>>): ValueDataModel<*, *> {
val meta: ValueDataModelDefinition = values(meta.index)
return object : ValueDataModel>(
meta.name
) {
override val Meta: ValueDataModelDefinition = meta
override fun invoke(values: ObjectValues>) =
ValueDataObjectWithValues(this.toBytes(values), values)
}.also {
values>(properties.index).forEach(it::addSingle)
}
}
override val Serializer = object: ObjectDataModelSerializer, IsObjectDataModel>, ContainsDefinitionsContext, ContainsDefinitionsContext>(this) {
override fun writeObjectAsJson(
obj: ValueDataModel<*, *>,
writer: IsJsonLikeWriter,
context: ContainsDefinitionsContext?,
skip: List>>?
) {
writer.writeStartObject()
for (def in meta.dataModel) {
// Skip name if defined higher
if (def == meta.dataModel.name && context != null && context.currentDefinitionName == obj.Meta.name) {
context.currentDefinitionName = "" // Reset after use
continue
}
val value = def.getPropertyAndSerialize(obj.Meta, context) ?: continue
writer.writeFieldName(def.name)
def.writeJsonValue(value, writer, context)
}
if (writer is YamlWriter) {
// Write optimized format when writing yaml
@Suppress("UNCHECKED_CAST")
for (property in obj as Iterable) {
properties.valueDefinition.writeJsonValue(property, writer, context)
}
} else {
@Suppress("UNCHECKED_CAST")
this.writeJsonValue(
properties as IsDefinitionWrapper>,
writer,
obj,
context
)
}
writer.writeEndObject()
}
override fun walkJsonToRead(
reader: IsJsonLikeReader,
values: MutableValueItems,
context: ContainsDefinitionsContext?
) {
val deserializedProperties = mutableListOf>>()
val metaValues = mutableListOf()
// Inject name if it was defined as a map key in a higher level
context?.currentDefinitionName?.let { name ->
if (name.isNotBlank()) {
if (values.contains(ValueDataModelDefinition.Model.name.index)) {
throw RequestException("Name $name was already defined by map")
}
// Reset it so no deeper value can reuse it
context.currentDefinitionName = ""
metaValues += ValueItem(ValueDataModelDefinition.Model.name.index, name)
}
}
walker@ do {
val token = reader.currentToken
when (token) {
is JsonToken.StartComplexFieldName -> {
deserializedProperties += properties.valueDefinition.readJson(reader, context)
}
is JsonToken.FieldName -> {
val value = token.value ?: throw ParseException("Empty field name not allowed in JSON")
when (val definition = ValueDataModelDefinition.Model[value]) {
null -> {
if (value == properties.name) {
reader.nextToken() // continue for field name
deserializedProperties += properties.readJson(reader, context as DefinitionsConversionContext)
} else {
reader.skipUntilNextField()
continue@walker
}
}
else -> {
reader.nextToken()
metaValues += ValueItem(definition.index, definition.definition.readJson(reader, context))
}
}
}
else -> break@walker
}
reader.nextToken()
} while (token !is JsonToken.Stopped)
if (model.isNotEmpty()) {
values[properties.index] = deserializedProperties
}
if (metaValues.isNotEmpty()) {
values[meta.index] = ObjectValues(
ValueDataModelDefinition.Model,
ValueItems(*metaValues.toTypedArray()),
)
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy