diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java b/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java index a7403110..55a56c42 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigNodeObject.java @@ -1,11 +1,9 @@ package com.typesafe.config.impl; -import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigSyntax; import java.util.ArrayList; import java.util.Collection; -import java.util.List; final class ConfigNodeObject extends ConfigNodeComplexValue { ConfigNodeObject(Collection children) { @@ -44,39 +42,38 @@ final class ConfigNodeObject extends ConfigNodeComplexValue { childrenCopy.remove(i); } continue; - }else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { + } else if (!(childrenCopy.get(i) instanceof ConfigNodeField)) { continue; } - ConfigNodeField node = (ConfigNodeField)childrenCopy.get(i); + ConfigNodeField node = (ConfigNodeField) childrenCopy.get(i); Path key = node.path().value(); - if (key.equals(desiredPath) || key.startsWith(desiredPath)) { - if (valueCopy == null) { - childrenCopy.remove(i); - // Remove any whitespace or commas after the deleted setting - for (int j = i; j < childrenCopy.size(); j++) { - if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) { - Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token(); - if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) { - childrenCopy.remove(j); - j--; - } else { - break; - } + + // Delete all multi-element paths that start with the desired path, since technically they are duplicates + if ((valueCopy == null && key.equals(desiredPath))|| (key.startsWith(desiredPath) && !key.equals(desiredPath))) { + childrenCopy.remove(i); + // Remove any whitespace or commas after the deleted setting + for (int j = i; j < childrenCopy.size(); j++) { + if (childrenCopy.get(j) instanceof ConfigNodeSingleToken) { + Token t = ((ConfigNodeSingleToken) childrenCopy.get(j)).token(); + if (Tokens.isIgnoredWhitespace(t) || t == Tokens.COMMA) { + childrenCopy.remove(j); + j--; } else { break; } + } else { + break; } } - else if (key.equals(desiredPath)){ - seenNonMatching = true; - childrenCopy.set(i, node.replaceValue(value)); - valueCopy = null; - } + } else if (key.equals(desiredPath)) { + seenNonMatching = true; + childrenCopy.set(i, node.replaceValue(value)); + valueCopy = null; } else if (desiredPath.startsWith(key)) { seenNonMatching = true; if (node.value() instanceof ConfigNodeObject) { Path remainingPath = desiredPath.subPath(key.length()); - childrenCopy.set(i, node.replaceValue(((ConfigNodeObject)node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); + childrenCopy.set(i, node.replaceValue(((ConfigNodeObject) node.value()).changeValueOnPath(remainingPath, valueCopy, flavor))); if (valueCopy != null && !node.equals(super.children.get(i))) valueCopy = null; } @@ -84,11 +81,6 @@ final class ConfigNodeObject extends ConfigNodeComplexValue { seenNonMatching = true; } } - - // Since we've removed values and valid JSON does not allow trailing commas, remove a comma after the final setting - if (flavor == ConfigSyntax.JSON) { - - } return new ConfigNodeObject(childrenCopy); } @@ -105,8 +97,8 @@ final class ConfigNodeObject extends ConfigNodeComplexValue { ConfigNodeObject node = changeValueOnPath(desiredPath.value(), value, flavor); // If the desired Path did not exist, add it - if (node.equals(this)) { - return addValueOnPath(desiredPath, value, flavor); + if (!node.hasValue(desiredPath.value())) { + return node.addValueOnPath(desiredPath, value, flavor); } return node; } diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala index 537b13cd..9aaf629a 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigDocumentTest.scala @@ -27,7 +27,7 @@ class ConfigDocumentTest extends TestUtils { } @Test - def configDocumentReplace() { + def configDocumentReplace { // Can handle parsing/replacement with a very simple map configDocumentReplaceConfTest("""{"a":1}""", """{"a":2}""", "2", "a") configDocumentReplaceJsonTest("""{"a":1}""", """{"a":2}""", "2", "a") @@ -136,6 +136,21 @@ class ConfigDocumentTest extends TestUtils { "this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}", "h.b.a") } + @Test + def configDocumentMultiElementDuplicatesRemoved { + var origText = "{a: b, a.b.c: d, a: e}" + var configDoc = ConfigDocumentFactory.parseString(origText) + assertEquals("{a: 2}", configDoc.setValue("a", "2").render()) + + origText = "{a: b, a: e, a.b.c: d}" + configDoc = ConfigDocumentFactory.parseString(origText) + assertEquals("{a: 2, }", configDoc.setValue("a", "2").render()) + + origText = "{a.b.c: d}" + configDoc = ConfigDocumentFactory.parseString(origText) + assertEquals("{\na : 2\n}", configDoc.setValue("a", "2").render()) + } + @Test def configDocumentSetNewValueBraceRoot { val origText = "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n}"