diff --git a/src/main/java/dev/openfeature/javasdk/Structure.java b/src/main/java/dev/openfeature/javasdk/Structure.java index 38ff2f89..87491e66 100644 --- a/src/main/java/dev/openfeature/javasdk/Structure.java +++ b/src/main/java/dev/openfeature/javasdk/Structure.java @@ -1,11 +1,10 @@ package dev.openfeature.javasdk; import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; +import dev.openfeature.javasdk.exceptions.ValueNotConvertableError; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -66,9 +65,9 @@ public Structure add(String key, Double value) { return this; } - /** + /** * Add date-time relevant key. - * + * * @param key feature key * @param value date-time value * @return Structure @@ -90,10 +89,73 @@ public Structure add(String key, List value) { /** * Get all values. - * + * * @return all attributes on the structure */ public Map asMap() { return new HashMap<>(this.attributes); } + + /** + * Get all values, with primitives types. + * + * @return all attributes on the structure into a Map + */ + public Map asObjectMap() { + return attributes + .entrySet() + .stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> convertValue(getValue(e.getKey())) + )); + } + + /** + * convertValue is converting the object type Value in a primitive type. + * @param value - Value object to convert + * @return an Object containing the primitive type. + */ + private Object convertValue(Value value) { + if (value.isBoolean()) { + return value.asBoolean(); + } + + if (value.isNumber()) { + Double valueAsDouble = value.asDouble(); + if (valueAsDouble == Math.floor(valueAsDouble) && !Double.isInfinite(valueAsDouble)) { + return value.asInteger(); + } + return valueAsDouble; + } + + if (value.isString()) { + return value.asString(); + } + + if (value.isInstant()) { + return value.asInstant(); + } + + if (value.isList()) { + return value.asList() + .stream() + .map(this::convertValue) + .collect(Collectors.toList()); + } + + if (value.isStructure()) { + Structure s = value.asStructure(); + return s.asMap() + .keySet() + .stream() + .collect( + Collectors.toMap( + key -> key, + key -> convertValue(s.getValue(key)) + ) + ); + } + throw new ValueNotConvertableError(); + } } diff --git a/src/main/java/dev/openfeature/javasdk/exceptions/ValueNotConvertableError.java b/src/main/java/dev/openfeature/javasdk/exceptions/ValueNotConvertableError.java new file mode 100644 index 00000000..1cbff419 --- /dev/null +++ b/src/main/java/dev/openfeature/javasdk/exceptions/ValueNotConvertableError.java @@ -0,0 +1,12 @@ +package dev.openfeature.javasdk.exceptions; + +import dev.openfeature.javasdk.ErrorCode; +import lombok.Getter; +import lombok.experimental.StandardException; + +@StandardException +public class ValueNotConvertableError extends OpenFeatureError { + private static final long serialVersionUID = 1L; + @Getter + private final ErrorCode errorCode = ErrorCode.GENERAL; +} diff --git a/src/test/java/dev/openfeature/javasdk/EvalContextTest.java b/src/test/java/dev/openfeature/javasdk/EvalContextTest.java index 29506919..3cb20788 100644 --- a/src/test/java/dev/openfeature/javasdk/EvalContextTest.java +++ b/src/test/java/dev/openfeature/javasdk/EvalContextTest.java @@ -4,6 +4,7 @@ import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -148,4 +149,55 @@ public class EvalContextTest { ctxMerged = EvaluationContext.merge(ctx1, ctx2); assertEquals(key1, ctxMerged.getTargetingKey()); } + + @Test void asObjectMap() { + String key1 = "key1"; + EvaluationContext ctx = new EvaluationContext(key1); + ctx.add("stringItem", "stringValue"); + ctx.add("boolItem", false); + ctx.add("integerItem", 1); + ctx.add("doubleItem", 1.2); + ctx.add("instantItem", Instant.ofEpochSecond(1663331342)); + List listItem = new ArrayList<>(); + listItem.add(new Value("item1")); + listItem.add(new Value("item2")); + ctx.add("listItem", listItem); + List listItem2 = new ArrayList<>(); + listItem2.add(new Value(true)); + listItem2.add(new Value(false)); + ctx.add("listItem2", listItem2); + Map structureValue = new HashMap<>(); + structureValue.put("structStringItem", new Value("stringValue")); + structureValue.put("structBoolItem", new Value(false)); + structureValue.put("structIntegerItem", new Value(1)); + structureValue.put("structDoubleItem", new Value(1.2)); + structureValue.put("structInstantItem", new Value(Instant.ofEpochSecond(1663331342))); + Structure structure = new Structure(structureValue); + ctx.add("structureItem", structure); + + + Map want = new HashMap<>(); + want.put("stringItem", "stringValue"); + want.put("boolItem", false); + want.put("integerItem", 1); + want.put("doubleItem", 1.2); + want.put("instantItem", Instant.ofEpochSecond(1663331342)); + List wantListItem = new ArrayList<>(); + wantListItem.add("item1"); + wantListItem.add("item2"); + want.put("listItem", wantListItem); + List wantListItem2 = new ArrayList<>(); + wantListItem2.add(true); + wantListItem2.add(false); + want.put("listItem2", wantListItem2); + Map wantStructureValue = new HashMap<>(); + wantStructureValue.put("structStringItem", "stringValue"); + wantStructureValue.put("structBoolItem", false); + wantStructureValue.put("structIntegerItem", 1); + wantStructureValue.put("structDoubleItem", 1.2); + wantStructureValue.put("structInstantItem", Instant.ofEpochSecond(1663331342)); + want.put("structureItem",wantStructureValue); + + assertEquals(want,ctx.asObjectMap()); + } }