diff --git a/kondor-auto/src/test/kotlin/com/ubertob/kondor/json/JDataClassTest.kt b/kondor-auto/src/test/kotlin/com/ubertob/kondor/json/JDataClassTest.kt index f5fa1247..7a3fbd95 100644 --- a/kondor-auto/src/test/kotlin/com/ubertob/kondor/json/JDataClassTest.kt +++ b/kondor-auto/src/test/kotlin/com/ubertob/kondor/json/JDataClassTest.kt @@ -1,5 +1,6 @@ package com.ubertob.kondor.json +import com.ubertob.kondor.json.array.JSet import com.ubertob.kondor.json.jsonnode.NodePathRoot import com.ubertob.kondor.outcome.Failure import com.ubertob.kondor.randomList @@ -146,4 +147,4 @@ class JDataClassTest { PersonRefl.testParserAndRender(100) { randomPerson() } } -} \ No newline at end of file +} diff --git a/kondor-core/build.gradle.kts b/kondor-core/build.gradle.kts index 1eaa6a5e..4e063a68 100644 --- a/kondor-core/build.gradle.kts +++ b/kondor-core/build.gradle.kts @@ -105,4 +105,4 @@ signing { tasks.withType { (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) -} \ No newline at end of file +} diff --git a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/ShortFunctions.kt b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/ShortFunctions.kt index 11a2bea5..136bcc9d 100644 --- a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/ShortFunctions.kt +++ b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/ShortFunctions.kt @@ -1,5 +1,7 @@ package com.ubertob.kondor.json +import com.ubertob.kondor.json.array.JList +import com.ubertob.kondor.json.array.JSet import com.ubertob.kondor.json.jsonnode.JsonNodeObject import java.math.BigDecimal import java.math.BigInteger diff --git a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JAnyAsArray.kt b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JAnyAsArray.kt new file mode 100644 index 00000000..10badf32 --- /dev/null +++ b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JAnyAsArray.kt @@ -0,0 +1,19 @@ +package com.ubertob.kondor.json.array + +import com.ubertob.kondor.json.* +import com.ubertob.kondor.json.JsonStyle.Companion.appendArrayValues + +class JAnyAsArray>( + override val converter: JConverter, + override val cons: (IterT) -> PT, + override val binder: PT.() -> IterT, +) : JArrayRepresentable() { + + override fun appendValue(app: CharWriter, style: JsonStyle, offset: Int, value: PT): CharWriter { + return app.appendArrayValues(style, offset, value.binder(), converter::appendValue) + } + + @Suppress("UNCHECKED_CAST") + override fun convertToAny(from: Iterable): PT = cons(from.filterNotNull() as IterT) +} + diff --git a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArray.kt b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArray.kt new file mode 100644 index 00000000..b13db4c0 --- /dev/null +++ b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArray.kt @@ -0,0 +1,31 @@ +package com.ubertob.kondor.json.array + +import com.ubertob.kondor.json.* +import com.ubertob.kondor.json.jsonnode.* + +abstract class JArray> : JArrayRepresentable() { + override val _nodeType = ArrayNode + + override val cons: (IterT) -> IterT = { it } + override val binder: IterT.() -> IterT = { this } + override fun convertToAny(from: Iterable) = convertToCollection(from) + + abstract fun convertToCollection(from: Iterable): IterT +} + + +data class JList(override val converter: JConverter) : JArray>() { + override fun convertToCollection(from: Iterable): List = from.filterNotNull() +} + +data class JNullableList(override val converter: JConverter) : JArray>() { + + override val jsonStyle = JsonStyle.singleLineWithNulls + override fun convertToCollection(from: Iterable): List = from.toList() + override val _nodeType = ArrayNode +} + +data class JSet(override val converter: JConverter) : JArray>() { + override fun convertToCollection(from: Iterable): Set = from.filterNotNull().toSet() + override val _nodeType = ArrayNode +} diff --git a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArrayRepresentable.kt b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArrayRepresentable.kt new file mode 100644 index 00000000..a993bca7 --- /dev/null +++ b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JArrayRepresentable.kt @@ -0,0 +1,51 @@ +package com.ubertob.kondor.json.array + +import com.ubertob.kondor.json.* +import com.ubertob.kondor.json.JsonStyle.Companion.appendArrayValues +import com.ubertob.kondor.json.jsonnode.* +import com.ubertob.kondor.json.parser.KondorSeparator +import com.ubertob.kondor.json.parser.TokensStream +import com.ubertob.kondor.json.parser.parseArray +import com.ubertob.kondor.json.parser.surrounded +import com.ubertob.kondor.json.schema.arraySchema +import com.ubertob.kondor.outcome.Outcome +import com.ubertob.kondor.outcome.traverseIndexed + +abstract class JArrayRepresentable> : JArrayConverter { + override val _nodeType = ArrayNode + + abstract val converter: JConverter + abstract val cons : (IterIT) -> T + abstract val binder: T.() -> IterIT + + abstract fun convertToAny(from: Iterable): T + + override fun fromJsonNode(node: JsonNodeArray, path: NodePath): Outcome = + mapFromArray(node) { i, e -> converter.fromJsonNodeBase(e, NodePathSegment("[$i]", path)) } + .transform { convertToAny(it) } + + override fun toJsonNode(value: T): JsonNodeArray = + mapToJson(value.binder()) { converter.toJsonNode(it) } + + override fun appendValue(app: CharWriter, style: JsonStyle, offset: Int, value: T): CharWriter = + app.appendArrayValues(style, offset, value.binder(), converter::appendValue) + + private fun mapFromArray( + node: JsonNodeArray, + f: (Int, JsonNode) -> JsonOutcome + ): JsonOutcome> = node.elements + .traverseIndexed(f) + + private fun mapToJson(objs: Iterable, f: (T) -> JsonNode): JsonNodeArray = + JsonNodeArray(objs.map { if (it == null) JsonNodeNull else f(it) }) + + @Suppress("UNCHECKED_CAST") + override fun fromTokens(tokens: TokensStream, path: NodePath): JsonOutcome = + surrounded( + KondorSeparator.OpeningBracket, + { t, p -> parseArray(t, p, converter::fromTokens).transform { cons(it as IterIT) } }, + KondorSeparator.ClosingBracket + )(tokens, path) + + override fun schema(): JsonNodeObject = arraySchema(converter) +} diff --git a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/JArray.kt b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailures.kt similarity index 60% rename from kondor-core/src/main/kotlin/com/ubertob/kondor/json/JArray.kt rename to kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailures.kt index 04a9c67c..fdd42650 100644 --- a/kondor-core/src/main/kotlin/com/ubertob/kondor/json/JArray.kt +++ b/kondor-core/src/main/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailures.kt @@ -1,5 +1,6 @@ -package com.ubertob.kondor.json +package com.ubertob.kondor.json.array +import com.ubertob.kondor.json.* import com.ubertob.kondor.json.JsonStyle.Companion.appendArrayValues import com.ubertob.kondor.json.jsonnode.* import com.ubertob.kondor.json.parser.KondorSeparator.ClosingBracket @@ -7,15 +8,19 @@ import com.ubertob.kondor.json.parser.KondorSeparator.OpeningBracket import com.ubertob.kondor.json.parser.TokensStream import com.ubertob.kondor.json.parser.parseArray import com.ubertob.kondor.json.parser.surrounded -import com.ubertob.kondor.json.schema.arraySchema import com.ubertob.kondor.outcome.Outcome -import com.ubertob.kondor.outcome.traverseIndexed +import com.ubertob.kondor.outcome.asSuccess +import com.ubertob.kondor.outcome.bindFailure +import com.ubertob.kondor.outcome.foldOutcomeIndexed -interface JArray> : JArrayConverter { +/** + * Array converter that ignorer failures in its sub converter + */ +class JListIgnoreFailures>(val converter: JConverter) : JArrayConverter { + override val _nodeType: NodeKind = ArrayNode - val converter: JConverter - - fun convertToCollection(from: Iterable): IterT + @Suppress("UNCHECKED_CAST") + fun convertToCollection(from: Iterable): IterT = from.filterNotNull() as IterT override fun fromJsonNode(node: JsonNodeArray, path: NodePath): Outcome = mapFromArray(node) { i, e -> converter.fromJsonNodeBase(e, NodePathSegment("[$i]", path)) } @@ -36,9 +41,9 @@ interface JArray> : JArrayConverter { node: JsonNodeArray, f: (Int, JsonNode) -> JsonOutcome ): JsonOutcome> = node.elements - .traverseIndexed(f) - - override fun schema(): JsonNodeObject = arraySchema(converter) + .foldOutcomeIndexed(mutableListOf()) { index, acc, e -> + f(index, e).transform { acc.add(it); acc }.bindFailure { acc.asSuccess() } + } override fun appendValue(app: CharWriter, style: JsonStyle, offset: Int, value: IterT): CharWriter = app.appendArrayValues(style, offset, value, converter::appendValue) @@ -50,23 +55,4 @@ interface JArray> : JArrayConverter { { t, p -> parseArray(t, p, converter::fromTokens).transform { it as IterT } }, ClosingBracket )(tokens, path) - } - - -data class JList(override val converter: JConverter) : JArray> { - override fun convertToCollection(from: Iterable): List = from.filterNotNull().toList() - override val _nodeType = ArrayNode -} - -data class JNullableList(override val converter: JConverter) : JArray> { - - override val jsonStyle = JsonStyle.singleLineWithNulls - override fun convertToCollection(from: Iterable): List = from.toList() - override val _nodeType = ArrayNode -} - -data class JSet(override val converter: JConverter) : JArray> { - override fun convertToCollection(from: Iterable): Set = from.filterNotNull().toSet() - override val _nodeType = ArrayNode -} \ No newline at end of file diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/DomainExampleForTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/DomainExampleForTest.kt index 32fb0d09..79ec441d 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/DomainExampleForTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/DomainExampleForTest.kt @@ -3,6 +3,8 @@ package com.ubertob.kondor.json import com.ubertob.kondor.* import com.ubertob.kondor.json.JsonStyle.Companion.prettyWithNulls import com.ubertob.kondor.json.TitleType.Companion.fromLabel +import com.ubertob.kondor.json.array.JArray +import com.ubertob.kondor.json.array.JList import com.ubertob.kondor.json.datetime.num import com.ubertob.kondor.json.datetime.str import com.ubertob.kondor.json.jsonnode.* @@ -337,7 +339,7 @@ class Products : ArrayList() { } //custom collection -object JProducts : JArray { +object JProducts : JArray() { override val converter = JProduct override fun convertToCollection(from: Iterable) = diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesExtraTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesExtraTest.kt index a29997fe..0a2f1e0d 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesExtraTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesExtraTest.kt @@ -1,6 +1,7 @@ package com.ubertob.kondor.json import com.ubertob.kondor.json.JsonStyle.Companion.pretty +import com.ubertob.kondor.json.array.JList import com.ubertob.kondor.json.jsonnode.NodePathRoot import com.ubertob.kondor.lowercase import com.ubertob.kondor.randomList diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesTest.kt index dd14f2ba..17d8b4fe 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JValuesTest.kt @@ -1,5 +1,7 @@ package com.ubertob.kondor.json +import com.ubertob.kondor.json.array.JList +import com.ubertob.kondor.json.array.JSet import com.ubertob.kondor.json.jsonnode.NodePathRoot import com.ubertob.kondor.randomList import com.ubertob.kondor.randomString diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JsonRenderTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JsonRenderTest.kt index 3f073f6b..0f0b50d6 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JsonRenderTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/JsonRenderTest.kt @@ -4,6 +4,8 @@ import com.ubertob.kondor.json.JsonStyle.Companion.compact import com.ubertob.kondor.json.JsonStyle.Companion.compactWithNulls import com.ubertob.kondor.json.JsonStyle.Companion.pretty import com.ubertob.kondor.json.JsonStyle.Companion.prettyWithNulls +import com.ubertob.kondor.json.array.JList +import com.ubertob.kondor.json.array.JNullableList import com.ubertob.kondor.json.jsonnode.* import com.ubertob.kondortools.expectSuccess import org.junit.jupiter.api.Test @@ -314,7 +316,7 @@ class JsonRenderTest { expectThat(jsonString).isEqualTo(expected) } } - + @Test fun `pretty render array with nested objects and arrays`() { repeat(5) { @@ -344,7 +346,7 @@ class JsonRenderTest { )) )) )) - + val jsonString = tree.render(pretty) val expected = """[ @@ -373,11 +375,11 @@ class JsonRenderTest { | ] | } |]""".trimMargin() - + expectThat(jsonString).isEqualTo(expected) } } - + @Test fun `pretty render object with nested objects and arrays`() { repeat(5) { @@ -407,7 +409,7 @@ class JsonRenderTest { )) )) )) - + val jsonString = tree.render(pretty) val expected = """{ @@ -436,7 +438,7 @@ class JsonRenderTest { | ] | } |}""".trimMargin() - + expectThat(jsonString).isEqualTo(expected) } } @@ -534,4 +536,4 @@ class JsonRenderTest { |}""".trimMargin() ) } -} \ No newline at end of file +} diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/ParserFailuresTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/ParserFailuresTest.kt index f8b08af6..393c7057 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/ParserFailuresTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/ParserFailuresTest.kt @@ -1,5 +1,6 @@ package com.ubertob.kondor.json +import com.ubertob.kondor.json.array.JList import com.ubertob.kondor.json.jsonnode.JsonNodeObject import com.ubertob.kondor.json.jsonnode.parseJsonNode import com.ubertob.kondortools.expectFailure diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JAnyAsArrayTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JAnyAsArrayTest.kt new file mode 100644 index 00000000..250eeb36 --- /dev/null +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JAnyAsArrayTest.kt @@ -0,0 +1,21 @@ +import com.ubertob.kondor.json.JInt +import com.ubertob.kondor.json.array.JAnyAsArray +import com.ubertob.kondortools.expectSuccess +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.isEqualTo + +class JAnyAsArrayTest { + @Test + fun `encodes an object that may be represented as an array`() { + val a = Arrayable(listOf(1, 2, 3)) + val json = JArrayable.toJson(a) + + expectThat(json).isEqualTo("[1, 2, 3]") + expectThat(JArrayable.fromJson(json).expectSuccess()).isEqualTo(a) + } +} + +private data class Arrayable(val list: List) + +private val JArrayable = JAnyAsArray(JInt, ::Arrayable, Arrayable::list) diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailuresTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailuresTest.kt new file mode 100644 index 00000000..694d18e4 --- /dev/null +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/json/array/JListIgnoreFailuresTest.kt @@ -0,0 +1,39 @@ +package com.ubertob.kondor.json.array + +import com.ubertob.kondor.json.JInt +import com.ubertob.kondor.json.jsonnode.JsonNodeArray +import com.ubertob.kondor.json.jsonnode.JsonNodeNumber +import com.ubertob.kondor.json.jsonnode.JsonNodeString +import com.ubertob.kondortools.expectSuccess +import strikt.api.expectThat +import strikt.assertions.isEqualTo +import org.junit.jupiter.api.Test + +class JListIgnoreFailuresTest { + val aMixedBagJsonNode = JsonNodeArray( + listOf( + JsonNodeNumber(1), + JsonNodeNumber(2), + JsonNodeString("two-and-a-half"), + JsonNodeNumber(3) + ) + ) + + val anUnmixedBag = listOf(1,2,3) + + val converter = JListIgnoreFailures(JInt) + + @Test + fun `ignores failures in the collection type conversion`() { + val deserialized = converter.fromJsonNodeBase(aMixedBagJsonNode).expectSuccess() + expectThat(deserialized).isEqualTo(anUnmixedBag) + } + + @Test + fun `still serializes normally`() { + val json = converter.toJson(anUnmixedBag) + val deserialized = converter.fromJson(json).expectSuccess() + + expectThat(deserialized).isEqualTo(anUnmixedBag) + } +} diff --git a/kondor-core/src/test/kotlin/com/ubertob/kondor/loadTests/PerformanceTest.kt b/kondor-core/src/test/kotlin/com/ubertob/kondor/loadTests/PerformanceTest.kt index f3a571b1..e0264b18 100644 --- a/kondor-core/src/test/kotlin/com/ubertob/kondor/loadTests/PerformanceTest.kt +++ b/kondor-core/src/test/kotlin/com/ubertob/kondor/loadTests/PerformanceTest.kt @@ -3,6 +3,7 @@ package com.ubertob.kondor.loadTests import com.ubertob.kondor.all import com.ubertob.kondor.chronoAndLog import com.ubertob.kondor.json.* +import com.ubertob.kondor.json.array.JList import com.ubertob.kondor.json.jsonnode.ArrayNode import com.ubertob.kondor.json.jsonnode.NodePathRoot import com.ubertob.kondor.json.jsonnode.onRoot diff --git a/kondor-examples/src/jmh/kotlin/com/ubertob/kondor/json/jmh/KondorConverters.kt b/kondor-examples/src/jmh/kotlin/com/ubertob/kondor/json/jmh/KondorConverters.kt index 7307fc43..f8119f3d 100644 --- a/kondor-examples/src/jmh/kotlin/com/ubertob/kondor/json/jmh/KondorConverters.kt +++ b/kondor-examples/src/jmh/kotlin/com/ubertob/kondor/json/jmh/KondorConverters.kt @@ -1,6 +1,7 @@ package com.ubertob.kondor.json.jmh import com.ubertob.kondor.json.* +import com.ubertob.kondor.json.array.JList import com.ubertob.kondor.json.jsonnode.JsonNodeObject @@ -26,4 +27,4 @@ object JDemoClass : JAny() { val jDemoClasses = JList(JDemoClass) val demoClassNdProducer = toNdJson(JDemoClass) -val demoClassNdConsumer = fromNdJsonToList(JDemoClass) \ No newline at end of file +val demoClassNdConsumer = fromNdJsonToList(JDemoClass)