如何高效对kotlinx.serialization.json.JsonObject执行增、删、改原语属性操作?
JsonObject (kotlinx.serialization) Hey there! Let's break down how to efficiently add, update, and delete primitive properties in an existing JsonObject—especially considering its underlying immutable content map and nested structures.
Key Background
First, remember that JsonObject is immutable: its private content: Map<String, JsonElement> can't be modified directly. Any "change" will create a new JsonObject instance, but we can optimize this to avoid unnecessary copies.
1. Top-Level Primitive Operations
For properties directly on the root JsonObject, these operations are straightforward and efficient (leveraging Kotlin's optimized map operators):
Add a Top-Level Primitive
val originalObj: JsonObject = ... val newObj = JsonObject(originalObj.content + ("newKey" to JsonPrimitive("newValue")))
The + operator creates a new map with the added key-value pair—no full copy of the original 50-property map is needed (hash map operations are amortized O(1)).
Update a Top-Level Primitive
Same as adding: the + operator overwrites existing keys automatically.
val updatedObj = JsonObject(originalObj.content + ("existingKey" to JsonPrimitive("updatedValue")))
Delete a Top-Level Primitive
Use the - operator to remove the key from the map:
val deletedObj = JsonObject(originalObj.content - "keyToRemove")
2. Nested Primitive Operations
For properties inside nested JsonObjects (e.g., user.address.zipCode), we need to recursively update only the path of nodes that change—this avoids copying the entire nested structure.
Add/Update Nested Primitive
Create an extension function to handle recursive updates:
fun JsonObject.setNestedPrimitive(path: List<String>, value: JsonPrimitive): JsonObject { if (path.isEmpty()) return this val currentKey = path.first() val remainingPath = path.drop(1) val currentElement = this[currentKey] val newElement = when { // Reached the target path: set the primitive value remainingPath.isEmpty() -> value // Recurse into nested JsonObject currentElement is JsonObject -> currentElement.setNestedPrimitive(remainingPath, value) else -> throw IllegalArgumentException("Path segment '$currentKey' is not a JsonObject") } // Return new JsonObject with updated element return JsonObject(this.content + (currentKey to newElement)) }
Usage:
// Update "user.address.zipCode" to "90210" val updatedNestedObj = originalObj.setNestedPrimitive( path = listOf("user", "address", "zipCode"), value = JsonPrimitive("90210") )
Delete Nested Primitive
Another extension function for recursive deletion:
fun JsonObject.removeNestedPrimitive(path: List<String>): JsonObject { if (path.isEmpty()) return this val currentKey = path.first() val remainingPath = path.drop(1) val currentElement = this[currentKey] ?: return this // Key doesn't exist, return original val newElement = when { // Reached target path: mark for deletion remainingPath.isEmpty() -> null // Recurse into nested JsonObject currentElement is JsonObject -> currentElement.removeNestedPrimitive(remainingPath) else -> throw IllegalArgumentException("Path segment '$currentKey' is not a JsonObject") } return if (newElement == null) { // Remove the key entirely if we deleted its value JsonObject(this.content - currentKey) } else { // Update the nested element with its new state JsonObject(this.content + (currentKey to newElement)) } }
Usage:
// Delete "user.address.zipCode" val deletedNestedObj = originalObj.removeNestedPrimitive( path = listOf("user", "address", "zipCode") )
3. About JsonObjectBuilder
You’re right that JsonObjectBuilder doesn’t directly accept existing JsonObjects out of the box—but you can manually copy existing content into it:
val originalObj: JsonObject = ... val newObj = buildJsonObject { // Copy all existing properties first originalObj.forEach { (key, value) -> put(key, value) } // Add/update/delete as needed put("newKey", JsonPrimitive("newValue")) put("existingKey", JsonPrimitive("updatedValue")) // To delete, just omit the key (or filter before copying) }
Note: This approach copies all 50 properties, which is less efficient than the map-operator or recursive extension methods for single changes. Use it only if you need to make multiple unrelated changes in one go.
Why This Is Optimal
- Minimal copying: For top-level changes, we only modify the root map. For nested changes, only the nodes along the target path are recreated—all other nested structures are reused.
- Immutability benefits: Original
JsonObjects stay intact, which is safer for concurrency and functional programming patterns.
内容的提问来源于stack exchange,提问作者Nikhilesh Patve

