diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/AttributeMap.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/AttributeMap.java index 9ca1c2d5..4fb1bee1 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/AttributeMap.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/AttributeMap.java @@ -1,9 +1,10 @@ package com.launchdarkly.sdk; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; -final class AttributeMap { +final class AttributeMap implements Serializable { private final AttributeMap parent; private final Map map; diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueBool.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueBool.java index 8f72c84e..69a45eaa 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueBool.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueBool.java @@ -38,4 +38,10 @@ public String toJsonString() { void write(JsonWriter writer) throws IOException { writer.value(value); } + + // Preserve singleton identity after Java deserialization, since equals() for booleans + // relies on TRUE and FALSE being singletons. + private Object readResolve() { + return fromBoolean(value); + } } diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueNull.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueNull.java index 1b3246da..478e645e 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueNull.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/LDValueNull.java @@ -26,4 +26,9 @@ public String toJsonString() { void write(JsonWriter writer) throws IOException { writer.nullValue(); } + + // Preserve singleton identity after Java deserialization. + private Object readResolve() { + return INSTANCE; + } } diff --git a/lib/shared/common/src/main/java/com/launchdarkly/sdk/json/JsonSerializable.java b/lib/shared/common/src/main/java/com/launchdarkly/sdk/json/JsonSerializable.java index 09ba91c7..dfdf405f 100644 --- a/lib/shared/common/src/main/java/com/launchdarkly/sdk/json/JsonSerializable.java +++ b/lib/shared/common/src/main/java/com/launchdarkly/sdk/json/JsonSerializable.java @@ -1,10 +1,12 @@ package com.launchdarkly.sdk.json; +import java.io.Serializable; + /** * Marker interface for SDK classes that have a custom JSON serialization. * * @see JsonSerialization * @see LDGson */ -public interface JsonSerializable { +public interface JsonSerializable extends Serializable { } diff --git a/lib/shared/common/src/test/java/com/launchdarkly/sdk/LDContextTest.java b/lib/shared/common/src/test/java/com/launchdarkly/sdk/LDContextTest.java index de85237d..6eb9707f 100644 --- a/lib/shared/common/src/test/java/com/launchdarkly/sdk/LDContextTest.java +++ b/lib/shared/common/src/test/java/com/launchdarkly/sdk/LDContextTest.java @@ -4,6 +4,10 @@ import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; @@ -437,4 +441,40 @@ public void contextFromUserErrors() { assertThat(c2.isValid(), is(false)); assertThat(c2.getError(), equalTo(Errors.CONTEXT_NO_KEY)); } + + @Test + public void javaSerializationRoundTripSingleKind() throws Exception { + LDContext c = LDContext.builder(kind1, "my-key") + .name("my-name") + .anonymous(true) + .set("email", "x@example.com") + .set("happy", true) + .set("count", 3) + .set("tags", LDValue.buildArray().add("a").add("b").build()) + .set("nested", LDValue.buildObject().put("inner", "v").build()) + .privateAttributes("email") + .build(); + + assertThat(javaSerializeAndDeserialize(c), equalTo(c)); + } + + @Test + public void javaSerializationRoundTripMultiKind() throws Exception { + LDContext c = LDContext.createMulti( + LDContext.builder(kind1, "key1").set("a", "b").build(), + LDContext.builder(kind2, "key2").set("c", LDValue.buildArray().add(1).add(2).build()).build() + ); + + assertThat(javaSerializeAndDeserialize(c), equalTo(c)); + } + + private static LDContext javaSerializeAndDeserialize(LDContext c) throws Exception { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(bytes)) { + out.writeObject(c); + } + try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) { + return (LDContext) in.readObject(); + } + } }