diff --git a/src/main/java/com/github/rschmitt/dynamicobject/FressianWriteHandler.java b/src/main/java/com/github/rschmitt/dynamicobject/FressianWriteHandler.java index e7a29b5..dd05d9a 100644 --- a/src/main/java/com/github/rschmitt/dynamicobject/FressianWriteHandler.java +++ b/src/main/java/com/github/rschmitt/dynamicobject/FressianWriteHandler.java @@ -1,5 +1,6 @@ package com.github.rschmitt.dynamicobject; +import clojure.lang.PersistentHashMap; import org.fressian.CachedObject; import org.fressian.Writer; import org.fressian.handlers.WriteHandler; @@ -73,7 +74,14 @@ private TransformedMap( Function keysTransformation, BiFunction valuesTransformation ) { - this.backingMap = backingMap; + if (backingMap.size() == 8) { + // If the backing map has 8 fields, Clojure Fressian deserializes it to a PersistentHashMap + // which means serializing -> deserializing -> serializing will yield a different value from the + // first serialization unless we serialize it as a PersistentHashMap here. + this.backingMap = (Map) PersistentHashMap.create(backingMap); + } else { + this.backingMap = backingMap; + } this.keysTransformation = keysTransformation; this.valuesTransformation = valuesTransformation; } diff --git a/src/test/java/com/github/rschmitt/dynamicobject/FressianTest.java b/src/test/java/com/github/rschmitt/dynamicobject/FressianTest.java index 806c2d5..cb8ffa7 100644 --- a/src/test/java/com/github/rschmitt/dynamicobject/FressianTest.java +++ b/src/test/java/com/github/rschmitt/dynamicobject/FressianTest.java @@ -1,5 +1,6 @@ package com.github.rschmitt.dynamicobject; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -51,6 +52,30 @@ public void formatCompatibilityTest() throws Exception { assertEquals(SAMPLE_VALUE, deserialized); } + @Test + public void repeatedSerializationYieldsSameResultWith8Fields() throws Exception { + // This only surfaces with 8 fields, because Clojure Fressian deserializes maps with 8 keys + // as a PersistentHashMap, when it should deserialize it as a PersistentArrayMap. + + DynamicObject.registerTag(DOWith8Fields.class, "doWith8Fields"); + DOWith8Fields initialDO = DynamicObject.newInstance(DOWith8Fields.class) + .withField1("a") + .withField2("b") + .withField3("c") + .withField4("d") + .withField5("e") + .withField6("f") + .withField7("g") + .withField8("h"); + + byte[] serialized = DynamicObject.toFressianByteArray(initialDO); + DOWith8Fields deserialized = DynamicObject.fromFressianByteArray(serialized); + + byte[] reserialized = DynamicObject.toFressianByteArray(deserialized); + + assertArrayEquals(serialized, reserialized); + } + @Test public void cachedKeys_canBeRoundTripped() throws Exception { String cachedValue = "cached value"; @@ -93,4 +118,15 @@ public interface BinarySerialized extends DynamicObject { @Key(":null") BinarySerialized withNull(Object nil); @Cached @Key(":cached") BinarySerialized withCached(String cached); } + + public interface DOWith8Fields extends DynamicObject { + @Key(":field1") DOWith8Fields withField1(String field1); + @Key(":field2") DOWith8Fields withField2(String field2); + @Key(":field3") DOWith8Fields withField3(String field3); + @Key(":field4") DOWith8Fields withField4(String field4); + @Key(":field5") DOWith8Fields withField5(String field5); + @Key(":field6") DOWith8Fields withField6(String field6); + @Key(":field7") DOWith8Fields withField7(String field7); + @Key(":field8") DOWith8Fields withField8(String field8); + } }