diff --git a/CHANGELOG.md b/CHANGELOG.md
index f75a0d56..3d336d11 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.11.1.0
+- **data4k** : Add support for Value classes and removed inner class structures to tidy up.
+
### v2.11.0.0
- **data4k** : [New module] [data4k](https://github.com/fork-handles/forkhandles/blob/trunk/data4k/README.md): Typesafe data-oriented programming.
diff --git a/data4k/README.md b/data4k/README.md
index 619b6665..14db0383 100644
--- a/data4k/README.md
+++ b/data4k/README.md
@@ -6,7 +6,7 @@
-Library to make working with Data-Oriented programming in Kotlin easier, to extract values from dynamic data structures such as Maps.
+Library to make working with Data-Oriented programming in Kotlin easier, to extract typed values from dynamic data structures such as Maps.
## Installation
@@ -19,7 +19,7 @@ implementation("dev.forkhandles:data4k")
## Usage
-The library defines a DataContainer class and implementations for:
+The library defines a `DataContainer` class and implementations for:
- Map
- Jackson JSON Node
diff --git a/data4k/build.gradle b/data4k/build.gradle
index 6655030d..5d0b81e6 100644
--- a/data4k/build.gradle
+++ b/data4k/build.gradle
@@ -2,6 +2,7 @@ description = 'ForkHandles data-oriented programming library'
dependencies {
api("org.jetbrains.kotlin:kotlin-reflect:_")
+ api(project(":values4k"))
compileOnly("com.fasterxml.jackson.core:jackson-databind:_")
testImplementation("com.fasterxml.jackson.core:jackson-databind:_")
testImplementation("io.strikt:strikt-jvm:_")
diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt
index b8dbe6ac..ade43027 100644
--- a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt
+++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt
@@ -1,10 +1,13 @@
package dev.forkhandles.data
+import dev.forkhandles.values.Value
+import dev.forkhandles.values.ValueFactory
+
/**
* Superclass for all container implementations. Defines the delegate property classes to extract data from the
* underlying data structure.
*/
-@Suppress("UNCHECKED_CAST", "ClassName")
+@Suppress("UNCHECKED_CAST")
abstract class DataContainer(
protected val data: CONTENT,
existsFn: (CONTENT, String) -> Boolean,
@@ -13,11 +16,25 @@ abstract class DataContainer(
private val exists: DataContainer.(String) -> Boolean = { existsFn(data, it) }
private val get: DataContainer.(String) -> Any? = { getFn(data, it) }
- inner class field : DataProperty, OUT>(exists, get)
+ fun field() = DataProperty, OUT>(exists, get)
+
+ fun field(mapFn: (OUT) -> NEXT) = DataProperty, NEXT>(exists) {
+ (get(it) as OUT).let(mapFn)
+ }
+
+ fun > field(factory: ValueFactory) =
+ DataProperty, OUT>(exists) { (get(it) as IN).let(factory::of) }
+
+ fun list(mapFn: (IN) -> OUT) =
+ DataProperty, List>(exists) { (get(it) as List).map(mapFn) }
+
+ fun > list(factory: ValueFactory) =
+ DataProperty, List>(exists) { (get(it) as List).map(factory::of) }
+
+ fun list() = list { it }
- inner class list(mapFn: (IN) -> OUT) :
- DataProperty, List>(exists, { (get(it) as List).map(mapFn) })
+ fun > obj(mapFn: (CONTENT) -> OUT) =
+ DataProperty, OUT>(exists) { mapFn(get(it) as CONTENT) }
- inner class obj>(mapFn: (CONTENT) -> OUT) :
- DataProperty, OUT>(exists, { mapFn(get(it) as CONTENT) })
+ fun obj() = DataProperty, CONTENT>(exists) { get(it) as CONTENT }
}
diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt
index 7a98ba00..f1f13b60 100644
--- a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt
+++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt
@@ -4,7 +4,7 @@ import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.jvmErasure
-abstract class DataProperty(
+open class DataProperty(
private val existsFn: IN.(String) -> Boolean,
private val getFn: IN.(String) -> Any?
) : ReadOnlyProperty {
diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt
index b2d503e5..66763bee 100644
--- a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt
+++ b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt
@@ -1,5 +1,7 @@
package dev.forkhandles.lens
+import dev.forkhandles.values.IntValue
+import dev.forkhandles.values.IntValueFactory
import org.junit.jupiter.api.Test
import strikt.api.expectThat
import strikt.api.expectThrows
@@ -15,10 +17,14 @@ interface MainClassFields {
val notAStringField: String
val optionalField: String?
val noSuchField: String
+ val listField: List
val listSubClassField: List
val listIntsField: List
+ val listValueField: List
val listStringsField: List
val objectField: SubClassFields
+ val valueField: MyType
+ val mappedField: Int
}
interface SubClassFields {
@@ -26,6 +32,10 @@ interface SubClassFields {
val noSuchField: String
}
+class MyType private constructor(value: Int) : IntValue(value) {
+ companion object : IntValueFactory(::MyType)
+}
+
abstract class DataContainerContract {
abstract fun container(input: Map): MainClassFields
@@ -41,6 +51,11 @@ abstract class DataContainerContract {
"longField" to Long.MAX_VALUE,
"decimalField" to 1.1234,
"notAStringField" to 123,
+ "valueField" to 123,
+ "mappedField" to "123",
+ "listField" to listOf("hello"),
+ "listField" to listOf("hello"),
+ "listValueField" to listOf(1, 2, 3),
"listIntsField" to listOf(1, 2, 3),
"listSubClassField" to listOf(
mapOf("stringField" to "string1"),
@@ -59,7 +74,11 @@ abstract class DataContainerContract {
expectThat(input.intField).isEqualTo(123)
expectThat(input.longField).isEqualTo(Long.MAX_VALUE)
expectThat(input.decimalField).isEqualTo(1.1234)
+ expectThat(input.valueField).isEqualTo(MyType.of(123))
+ expectThat(input.mappedField).isEqualTo(123)
+ expectThat(input.listField).isEqualTo(listOf("hello"))
expectThat(input.listIntsField).isEqualTo(listOf(1, 2, 3))
+ expectThat(input.listValueField).isEqualTo(listOf(1, 2, 3).map(MyType::of))
expectThat(input.listSubClassField.map { it.stringField }).isEqualTo(listOf("string1", "string2"))
expectThat(input.listStringsField).isEqualTo(listOf("string1", "string2"))
expectThat(input.objectField.stringField).isEqualTo("string")
diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt
index b5cd6dd9..1aad5ac2 100644
--- a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt
+++ b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt
@@ -22,8 +22,12 @@ class JacksonDataContainerTest : DataContainerContract() {
override val noSuchField by field()
override val listSubClassField by list(::SubNodeBacked)
override val listStringsField by list(Any::toString)
- override val listIntsField by list { it as Int }
+ 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)
}
override fun container(input: Map) = NodeBacked(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 11f14c96..38d63720 100644
--- a/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt
+++ b/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt
@@ -18,10 +18,14 @@ class MapDataContainerTest : DataContainerContract() {
override val decimalField by field()
override val notAStringField by field()
override val noSuchField by field()
+ override val listField by list()
+ override val listValueField by list(MyType)
override val listSubClassField by list(::SubMap)
override val listStringsField by list(Any::toString)
- override val listIntsField by list { it as Int }
+ override val listIntsField by list()
override val objectField by obj(::SubMap)
+ override val valueField by field(MyType)
+ override val mappedField by field(String::toInt)
}
override fun container(input: Map): MainClassFields = MapBacked(input)