allow duplicate keys in .conf with later key winning

This commit is contained in:
Havoc Pennington 2011-11-12 00:24:22 -05:00
parent df3b3792da
commit a3128871ac
3 changed files with 62 additions and 6 deletions

View File

@ -290,13 +290,25 @@ final class Parser {
consolidateValueTokens(); consolidateValueTokens();
Token valueToken = nextTokenIgnoringNewline(); Token valueToken = nextTokenIgnoringNewline();
AbstractConfigValue newValue = parseValue(valueToken);
// note how we handle duplicate keys: the last one just // In strict JSON, dups should be an error; while in
// wins.
// FIXME in strict JSON, dups should be an error; while in
// our custom config language, they should be merged if the // our custom config language, they should be merged if the
// value is an object. // value is an object (or substitution that could become
values.put(key, parseValue(valueToken)); // an object).
AbstractConfigValue existing = values.get(key);
if (existing != null) {
if (flavor == SyntaxFlavor.JSON) {
throw parseError("JSON does not allow duplicate fields: '"
+ key
+ "' was already seen at "
+ existing.origin().description());
} else {
newValue = newValue.withFallback(existing);
}
}
values.put(key, newValue);
afterComma = false; afterComma = false;
} else if (t == Tokens.CLOSE_CURLY) { } else if (t == Tokens.CLOSE_CURLY) {

View File

@ -116,4 +116,48 @@ class ConfParserTest extends TestUtils {
parsePath("-") parsePath("-")
} }
} }
@Test
def duplicateKeyLastWins() {
val obj = parseObject("""{ "a" : 10, "a" : 11 } """)
assertEquals(1, obj.size())
assertEquals(11, obj.getInt("a"))
}
@Test
def duplicateKeyObjectsMerged() {
val obj = parseObject("""{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }""")
assertEquals(1, obj.size())
assertEquals(3, obj.getObject("a").size())
assertEquals(42, obj.getInt("a.x"))
assertEquals(2, obj.getInt("a.y"))
assertEquals(100, obj.getInt("a.z"))
}
@Test
def duplicateKeyObjectsMergedRecursively() {
val obj = parseObject("""{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }""")
assertEquals(1, obj.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(3, obj.getObject("a.b").size())
assertEquals(42, obj.getInt("a.b.x"))
assertEquals(2, obj.getInt("a.b.y"))
assertEquals(100, obj.getInt("a.b.z"))
}
@Test
def duplicateKeyObjectsMergedRecursivelyDeeper() {
val obj = parseObject("""{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }""")
assertEquals(1, obj.size())
assertEquals(1, obj.getObject("a").size())
assertEquals(1, obj.getObject("a.b").size())
assertEquals(3, obj.getObject("a.b.c").size())
assertEquals(42, obj.getInt("a.b.c.x"))
assertEquals(2, obj.getInt("a.b.c.y"))
assertEquals(100, obj.getInt("a.b.c.z"))
}
} }

View File

@ -149,7 +149,6 @@ abstract trait TestUtils {
"""{ "foo" : { "bar" : "baz" }, "baz" : "boo" }""", """{ "foo" : { "bar" : "baz" }, "baz" : "boo" }""",
"""{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : "boo" }""", """{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : "boo" }""",
"""{ "foo" : [10,11,12], "baz" : "boo" }""", """{ "foo" : [10,11,12], "baz" : "boo" }""",
ParseTest(true, """{ "foo" : "bar", "foo" : "bar2" }"""), // dup keys - lift just returns both, we use last one
"""[{},{},{},{}]""", """[{},{},{},{}]""",
"""[[[[[[]]]]]]""", """[[[[[[]]]]]]""",
"""{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":42}}}}}}}}""", """{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":42}}}}}}}}""",
@ -160,6 +159,7 @@ abstract trait TestUtils {
private val validConfInvalidJson = List[ParseTest]( private val validConfInvalidJson = List[ParseTest](
"""{ "foo" : bar }""", // no quotes on value """{ "foo" : bar }""", // no quotes on value
"""{ "foo" : null bar 42 baz true 3.14 "hi" }""", // bunch of values to concat into a string """{ "foo" : null bar 42 baz true 3.14 "hi" }""", // bunch of values to concat into a string
ParseTest(true, """{ "foo" : "bar", "foo" : "bar2" }"""), // dup keys - lift just returns both
"[ foo ]", // not a known token in JSON "[ foo ]", // not a known token in JSON
"[ t ]", // start of "true" but ends wrong in JSON "[ t ]", // start of "true" but ends wrong in JSON
"[ tx ]", "[ tx ]",