diff --git a/paper-api/src/main/java/org/bukkit/configuration/MemorySection.java b/paper-api/src/main/java/org/bukkit/configuration/MemorySection.java index fe7762b989..3da7da3486 100644 --- a/paper-api/src/main/java/org/bukkit/configuration/MemorySection.java +++ b/paper-api/src/main/java/org/bukkit/configuration/MemorySection.java @@ -742,7 +742,12 @@ public class MemorySection implements ConfigurationSection { MemorySection sec = (MemorySection) section; for (Map.Entry entry : sec.map.entrySet()) { - output.put(createPath(section, entry.getKey(), this), entry.getValue()); + // Because of the copyDefaults call potentially copying out of order, we must remove and then add in our saved order + // This means that default values we haven't set end up getting placed first + // See SPIGOT-4558 for an example using spigot.yml - watch subsections move around to default order + String childPath = createPath(section, entry.getKey(), this); + output.remove(childPath); + output.put(childPath, entry.getValue()); if (entry.getValue() instanceof ConfigurationSection) { if (deep) { diff --git a/paper-api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java b/paper-api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java index 56a34f0b96..462fc0a71e 100644 --- a/paper-api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java +++ b/paper-api/src/test/java/org/bukkit/configuration/ConfigurationSectionTest.java @@ -69,18 +69,21 @@ public abstract class ConfigurationSectionTest { ConfigurationSection section = getConfigurationSection(); section.getRoot().options().copyDefaults(true); + // Fix for SPIGOT-4558 means that defaults will always be first + // This is a little bit unintuitive for section defaults when deep iterating keys / values as shown below + // But the API doesn't guarantee order & when serialized (using shallow getters) all is well section.set("bool", true); section.set("subsection.string", "test"); section.addDefault("subsection.long", Long.MAX_VALUE); section.addDefault("int", 42); Map shallowValues = section.getValues(false); - assertArrayEquals(new String[] { "subsection", "int", "bool" }, shallowValues.keySet().toArray()); - assertArrayEquals(new Object[] { section.getConfigurationSection("subsection"), 42, true }, shallowValues.values().toArray()); + assertArrayEquals(new String[] { "int", "bool", "subsection" }, shallowValues.keySet().toArray()); + assertArrayEquals(new Object[] { 42, true, section.getConfigurationSection("subsection") }, shallowValues.values().toArray()); Map deepValues = section.getValues(true); - assertArrayEquals(new String[] { "subsection", "subsection.long", "int", "bool", "subsection.string" }, deepValues.keySet().toArray()); - assertArrayEquals(new Object[] { section.getConfigurationSection("subsection"), Long.MAX_VALUE, 42, true, "test" }, deepValues.values().toArray()); + assertArrayEquals(new String[] { "subsection.long", "int", "bool", "subsection", "subsection.string" }, deepValues.keySet().toArray()); + assertArrayEquals(new Object[] { Long.MAX_VALUE, 42, true, section.getConfigurationSection("subsection"), "test" }, deepValues.values().toArray()); } @Test