From 4b8dd130241eab64234d9661239b2ed84634a746 Mon Sep 17 00:00:00 2001 From: David Denton Date: Fri, 5 Jan 2024 11:51:57 +0000 Subject: [PATCH] adding Jackson implementation and write (#55) --- CHANGELOG.md | 3 + data4k/README.md | 11 +- .../dev/forkhandles/data/DataContainer.kt | 96 +++++-- .../dev/forkhandles/data/DataProperty.kt | 10 +- .../forkhandles/data/JacksonDataContainer.kt | 43 ++- .../dev/forkhandles/data/MapDataContainer.kt | 9 +- .../forkhandles/lens/DataContainerContract.kt | 248 ++++++++++++------ .../lens/JacksonDataContainerTest.kt | 51 ++-- .../forkhandles/lens/MapDataContainerTest.kt | 63 +++-- 9 files changed, 377 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d336d11..f87bf8da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ This list is not intended to be all-encompassing - it will document major and breaking API changes with their rationale when appropriate: +### v2.12.0.0 +- **data4k** : [Breaking] Add support for writing to data structure, and rename methods to determine between required and optional fields + ### v2.11.1.0 - **data4k** : Add support for Value classes and removed inner class structures to tidy up. diff --git a/data4k/README.md b/data4k/README.md index 91645a63..e56cc59c 100644 --- a/data4k/README.md +++ b/data4k/README.md @@ -25,7 +25,7 @@ The library defines a `DataContainer` class and implementations for: Support for extracting: - primitive values -- subobjects +- sub-objects - lists - values4k value types @@ -33,13 +33,15 @@ To extract data from the underlying data, define wrappers which provides access ```kotlin class MapBacked(propertySet: Map) : MapDataContainer(propertySet) { - val stringField by field() + val stringField by required() + val optionalStringField by optional() val listSubClassField by list(::SubMap) val objectField by obj(::SubMap) } class SubMap(propertySet: Map) : MapDataContainer(propertySet) { - val stringField by field() + val stringField by required() + var optionalStringField by optional() } val input = MapBacked( @@ -58,4 +60,7 @@ val input = MapBacked( // then just get the values from the underlying data using the type system. Errors will be thrown for missing/invalid properties val data: String = input.objectField.stringField + +// to write into the structure, just assign to a var +input.objectField.optionalStringField = "hello" ``` diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt index 8ec17fe6..6c76c9ea 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt @@ -9,30 +9,92 @@ import dev.forkhandles.values.ValueFactory */ @Suppress("UNCHECKED_CAST") abstract class DataContainer( - protected val data: CONTENT, - existsFn: (CONTENT, String) -> Boolean, - getFn: (CONTENT, String) -> Any? + val data: CONTENT, + private val existsFn: (CONTENT, String) -> Boolean, + private val getFn: (CONTENT, String) -> Any?, + private val setFn: (CONTENT, String, Any?) -> Unit ) { - private val exists: DataContainer.(String) -> Boolean = { existsFn(data, it) } - private val get: DataContainer.(String) -> Any? = { getFn(data, it) } + /** Required **/ - fun field() = DataProperty, OUT>(exists, get) + fun required(mapInFn: (OUT) -> NEXT, mapOutFn: (NEXT) -> OUT?) = + property(mapInFn, mapOutFn) - fun field(mapFn: (OUT) -> NEXT) = DataProperty, NEXT>(exists) { - (get(it) as OUT)?.let(mapFn) - } + fun required() = required({ it }, { it }) + + fun required(mapInFn: (OUT) -> NEXT) = required(mapInFn) { error("no outbound mapping defined") } + + fun > required(factory: ValueFactory) = + required(factory::of) { it.value } + + /** Optional **/ + + fun optional(mapInFn: (OUT) -> NEXT) = + required(mapInFn) { error("no outbound mapping defined") } + + fun optional(mapInFn: (OUT) -> NEXT, mapOutFn: (NEXT) -> OUT?) = + required(mapInFn) { it?.let(mapOutFn) } + + fun optional() = required({ it }, { it }) + + fun > optional(factory: ValueFactory): DataProperty, OUT?> = + required(factory::of) { it?.value } + + /** Object **/ + + fun > obj(mapInFn: (CONTENT) -> OUT, mapOutFn: (OUT) -> CONTENT?) = + property(mapInFn, mapOutFn) + + fun > obj(mapInFn: (CONTENT) -> OUT) = + obj(mapInFn) { it.data } + + fun > optionalObj(mapInFn: (CONTENT) -> OUT): DataProperty, OUT?> = + property(mapInFn) { it?.data } + + /** List **/ - fun , OUT2 : OUT?> field(factory: ValueFactory) = field(factory::of) + fun list(mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?) = + property, List, List>({ it.map(mapInFn) }, { it.mapNotNull(mapOutFn) }) - fun list(mapFn: (IN) -> OUT) = - DataProperty, List>(exists) { get(it)?.let { (it as List).map(mapFn) } } + fun list(mapInFn: (IN) -> OUT) = list(mapInFn) { error("no outbound mapping defined") } - fun > list(factory: ValueFactory) = list(factory::of) + fun list() = list({ it }, { it }) + + fun > list(factory: ValueFactory) = list(factory::of) { it.value } + + @JvmName("listDataContainer") + fun ?> list(mapInFn: (CONTENT) -> OUT) = list(mapInFn) { it?.data } + + fun optionalList(mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?) = + property?, List, List>({ it.map(mapInFn) }, { it?.mapNotNull(mapOutFn) }) + + fun optionalList(mapInFn: (IN) -> OUT) = + optionalList(mapInFn) { error("no outbound mapping defined") } + + fun optionalList() = optionalList({ it }, { it }) + + fun > optionalList(factory: ValueFactory) = optionalList(factory::of) { it.value } + + @JvmName("optionalListDataContainer") + fun ?> optionalList(mapInFn: (CONTENT) -> OUT) = optionalList(mapInFn) { it?.data } + + /** Utility **/ + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DataContainer<*> + + return data == other.data + } - fun list() = list { it } + override fun hashCode() = data?.hashCode() ?: 0 - fun ?> obj(mapFn: (CONTENT) -> OUT) = - DataProperty, OUT>(exists) { (get(it) as CONTENT)?.let(mapFn) } + override fun toString() = data.toString() - fun obj() = DataProperty, CONTENT>(exists) { get(it) as CONTENT? } + private fun property(mapInFn: (OUT) -> IN, mapOutFn: (IN) -> OUT2?) = + DataProperty, IN>( + { existsFn(data, it) }, + { getFn(data, it)?.let { value -> value as OUT }?.let(mapInFn) }, + { name, value -> setFn(data, name, (value as IN?)?.let(mapOutFn)) }) } diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt index d1775a7f..959e9e16 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt @@ -1,13 +1,14 @@ package dev.forkhandles.data -import kotlin.properties.ReadOnlyProperty +import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty import kotlin.reflect.jvm.jvmErasure open class DataProperty( private val existsFn: IN.(String) -> Boolean, - private val getFn: IN.(String) -> Any? -) : ReadOnlyProperty { + private val getFn: IN.(String) -> Any?, + private val setFn: IN.(String, Any?) -> Unit +) : ReadWriteProperty { @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: IN, property: KProperty<*>): OUT { val result = thisRef.getFn(property.name) @@ -18,10 +19,13 @@ open class DataProperty( thisRef.existsFn(property.name) -> throw NoSuchElementException("Value for field <${property.name}> is null") else -> throw NoSuchElementException("Field <${property.name}> is missing") } + property.returnType.jvmErasure.isInstance(result) -> result as OUT else -> throw NoSuchElementException("Value for field <${property.name}> is not a ${property.returnType.jvmErasure} but ${result.javaClass.kotlin}") } } + + override fun setValue(thisRef: IN, property: KProperty<*>, value: OUT) = thisRef.setFn(property.name, value) } diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/JacksonDataContainer.kt b/data4k/src/main/kotlin/dev/forkhandles/data/JacksonDataContainer.kt index 8de3e22d..cf5c12a4 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/JacksonDataContainer.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/JacksonDataContainer.kt @@ -1,7 +1,20 @@ package dev.forkhandles.data import com.fasterxml.jackson.databind.JsonNode -import com.fasterxml.jackson.databind.node.* +import com.fasterxml.jackson.databind.node.ArrayNode +import com.fasterxml.jackson.databind.node.BooleanNode +import com.fasterxml.jackson.databind.node.BooleanNode.FALSE +import com.fasterxml.jackson.databind.node.BooleanNode.TRUE +import com.fasterxml.jackson.databind.node.DecimalNode +import com.fasterxml.jackson.databind.node.DoubleNode +import com.fasterxml.jackson.databind.node.FloatNode +import com.fasterxml.jackson.databind.node.IntNode +import com.fasterxml.jackson.databind.node.JsonNodeFactory.instance +import com.fasterxml.jackson.databind.node.LongNode +import com.fasterxml.jackson.databind.node.NullNode +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.node.TextNode +import java.math.BigDecimal /** * Jackson JsonNode-based implementation of the DataContainer @@ -10,7 +23,12 @@ abstract class JacksonDataContainer(input: JsonNode) : DataContainer( input, { content, it -> content.has(it) }, - { content, it -> content[it]?.let(Companion::nodeToValue) } + { content, it -> content[it]?.let(Companion::nodeToValue) }, + { node: JsonNode, name, value -> + (node as? ObjectNode)?.also { + node.set(name, value.toNode()) + } ?: error("Invalid node type ${input::class.java}") + } ) { companion object { @@ -18,13 +36,32 @@ abstract class JacksonDataContainer(input: JsonNode) : is BooleanNode -> input.booleanValue() is IntNode -> input.intValue() is LongNode -> input.longValue() + is FloatNode -> input.floatValue() is DecimalNode -> input.decimalValue() is DoubleNode -> input.doubleValue() is TextNode -> input.textValue() is ArrayNode -> input.map(::nodeToValue) is ObjectNode -> input is NullNode -> null - else -> error("Invalid node type $input") + else -> error("Invalid node type ${input::class.java}") } + + private fun Any?.toNode(): JsonNode? = + when (this) { + null -> NullNode.instance + is JsonNode -> this + is DataContainer<*> -> data.toNode() + is Boolean -> if (this) TRUE else FALSE + is Int -> IntNode(this) + is Long -> LongNode(this) + is Float -> FloatNode(this) + is BigDecimal -> DecimalNode(this) + is Double -> DoubleNode(this) + is String -> TextNode(this) + is Iterable<*> -> ArrayNode(instance).also { + map { if (it is JsonNode) it else it.toNode() }.forEach(it::add) + } + else -> error("Cannot set value of type ${this::class.java}") + } } } diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/MapDataContainer.kt b/data4k/src/main/kotlin/dev/forkhandles/data/MapDataContainer.kt index 6b1759f2..cc10e152 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/MapDataContainer.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/MapDataContainer.kt @@ -1,11 +1,10 @@ package dev.forkhandles.data -import kotlin.collections.Map - /** * Map-based implementation of the DataContainer */ abstract class MapDataContainer(input: Map) : - DataContainer>(input, { content, it -> content.containsKey(it) }, { content, it -> content[it] }) - - + DataContainer>(input.toMutableMap(), { content, it -> content.containsKey(it) }, + { content, it -> content[it] }, + { map, name, value -> map[name] = value } + ) diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt index c62fa528..7ef83fa5 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt @@ -8,123 +8,215 @@ import strikt.api.expectThrows import strikt.assertions.isEqualTo import strikt.assertions.isNull import strikt.assertions.message - -interface MainClassFields { - val stringField: String - val booleanField: Boolean - val intField: Int - val longField: Long - val decimalField: Double - val notAStringField: String - - val mappedField: Int - - val listField: List - val listSubClassField: List - val listIntsField: List - val listValueField: List - - val objectField: SubClassFields - - val valueField: MyType - - val optionalField: String? - val optionalValueField: MyType? - val optionalObjectField: SubClassFields? - val optionalListField: List? +import java.math.BigDecimal +import kotlin.reflect.KMutableProperty0 + +interface MainClassFields { + var string: String + var boolean: Boolean + var int: Int + var long: Long + var double: Double + var decimal: BigDecimal + var notAString: String + + var mapped: Int + + var list: List + var listSubClass: List + var listInts: List + var listValue: List + val listMapped: List + + var subClass: T + + var value: MyType + + var optional: String? + var optionalMapped: Int? + val optionalValue: MyType? + var optionalSubClass: T? + var optionalSubClassList: List? + var optionalList: List? + var optionalValueList: List? + var optionalMappedList: List? } interface SubClassFields { - val stringField: String - val noSuchField: String + var string: String + var noSuch: String } class MyType private constructor(value: Int) : IntValue(value) { companion object : IntValueFactory(::MyType) } -abstract class DataContainerContract { +abstract class DataContainerContract { - abstract fun container(input: Map): MainClassFields + abstract fun container(input: Map): MainClassFields + abstract fun subContainer(input: Map): T @Test - fun `can get primitive values from properties`() { + fun `can read primitives values`() { val input = container( mapOf( - "stringField" to "string", - "booleanField" to true, - "intField" to 123, - "longField" to Long.MAX_VALUE, - "decimalField" to 1.1234, - "notAStringField" to 123, - "valueField" to 123, - "mappedField" to "123", - - "optionalValueField" to 123, - "optionalField" to "optional", + "string" to "string", + "boolean" to true, + "int" to 123, + "long" to Long.MAX_VALUE, + "double" to 1.1234, + "decimal" to "1.1234", + "notAString" to 123, + "value" to 123, + "mapped" to "123", + + "optionalValue" to 123, + "optional" to "optional", ) ) - expectThat(input.stringField).isEqualTo("string") - expectThrows { container(mapOf()).stringField }.message.isEqualTo("Field is missing") - expectThrows { input.notAStringField }.message.isEqualTo("Value for field is not a class kotlin.String but class kotlin.Int") + expectThat(input.string).isEqualTo("string") + expectThrows { container(mapOf()).string }.message.isEqualTo("Field is missing") + expectThrows { input.notAString }.message.isEqualTo("Value for field is not a class kotlin.String but class kotlin.Int") - expectThat(input.booleanField).isEqualTo(true) - expectThat(input.intField).isEqualTo(123) - expectThat(input.longField).isEqualTo(Long.MAX_VALUE) - expectThat(input.decimalField).isEqualTo(1.1234) + expectThat(input.boolean).isEqualTo(true) + expectThat(input.int).isEqualTo(123) + expectThat(input.long).isEqualTo(Long.MAX_VALUE) + expectThat(input.double).isEqualTo(1.1234) - expectThat(input.mappedField).isEqualTo(123) - expectThrows { container(mapOf("mappedField" to 123)).mappedField } - expectThat(input.valueField).isEqualTo(MyType.of(123)) + expectThat(input.mapped).isEqualTo(123) + expectThrows { container(mapOf("mapped" to 123)).mapped } + expectThat(input.value).isEqualTo(MyType.of(123)) - expectThat(input.optionalField).isEqualTo("optional") - expectThat(container(mapOf()).optionalField).isNull() + expectThat(input.optional).isEqualTo("optional") + expectThat(container(mapOf()).optional).isNull() - expectThat(input.optionalValueField).isEqualTo(MyType.of(123)) - expectThat(container(mapOf()).optionalValueField).isNull() + expectThat(input.optionalValue).isEqualTo(MyType.of(123)) + expectThat(container(mapOf()).optionalValue).isNull() } @Test - fun `object inputs`() { + fun `can write primitives values`() { val input = container( mapOf( - "objectField" to mapOf( - "stringField" to "string" + "string" to "string", + "boolean" to true, + "int" to 123, + "long" to Long.MAX_VALUE, + "double" to 1.1234, + "value" to 123, + "mapped" to "123", + + "optionalValue" to 123, + "optional" to "optional", + ) + ) + + expectSetWorks(input::string, "123") + expectSetWorks(input::boolean, false) + expectSetWorks(input::int, 999) + expectSetWorks(input::long, 0) + expectSetWorks(input::double, 5.4536) + + expectSetWorks(input::optional, "123123") + expectSetWorks(input::optional, null) + expectSetWorks(input::mapped, 123) + } + + @Test + fun `read object values`() { + val input = container( + mapOf( + "subClass" to mapOf( + "string" to "string" ), - "optionalObjectField" to mapOf( - "stringField" to "string" + "optionalSubClass" to mapOf( + "string" to "string" ) ) ) - expectThat(input.objectField.stringField).isEqualTo("string") - expectThrows { input.objectField.noSuchField }.message.isEqualTo("Field is missing") + expectThat(input.subClass.string).isEqualTo("string") + expectThrows { input.subClass.noSuch }.message.isEqualTo("Field is missing") - expectThat(input.optionalObjectField?.stringField).isEqualTo("string") - expectThat(container(mapOf()).optionalObjectField).isNull() + expectThat(input.optionalSubClass?.string).isEqualTo("string") + expectThat(container(mapOf()).optionalSubClass).isNull() + expectThat(input.optionalSubClass?.string).isEqualTo("string") } @Test - fun `list inputs`() { - val listInput = container( + fun `write object values`() { + val objFieldNext = mapOf( + "string" to "string2" + ) + val input = container( mapOf( - "listField" to listOf("string1", "string2"), - "listIntsField" to listOf(1, 2, 3), - "listValueField" to listOf(1, 2, 3), - "listSubClassField" to listOf( - mapOf("stringField" to "string1"), - mapOf("stringField" to "string2"), + "object" to objFieldNext, + "optionalObject" to mapOf( + "string" to "string" + ) + ) + ) + + val nextObj = subContainer(objFieldNext) + expectSetWorks(input::subClass, nextObj) + expectThat(input.subClass).isEqualTo(subContainer(objFieldNext)) + + expectSetWorks(input::optionalSubClass, nextObj) + expectThat(input.optionalSubClass).isEqualTo(nextObj) + expectSetWorks(input::optionalSubClass, null) + expectThat(input.optionalSubClass).isEqualTo(null) + } + + @Test + fun `read list values`() { + val input = container( + mapOf( + "list" to listOf("string1", "string2"), + "listInts" to listOf(1, 2, 3), + "listMapped" to listOf(123, 456), + "listValue" to listOf(1, 2, 3), + "listSubClass" to listOf( + mapOf("string" to "string1"), + mapOf("string" to "string2"), + ), + "optionalList" to listOf("hello") + ) + ) + expectThat(input.list).isEqualTo(listOf("string1", "string2")) + expectThat(input.listMapped).isEqualTo(listOf("123", "456")) + expectThat(input.listInts).isEqualTo(listOf(1, 2, 3)) + expectThat(input.listValue).isEqualTo(listOf(1, 2, 3).map(MyType::of)) + expectThat(input.listSubClass.map { it.string }).isEqualTo(listOf("string1", "string2")) + + expectThat(input.optionalList).isEqualTo(listOf("hello")) + expectThat(container(mapOf()).optionalList).isNull() + } + + @Test + fun `write list values`() { + val input = container( + mapOf( + "list" to listOf("string1", "string2"), + "listSubClass" to listOf( + mapOf("string" to "string1"), + mapOf("string" to "string2"), ), - "optionalListField" to listOf("hello") + "listValue" to listOf(1, 2, 3), + "optionalList" to listOf("hello") ) ) - expectThat(listInput.listField).isEqualTo(listOf("string1", "string2")) - expectThat(listInput.listIntsField).isEqualTo(listOf(1, 2, 3)) - expectThat(listInput.listValueField).isEqualTo(listOf(1, 2, 3).map(MyType::of)) - expectThat(listInput.listSubClassField.map { it.stringField }).isEqualTo(listOf("string1", "string2")) - expectThat(listInput.optionalListField).isEqualTo(listOf("hello")) - expectThat(container(mapOf()).optionalListField).isNull() + expectSetWorks(input::list, listOf("123")) + expectSetWorks(input::listSubClass, listOf(subContainer(mapOf("123" to "123")))) + expectSetWorks(input::listValue, listOf(MyType.of(123), MyType.of(456))) + expectSetWorks(input::optionalSubClassList, listOf(subContainer(mapOf("123" to "123")))) + expectSetWorks(input::optionalValueList, listOf(MyType.of(123), MyType.of(456))) + expectSetWorks(input::optionalList, listOf("hello")) + } + + private fun expectSetWorks(prop: KMutableProperty0, value: T) { + prop.set(value) + expectThat(prop.get()).isEqualTo(value) } } diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt index 9ab5b06d..bd8c81d7 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt @@ -3,34 +3,43 @@ package dev.forkhandles.lens import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import dev.forkhandles.data.JacksonDataContainer +import java.math.BigDecimal -class JacksonDataContainerTest : DataContainerContract() { +class JacksonDataContainerTest : DataContainerContract() { class SubNodeBacked(node: JsonNode) : JacksonDataContainer(node), SubClassFields { - override val stringField by field() - override val noSuchField by field() + override var string by required() + override var noSuch by required() } - class NodeBacked(node: JsonNode) : JacksonDataContainer(node), MainClassFields { - override val stringField by field() - override val optionalField by field() - override val booleanField by field() - override val intField by field() - override val longField by field() - override val decimalField by field() - override val notAStringField by field() - override val listSubClassField by list(::SubNodeBacked) - override val listField by list() - override val listIntsField by list() - override val listValueField by list(MyType) - override val objectField by obj(::SubNodeBacked) - override val valueField by field(MyType) - override val mappedField by field(String::toInt) + class NodeBacked(node: JsonNode) : JacksonDataContainer(node), MainClassFields { + override var string by required() + override var boolean by required() + override var int by required() + override var long by required() + override var double by required() + override var decimal by required() + override var notAString by required() + override var listSubClass by list(::SubNodeBacked) + override var list by list() + override var listInts by list() + override var listValue by list(MyType) + override val listMapped by list(Int::toString) + override var subClass by obj(::SubNodeBacked) + override var value by required(MyType) + override var mapped by required(String::toInt, Int::toString) - override val optionalListField: List? by list() - override val optionalObjectField: SubClassFields? by obj(::SubNodeBacked) - override val optionalValueField: MyType? by field(MyType) + override var optional by optional() + override var optionalMapped by optional(String::toInt, Int::toString) + override var optionalList by optionalList() + override var optionalValueList by optionalList(MyType) + override var optionalMappedList by optionalList(String::toInt, Int::toString) + override var optionalSubClass by optionalObj(::SubNodeBacked) + override var optionalSubClassList by optionalList(::SubNodeBacked) + override var optionalValue by optional(MyType) } override fun container(input: Map) = NodeBacked(ObjectMapper().valueToTree(input)) + override fun subContainer(input: Map) = + SubNodeBacked(ObjectMapper().valueToTree(input)) } diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt index 6548404b..59495280 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt @@ -1,38 +1,47 @@ package dev.forkhandles.lens import dev.forkhandles.data.MapDataContainer +import java.math.BigDecimal -class MapDataContainerTest : DataContainerContract() { +class MapDataContainerTest : DataContainerContract() { class SubMap(propertySet: Map) : MapDataContainer(propertySet), SubClassFields { - override val stringField by field() - override val noSuchField by field() + override var string by required() + override var noSuch by required() } - class MapBacked(propertySet: Map) : MapDataContainer(propertySet), MainClassFields { - override val stringField by field() - override val booleanField by field() - override val intField by field() - override val longField by field() - override val decimalField by field() - override val notAStringField by field() - - override val mappedField by field(String::toInt) - - override val listField by list() - override val listValueField by list(MyType) - override val listSubClassField by list(::SubMap) - override val listIntsField by list() - - override val objectField by obj(::SubMap) - - override val valueField by field(MyType) - - override val optionalField by field() - override val optionalListField: List? by list() - override val optionalObjectField: SubMap? by obj(::SubMap) - override val optionalValueField: MyType? by field(MyType) + class MapBacked(map: Map) : MapDataContainer(map), MainClassFields { + override var string by required() + override var boolean by required() + override var int by required() + override var long by required() + override var double by required() + override var decimal by required() + override var notAString by required() + + override var mapped by required(String::toInt, Int::toString) + + override var list by list() + override var listValue by list(MyType) + override var listSubClass by list(::SubMap) + override var listInts by list() + override val listMapped by list(Int::toString) + + override var subClass by obj(::SubMap) + + override var value by required(MyType) + + override var optional by optional() + override var optionalList by optionalList() + override var optionalValueList by optionalList(MyType) + override var optionalSubClassList by optionalList(::SubMap) + override var optionalSubClass by optionalObj(::SubMap) + override var optionalValue by optional(MyType) + override var optionalMapped by optional(String::toInt, Int::toString) + override var optionalMappedList by optionalList(String::toInt, Int::toString) } - override fun container(input: Map): MainClassFields = MapBacked(input) + override fun container(input: Map) = MapBacked(input) + + override fun subContainer(input: Map) = SubMap(input) }