diff --git a/api/IonElement.api b/api/IonElement.api index f6a3823..bec2bf5 100644 --- a/api/IonElement.api +++ b/api/IonElement.api @@ -215,6 +215,10 @@ public final class com/amazon/ionelement/api/IntElementSize : java/lang/Enum { } public final class com/amazon/ionelement/api/Ion { + public static final fun buildStruct (Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement; + public static final fun buildStruct (Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement; + public static final fun buildStruct (Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement; + public static synthetic fun buildStruct$default (Ljava/util/List;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/amazon/ionelement/api/StructElement; public static final fun emptyBlob ()Lcom/amazon/ionelement/api/BlobElement; public static final fun emptyClob ()Lcom/amazon/ionelement/api/ClobElement; public static final fun emptyIonList ()Lcom/amazon/ionelement/api/ListElement; @@ -573,6 +577,21 @@ public abstract interface class com/amazon/ionelement/api/LobElement : com/amazo public abstract fun withoutMetas ()Lcom/amazon/ionelement/api/LobElement; } +public abstract interface class com/amazon/ionelement/api/MutableStructFields : java/util/Collection, kotlin/jvm/internal/markers/KMutableCollection { + public abstract fun add (Lcom/amazon/ionelement/api/StructField;)Z + public abstract fun add (Ljava/lang/String;Lcom/amazon/ionelement/api/IonElement;)Z + public abstract fun clearField (Ljava/lang/String;)Z + public abstract fun containsField (Ljava/lang/String;)Z + public abstract fun get (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement; + public abstract fun getAll (Ljava/lang/String;)Ljava/util/Collection; + public abstract fun getOptional (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement; + public abstract fun plusAssign (Ljava/util/Collection;)V + public abstract fun remove (Lcom/amazon/ionelement/api/StructField;)Z + public abstract fun removeAll (Ljava/util/Collection;)Z + public abstract fun set (Ljava/lang/String;Lcom/amazon/ionelement/api/IonElement;)V + public abstract fun setAll (Ljava/lang/Iterable;)V +} + public abstract interface class com/amazon/ionelement/api/SeqElement : com/amazon/ionelement/api/ContainerElement { public abstract fun copy (Ljava/util/List;Ljava/util/Map;)Lcom/amazon/ionelement/api/SeqElement; public abstract fun getValues ()Ljava/util/List; @@ -611,6 +630,8 @@ public abstract interface class com/amazon/ionelement/api/StructElement : com/am public abstract fun getAll (Ljava/lang/String;)Ljava/lang/Iterable; public abstract fun getFields ()Ljava/util/Collection; public abstract fun getOptional (Ljava/lang/String;)Lcom/amazon/ionelement/api/AnyElement; + public abstract fun mutableFields ()Lcom/amazon/ionelement/api/MutableStructFields; + public abstract fun update (Lkotlin/jvm/functions/Function1;)Lcom/amazon/ionelement/api/StructElement; public abstract fun withAnnotations (Ljava/lang/Iterable;)Lcom/amazon/ionelement/api/StructElement; public abstract fun withAnnotations ([Ljava/lang/String;)Lcom/amazon/ionelement/api/StructElement; public abstract fun withMeta (Ljava/lang/String;Ljava/lang/Object;)Lcom/amazon/ionelement/api/StructElement; diff --git a/src/com/amazon/ionelement/api/Ion.kt b/src/com/amazon/ionelement/api/Ion.kt index 0fdaa78..bd3d286 100644 --- a/src/com/amazon/ionelement/api/Ion.kt +++ b/src/com/amazon/ionelement/api/Ion.kt @@ -471,6 +471,17 @@ public fun ionStructOf( metas ) +@JvmOverloads +public fun buildStruct( + annotations: Annotations = emptyList(), + metas: MetaContainer = emptyMetaContainer(), + body: MutableStructFields.() -> Unit +): StructElement { + val fields = emptyIonStruct().mutableFields() + body(fields) + return ionStructOf(fields, annotations, metas) +} + // Memoized empty PersistentList for the memoized container types and null values private val EMPTY_PERSISTENT_LIST: PersistentList = emptyList().toPersistentList() diff --git a/src/com/amazon/ionelement/api/IonElement.kt b/src/com/amazon/ionelement/api/IonElement.kt index 4bcb362..932d90c 100644 --- a/src/com/amazon/ionelement/api/IonElement.kt +++ b/src/com/amazon/ionelement/api/IonElement.kt @@ -443,6 +443,16 @@ public interface StructElement : ContainerElement { /** Returns true if this StructElement has at least one field with the given field name. */ public fun containsField(fieldName: String): Boolean + /** A mutable shallow copy of this struct's fields */ + public fun mutableFields(): MutableStructFields + + /** Creates a new struct from this struct after executing the lambda body with [MutableStructFields] as the + * receiver. + * + * Annotations and metas are preserved. + */ + public fun update(body: MutableStructFields.() -> Unit): StructElement + override fun copy(annotations: List, metas: MetaContainer): StructElement override fun withAnnotations(vararg additionalAnnotations: String): StructElement override fun withAnnotations(additionalAnnotations: Iterable): StructElement diff --git a/src/com/amazon/ionelement/api/MutableStructFields.kt b/src/com/amazon/ionelement/api/MutableStructFields.kt new file mode 100644 index 0000000..1091dca --- /dev/null +++ b/src/com/amazon/ionelement/api/MutableStructFields.kt @@ -0,0 +1,67 @@ +package com.amazon.ionelement.api + +/** + * Represents a mutable view of a collection of struct fields. + */ +public interface MutableStructFields : MutableCollection { + + /** + * Retrieves the value of the first field found with the specified name. + * + * In the case of multiple fields with the specified name, the caller assumes that one is picked at random. + * + * @throws IonElementException If there are no fields with the specified [fieldName]. + */ + public operator fun get(fieldName: String): AnyElement + + /** The same as [get] but returns a null reference if the field does not exist. */ + public fun getOptional(fieldName: String): AnyElement? + + /** Retrieves all values with a given field name. Returns an empty iterable if the field does not exist. */ + public fun getAll(fieldName: String): Collection + + /** Returns true if this StructElement has at least one field with the given field name. */ + public fun containsField(fieldName: String): Boolean + + /** + * If one or more fields with the specified name already exists, this replaces all of them with the value provided. + * + * Otherwise, a new field with the given name and value is added to the collection. + */ + public operator fun set(fieldName: String, value: IonElement) + + /** + * Adds all the given fields to the collection. For existing fields with the same names as the fields provided, all + * instances of those fields will be removed. + */ + public fun setAll(fields: Iterable) + + /** + * Adds a new field to the collection with the given name and value. The collection may have multiple fields with + * the same name. + */ + public fun add(fieldName: String, value: IonElement): Boolean + + /** Adds the given field to the collection. The collection may have multiple fields with the same name. + * + * Repeated fields are allowed, so this will always return true. */ + public override fun add(element: StructField): Boolean + + /** Removes a random occurrence of a field the matches the given field, or does nothing if no field exists. + * + * Returns true is a field was removed. */ + public override fun remove(element: StructField): Boolean + + /** Removes all occurrence of a field the matches the given field name, or does nothing if no field exists. + * + * Returns true if a field was removed. */ + public fun clearField(fieldName: String): Boolean + + /** Removes all of this collection's elements that are also contained in the specified collection. + * + * At most one field per element in the give collection is removed. */ + public override fun removeAll(elements: Collection): Boolean + + /** Adds all the given fields to the collection */ + public operator fun plusAssign(fields: Collection) +} diff --git a/src/com/amazon/ionelement/impl/MutableStructFieldsImpl.kt b/src/com/amazon/ionelement/impl/MutableStructFieldsImpl.kt new file mode 100644 index 0000000..38d8f33 --- /dev/null +++ b/src/com/amazon/ionelement/impl/MutableStructFieldsImpl.kt @@ -0,0 +1,177 @@ +package com.amazon.ionelement.impl + +import com.amazon.ionelement.api.AnyElement +import com.amazon.ionelement.api.IonElement +import com.amazon.ionelement.api.MutableStructFields +import com.amazon.ionelement.api.StructField +import com.amazon.ionelement.api.field +import java.util.NoSuchElementException + +internal class MutableStructFieldsImpl(private val fields: MutableMap>) : + MutableStructFields { + override val size: Int + get() = fields.values.sumBy { it.size } + + override fun get(fieldName: String): AnyElement { + return requireNotNull(fields[fieldName]?.firstOrNull()?.value) { + "Required struct field '$fieldName' missing" + } + } + + override fun getOptional(fieldName: String): AnyElement? { + return fields[fieldName]?.firstOrNull()?.value + } + + override fun getAll(fieldName: String): Collection { + return fields[fieldName]?.map { it.value } ?: mutableListOf() + } + + override fun containsField(fieldName: String): Boolean { + return fieldName in fields + } + + override fun set(fieldName: String, value: IonElement) { + val values = fields.getOrPut(fieldName, ::mutableListOf) + values.clear() + values.add(field(fieldName, value)) + } + + override fun setAll(fields: Iterable) { + fields.groupByTo(mutableMapOf(), { it.name }, { it.value }).forEach { (name, values) -> + this.fields[name] = values.map { field(name, it) }.toMutableList() + } + } + + override fun add(fieldName: String, value: IonElement): Boolean { + value as AnyElement + val values = fields[fieldName] + if (values == null) { + fields[fieldName] = mutableListOf(field(fieldName, value)) + } else { + values.add(field(fieldName, value)) + } + return true + } + + override fun add(element: StructField): Boolean { + return add(element.name, element.value) + } + + override fun remove(element: StructField): Boolean { + return fields[element.name]?.remove(element) ?: false + } + + override fun clearField(fieldName: String): Boolean { + fields[fieldName]?.clear() ?: return false + return true + } + + override fun removeAll(elements: Collection): Boolean { + var modified = false + for (element in elements) { + modified = remove(element) || modified + } + return modified + } + + override fun plusAssign(fields: Collection) { + addAll(fields) + } + + override fun addAll(elements: Collection): Boolean { + elements.forEach { add(it) } + return true + } + + override fun clear() { + fields.clear() + } + + override fun contains(element: StructField): Boolean { + return fields[element.name]?.contains(element) ?: false + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } + } + + override fun isEmpty(): Boolean { + return fields.all { it.value.isEmpty() } + } + + override fun iterator(): MutableIterator { + return MutableStructFieldsIterator(fields) + } + + override fun retainAll(elements: Collection): Boolean { + var modified = false + val it = iterator() + while (it.hasNext()) { + val field = it.next() + if (field !in elements) { + it.remove() + modified = true + } + } + return modified + } +} + +internal class MutableStructFieldsIterator(fieldsMap: MutableMap>) : + MutableIterator { + + private val mapIterator = fieldsMap.iterator() + private var listIterator: MutableIterator = + if (mapIterator.hasNext()) { + mapIterator.next().value.iterator() + } else { + EMPTY_ITERATOR + } + + override fun hasNext(): Boolean { + if (listIterator.hasNext()) { + return true + } + + nextFieldList() + + return listIterator.hasNext() + } + + override fun next(): StructField { + nextFieldList() + + return listIterator.next() + } + + override fun remove() { + listIterator.remove() + } + + private fun nextFieldList() { + while (!listIterator.hasNext()) { + if (mapIterator.hasNext()) { + listIterator = mapIterator.next().value.iterator() + } else { + listIterator = EMPTY_ITERATOR + break + } + } + } + + companion object { + private val EMPTY_ITERATOR = object : MutableIterator { + override fun hasNext(): Boolean { + return false + } + + override fun next(): StructField { + throw NoSuchElementException() + } + + override fun remove() { + throw IllegalStateException() + } + } + } +} diff --git a/src/com/amazon/ionelement/impl/StructElementImpl.kt b/src/com/amazon/ionelement/impl/StructElementImpl.kt index 59ca149..5ae3a19 100644 --- a/src/com/amazon/ionelement/impl/StructElementImpl.kt +++ b/src/com/amazon/ionelement/impl/StructElementImpl.kt @@ -66,6 +66,21 @@ internal class StructElementImpl( return fieldsByNameBackingField!! } + override fun mutableFields(): MutableStructFields { + val internalMap = mutableMapOf>() + return MutableStructFieldsImpl( + fieldsByName.mapValuesTo(internalMap) { (name, values) -> + values.map { field(name, it) }.toMutableList() + } + ) + } + + override fun update(body: MutableStructFields.() -> Unit): StructElement { + val mutableFields = mutableFields() + body(mutableFields) + return ionStructOf(mutableFields, annotations, metas) + } + override fun get(fieldName: String): AnyElement = fieldsByName[fieldName]?.firstOrNull() ?: constraintError(this, "Required struct field '$fieldName' missing") @@ -80,7 +95,9 @@ internal class StructElementImpl( StructElementImpl(allFields, annotations.toPersistentList(), metas.toPersistentMap()) override fun withAnnotations(vararg additionalAnnotations: String): StructElementImpl = _withAnnotations(*additionalAnnotations) + override fun withAnnotations(additionalAnnotations: Iterable): StructElementImpl = _withAnnotations(additionalAnnotations) + override fun withoutAnnotations(): StructElementImpl = _withoutAnnotations() override fun withMetas(additionalMetas: MetaContainer): StructElementImpl = _withMetas(additionalMetas) override fun withMeta(key: String, value: Any): StructElementImpl = _withMeta(key, value) diff --git a/test/com/amazon/ionelement/MutableStructFieldsTests.kt b/test/com/amazon/ionelement/MutableStructFieldsTests.kt new file mode 100644 index 0000000..42afb10 --- /dev/null +++ b/test/com/amazon/ionelement/MutableStructFieldsTests.kt @@ -0,0 +1,464 @@ +package com.amazon.ionelement + +import com.amazon.ionelement.api.buildStruct +import com.amazon.ionelement.api.emptyIonStruct +import com.amazon.ionelement.api.field +import com.amazon.ionelement.api.ionInt +import com.amazon.ionelement.api.ionListOf +import com.amazon.ionelement.api.ionString +import com.amazon.ionelement.api.ionStructOf +import com.amazon.ionelement.api.ionSymbol +import com.amazon.ionelement.api.loadSingleElement +import kotlin.test.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class MutableStructFieldsTests { + @Test + fun `StructElement to mutable fields back to StructElement`() { + testStruct.mutableFields().size + assertEquals(testStruct, ionStructOf(testStruct.mutableFields())) + } + + @Test + fun `StructElement to mutable fields back to StructElement with annotations and metas`() { + assertEquals( + testStruct + .withAnnotations("foo", "bar") + .withMeta("meta", 1), + + ionStructOf(testStruct.mutableFields(), listOf("foo", "bar"), mapOf("meta" to 1)) + ) + } + + @Test + fun get() { + val mutableFields = testStruct.mutableFields() + assertEquals(ionString("123-456-789"), mutableFields["isbn"].asString()) + assertThrows { + mutableFields["nothing"] + } + } + + @Test + fun getOptional() { + val mutableFields = testStruct.mutableFields() + assertNull(mutableFields.getOptional("nothing")) + assertEquals(ionString("123-456-789"), mutableFields.getOptional("isbn")?.asString()) + } + + @Test + fun getAll() { + val mutableFields = testStruct.mutableFields() + assertEquals(2, mutableFields.getAll("author").count()) + assertEquals( + setOf( + ionStructOf("lastname" to ionString("Doe"), "firstname" to ionString("Jane")), + ionStructOf("lastname" to ionString("Smith"), "firstname" to ionString("Jane")) + ), + mutableFields.getAll("author").map { it.asStruct() }.toSet() + ) + assertEquals(0, mutableFields.getAll("nothing").count()) + } + + @Test + fun containsField() { + val mutableFields = testStruct.mutableFields() + assertTrue(mutableFields.containsField("author")) + assertTrue(mutableFields.containsField("isbn")) + assertFalse(mutableFields.containsField("nothing")) + } + + @Test + fun set() { + val mutableFields = testStruct.mutableFields() + mutableFields["nothing"] = ionInt(0) + mutableFields["isbn"] = ionInt(1) + mutableFields["author"] = ionInt(2) + + assertEquals(ionInt(0), mutableFields["nothing"].asInt()) + assertEquals(ionInt(1), mutableFields["isbn"].asInt()) + assertEquals(1, mutableFields.getAll("author").count()) + assertEquals(ionInt(2), mutableFields.getAll("author").first().asInt()) + } + + @Test + fun setAll() { + val mutableFields = testStruct.mutableFields() + mutableFields.setAll( + listOf( + field("year", ionInt(2000)), + field("year", ionInt(2012)) + ) + ) + mutableFields.setAll( + listOf( + field("author", ionString("Alice")), + field("author", ionString("Bob")) + ) + ) + assertEquals( + setOf(ionInt(2000).asAnyElement(), ionInt(2012).asAnyElement()), + mutableFields.getAll("year").toSet() + ) + + assertEquals( + setOf(ionString("Alice").asAnyElement(), ionString("Bob").asAnyElement()), + mutableFields.getAll("author").toSet() + ) + } + + @Test + fun add() { + val mutableFields = testStruct.mutableFields() + mutableFields.add(field("year", ionInt(2000))) + mutableFields.add("year", ionInt(2012)) + mutableFields.add("author", ionString("Alice")) + + assertEquals( + setOf(ionInt(2000).asAnyElement(), ionInt(2012).asAnyElement()), + mutableFields.getAll("year").toSet() + ) + + assertEquals( + setOf(ionString("Alice").asAnyElement()).plus(testStruct.getAll("author")), + mutableFields.getAll("author").toSet() + ) + } + + @Test + fun plusAssign() { + val mutableFields = testStruct.mutableFields() + mutableFields += field("year", ionInt(2000)) + mutableFields += listOf(field("year", ionInt(2012)), field("year", ionInt(2021))) + + assertEquals( + setOf(ionInt(2000).asAnyElement(), ionInt(2012).asAnyElement(), ionInt(2021).asAnyElement()), + mutableFields.getAll("year").toSet() + ) + } + + @Test + fun remove() { + val mutableFields = testStruct.mutableFields() + mutableFields.remove(field("author", loadSingleElement("{lastname: \"Doe\", firstname: \"Jane\" }"))) + mutableFields.remove(field("author", loadSingleElement("{lastname: \"Smith\", firstname: \"Jane\" }"))) + + assertNull(mutableFields.getOptional("author")) + } + + @Test + fun removeAll() { + val mutableFields = testStruct.mutableFields() + + mutableFields.removeAll( + listOf( + field( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ), + field( + "author", + buildStruct { + add("lastname", ionString("Smith")) + add("firstname", ionString("Jane")) + } + ) + ) + ) + + val expected = buildStruct { + add("isbn", ionString("123-456-789")) + add("title", ionString("AWS User Guide")) + add("category", ionListOf(ionSymbol("Non-Fiction"), ionSymbol("Technology"))) + } + + assertEquals(expected, ionStructOf(mutableFields)) + } + + @Test + fun retainAll() { + val mutableFields = testStruct.mutableFields() + + mutableFields.retainAll( + listOf( + field( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ), + field( + "author", + buildStruct { + add("lastname", ionString("Smith")) + add("firstname", ionString("Jane")) + } + ) + ).toSet() + ) + + val updated = ionStructOf(mutableFields) + + val expected = testStruct.update { + this.removeIf { + it.name in setOf("isbn", "title", "category") + } + } + + assertEquals(expected, updated) + } + + @Test + fun testClear() { + val mutableFields = testStruct.mutableFields() + mutableFields.clear() + + assertEquals(emptyIonStruct(), ionStructOf(mutableFields)) + assertTrue(mutableFields.size == 0) + assertFalse(mutableFields.iterator().hasNext()) + } + + @Test + fun testClearField() { + val mutableFields = testStruct.mutableFields() + mutableFields.clearField("author") + + val expected = testStruct.update { + removeAll( + listOf( + field( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ), + field( + "author", + buildStruct { + add("lastname", ionString("Smith")) + add("firstname", ionString("Jane")) + } + ) + ) + ) + } + + assertEquals(expected, ionStructOf(mutableFields)) + } + + @Test + fun testContains() { + val mutableFields = testStruct.mutableFields() + + assertTrue( + mutableFields.contains( + field( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ) + ) + ) + + assertFalse(mutableFields.contains(field("foo", ionString("bar")))) + } + + @Test + fun testContainsAll() { + val mutableFields = testStruct.mutableFields() + + assertTrue( + mutableFields.containsAll( + listOf( + field( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ), + field( + "author", + buildStruct { + add("lastname", ionString("Smith")) + add("firstname", ionString("Jane")) + } + ) + ) + ) + ) + + assertFalse( + mutableFields.containsAll( + listOf( + field("foo", ionString("bar")), + field("isbn", ionString("123-456-789")) + ) + ) + ) + } + + @Test + fun testIsEmpty() { + val mutableFields = testStruct.mutableFields() + + assertFalse(mutableFields.isEmpty()) + + mutableFields.clear() + + assertTrue(mutableFields.isEmpty()) + assertTrue(emptyIonStruct().mutableFields().isEmpty()) + } + + @Test + fun testIterator() { + val mutableFields = testStruct.mutableFields() + + // Ensure the iterator works properly when fields have been removed + mutableFields.add("foo", ionString("999-999")) + mutableFields.add("foo", ionString("999-999")) + mutableFields.add("bar", ionString("999-999")) + mutableFields.remove(field("foo", ionString("999-999"))) + mutableFields.remove(field("foo", ionString("999-999"))) + mutableFields.remove(field("bar", ionString("999-999"))) + + val output = buildStruct { + for (field in testStruct.mutableFields()) { + add(field) + } + } + + assertEquals(testStruct, output) + } + + @Test + fun testMutableIterator() { + val mutableFields = testStruct.mutableFields() + + val it = mutableFields.iterator() + while (it.hasNext()) { + val field = it.next() + if (field.name in setOf("title", "author", "category")) { + it.remove() + } + } + + val updated = ionStructOf(mutableFields) + + assertEquals(ionStructOf(field("isbn", ionString("123-456-789"))), updated) + } + + @Test + fun testMutableIteratorAfterRemovingAll() { + val mutableFields = testStruct.mutableFields() + + val it = mutableFields.iterator() + while (it.hasNext()) { + it.next() + it.remove() + } + + val updated = ionStructOf(mutableFields) + + assertEquals(emptyIonStruct(), updated) + assertFalse(it.hasNext()) + assertThrows { + it.next() + } + } + + @Test + fun testIteratorFailureCases() { + val it = testStruct.mutableFields().iterator() + + assertThrows { + it.remove() + } + + while (it.hasNext()) { + it.next() + } + + assertThrows { + it.next() + } + + assertThrows { + it.remove() + } + } + + @Test + fun testEmptyIterator() { + val it = emptyIonStruct().mutableFields().iterator() + + assertFalse(it.hasNext()) + + assertThrows { + it.next() + } + + assertThrows { + it.remove() + } + } + + @Test + fun buildStructTest() { + val actual = buildStruct { + add("isbn", ionString("123-456-789")) + add( + "author", + buildStruct { + add("lastname", ionString("Doe")) + add("firstname", ionString("Jane")) + } + ) + add( + "author", + buildStruct { + add("lastname", ionString("Smith")) + add("firstname", ionString("Jane")) + } + ) + add("title", ionString("AWS User Guide")) + add("category", ionListOf(ionSymbol("Non-Fiction"), ionSymbol("Technology"))) + } + + assertEquals(testStruct, actual) + } + + companion object { + val testStruct = loadSingleElement( + """ + { + isbn: "123-456-789", + author: { + lastname: "Doe", + firstname: "Jane" + }, + author: { + lastname: "Smith", + firstname: "Jane" + }, + title: "AWS User Guide", + category: [ + 'Non-Fiction', + 'Technology' + ] + } + """ + ).asStruct() + } +} diff --git a/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java b/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java new file mode 100644 index 0000000..af96eb1 --- /dev/null +++ b/test/com/amazon/ionelement/demos/java/MutableStructFieldsDemo.java @@ -0,0 +1,33 @@ +package com.amazon.ionelement.demos.java; + +import com.amazon.ionelement.api.Ion; +import com.amazon.ionelement.api.StructElement; +import kotlin.Unit; +import org.junit.jupiter.api.Test; + +import static com.amazon.ionelement.api.ElementLoader.loadSingleElement; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MutableStructFieldsDemo { + + @Test + void createUpdatedStructFromExistingStruct() { + StructElement original = loadSingleElement( + "{name:\"Alice\",scores:{darts:100,billiards:15,}}").asStruct(); + + StructElement expected = loadSingleElement( + "{name:\"Alice\",scores:{darts:100,billiards:30,pingPong:200,}}").asStruct(); + + StructElement updated = original.update(fields -> { + fields.set("scores", + original.get("scores").asStruct().update(scoreFields -> { + scoreFields.add("pingPong", Ion.ionInt(200)); + scoreFields.set("billiards", Ion.ionInt(30)); + return Unit.INSTANCE; + })); + return Unit.INSTANCE; + }); + + assertEquals(expected, updated); + } +} diff --git a/test/com/amazon/ionelement/demos/kotlin/MutableStructFieldsDemo.kt b/test/com/amazon/ionelement/demos/kotlin/MutableStructFieldsDemo.kt new file mode 100644 index 0000000..8624ec0 --- /dev/null +++ b/test/com/amazon/ionelement/demos/kotlin/MutableStructFieldsDemo.kt @@ -0,0 +1,48 @@ +package com.amazon.ionelement.demos.kotlin + +import com.amazon.ionelement.api.ionInt +import com.amazon.ionelement.api.loadSingleElement +import kotlin.test.assertEquals +import org.junit.jupiter.api.Test + +class MutableStructFieldsDemo { + @Test + fun `create updated struct from existing struct`() { + val original = loadSingleElement( + """ + { + name: "Alice", + scores: { + darts: 100, + billiards: 15, + } + } + """.trimIndent() + ).asStruct() + + val expected = loadSingleElement( + """ + { + name: "Alice", + scores: { + darts: 100, + billiards: 30, + pingPong: 200, + } + } + """.trimIndent() + ).asStruct() + + val updated = original.update { + set( + "scores", + original["scores"].asStruct().update { + set("pingPong", ionInt(200)) + set("billiards", ionInt(30)) + } + ) + } + + assertEquals(expected, updated) + } +}