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();
Token valueToken = nextTokenIgnoringNewline();
AbstractConfigValue newValue = parseValue(valueToken);
// note how we handle duplicate keys: the last one just
// wins.
// FIXME in strict JSON, dups should be an error; while in
// In strict JSON, dups should be an error; while in
// our custom config language, they should be merged if the
// value is an object.
values.put(key, parseValue(valueToken));
// value is an object (or substitution that could become
// 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;
} else if (t == Tokens.CLOSE_CURLY) {

View File

@ -116,4 +116,48 @@ class ConfParserTest extends TestUtils {
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", "woo" : "w00t" }, "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}}}}}}}}""",
@ -160,6 +159,7 @@ abstract trait TestUtils {
private val validConfInvalidJson = List[ParseTest](
"""{ "foo" : bar }""", // no quotes on value
"""{ "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
"[ t ]", // start of "true" but ends wrong in JSON
"[ tx ]",