Skip to content

Commit d1576fb

Browse files
committed
json
1 parent 4cfc64b commit d1576fb

File tree

4 files changed

+191
-9
lines changed

4 files changed

+191
-9
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ f.<MyCustomClass>getVariableObject(featureKey, variableKey, context);
303303

304304
f.<Map<String, Object>>getVariableJSON(featureKey, variableKey, context);
305305
f.<MyCustomClass>getVariableJSON(featureKey, variableKey, context);
306+
f.getVariableJSONNode(featureKey, variableKey, context);
306307
```
307308

308309
For strongly typed decoding, additional overloads are available:
@@ -335,6 +336,24 @@ Map<String, List<MyConfig>> nested = f.getVariableObject(
335336

336337
Typed overloads are additive and non-breaking. If decoding fails for the requested target type, these methods return `null`.
337338

339+
For dynamic JSON values with unknown shape, use `getVariableJSONNode`:
340+
341+
```java
342+
import com.fasterxml.jackson.databind.JsonNode;
343+
344+
JsonNode node = f.getVariableJSONNode(featureKey, variableKey, context);
345+
346+
if (node != null && node.isObject()) {
347+
String nested = node.path("key").path("nested").asText(null);
348+
}
349+
```
350+
351+
If a variable schema type is `json` and the resolved value is a malformed stringified JSON, JSON parsing fails safely and these methods return `null`:
352+
353+
- `getVariable(...)`
354+
- `getVariableJSONNode(...)`
355+
- `getVariableJSON(...)`
356+
338357
## Getting all evaluations
339358

340359
You can get evaluations of all features available in the SDK instance:
@@ -720,6 +739,7 @@ Similar to parent SDK, child instances also support several additional methods:
720739
- `getVariableArray`
721740
- `getVariableObject`
722741
- `getVariableJSON`
742+
- `getVariableJSONNode`
723743
- `getAllEvaluations`
724744
- `on`
725745
- `close`

src/main/java/com/featurevisor/sdk/ChildInstance.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.featurevisor.sdk;
22

33
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.JsonNode;
45
import java.util.Map;
56
import java.util.HashMap;
67
import java.util.List;
@@ -394,6 +395,28 @@ public <T> T getVariableJSON(String featureKey, String variableKey) {
394395
return getVariableJSON(featureKey, variableKey, null, null);
395396
}
396397

398+
public JsonNode getVariableJSONNode(
399+
String featureKey,
400+
String variableKey,
401+
Map<String, Object> context,
402+
Featurevisor.OverrideOptions options
403+
) {
404+
return this.parent.getVariableJSONNode(
405+
featureKey,
406+
variableKey,
407+
mergeContexts(this.context, context),
408+
mergeOverrideOptions(options)
409+
);
410+
}
411+
412+
public JsonNode getVariableJSONNode(String featureKey, String variableKey, Map<String, Object> context) {
413+
return getVariableJSONNode(featureKey, variableKey, context, null);
414+
}
415+
416+
public JsonNode getVariableJSONNode(String featureKey, String variableKey) {
417+
return getVariableJSONNode(featureKey, variableKey, null, null);
418+
}
419+
397420
/**
398421
* Get all evaluations
399422
*/

src/main/java/com/featurevisor/sdk/Featurevisor.java

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.featurevisor.types.EvaluatedFeatures;
77
import com.featurevisor.types.VariableType;
88
import com.fasterxml.jackson.core.type.TypeReference;
9+
import com.fasterxml.jackson.databind.JsonNode;
910
import com.fasterxml.jackson.databind.ObjectMapper;
1011
import java.util.Map;
1112
import java.util.HashMap;
@@ -17,6 +18,8 @@
1718
* Provides the primary interface for feature flag evaluation and factory methods
1819
*/
1920
public class Featurevisor {
21+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
22+
2023
// from options
2124
private Map<String, Object> context = new HashMap<>();
2225
private Logger logger;
@@ -531,10 +534,9 @@ public Object getVariable(String featureKey, String variableKey, Map<String, Obj
531534
evaluation.getVariableSchema().getType() == VariableType.JSON;
532535
if (isJsonType) {
533536
try {
534-
ObjectMapper mapper = new ObjectMapper();
535-
return mapper.readValue(strValue, Object.class);
537+
return OBJECT_MAPPER.readValue(strValue, Object.class);
536538
} catch (Exception e) {
537-
// fallback to string
539+
return null;
538540
}
539541
}
540542
}
@@ -650,6 +652,27 @@ public <T> T getVariableJSON(String featureKey, String variableKey) {
650652
return getVariableJSON(featureKey, variableKey, null, null);
651653
}
652654

655+
public JsonNode getVariableJSONNode(String featureKey, String variableKey, Map<String, Object> context, OverrideOptions options) {
656+
Object variableValue = getVariable(featureKey, variableKey, context, options);
657+
if (variableValue == null) {
658+
return null;
659+
}
660+
661+
if (variableValue instanceof JsonNode) {
662+
return (JsonNode) variableValue;
663+
}
664+
665+
return OBJECT_MAPPER.valueToTree(variableValue);
666+
}
667+
668+
public JsonNode getVariableJSONNode(String featureKey, String variableKey, Map<String, Object> context) {
669+
return getVariableJSONNode(featureKey, variableKey, context, null);
670+
}
671+
672+
public JsonNode getVariableJSONNode(String featureKey, String variableKey) {
673+
return getVariableJSONNode(featureKey, variableKey, null, null);
674+
}
675+
653676
public <T> List<T> getVariableArray(
654677
String featureKey,
655678
String variableKey,
@@ -664,10 +687,9 @@ public <T> List<T> getVariableArray(
664687
}
665688

666689
try {
667-
ObjectMapper mapper = new ObjectMapper();
668-
return mapper.convertValue(
690+
return OBJECT_MAPPER.convertValue(
669691
arrayValue,
670-
mapper.getTypeFactory().constructCollectionType(List.class, itemType)
692+
OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, itemType)
671693
);
672694
} catch (IllegalArgumentException e) {
673695
return null;
@@ -701,7 +723,7 @@ public <T> T getVariableArray(
701723
}
702724

703725
try {
704-
return new ObjectMapper().convertValue(arrayValue, typeRef);
726+
return OBJECT_MAPPER.convertValue(arrayValue, typeRef);
705727
} catch (IllegalArgumentException e) {
706728
return null;
707729
}
@@ -734,7 +756,7 @@ public <T> T getVariableObject(
734756
}
735757

736758
try {
737-
return new ObjectMapper().convertValue(objectValue, type);
759+
return OBJECT_MAPPER.convertValue(objectValue, type);
738760
} catch (IllegalArgumentException e) {
739761
return null;
740762
}
@@ -767,7 +789,7 @@ public <T> T getVariableObject(
767789
}
768790

769791
try {
770-
return new ObjectMapper().convertValue(objectValue, typeRef);
792+
return OBJECT_MAPPER.convertValue(objectValue, typeRef);
771793
} catch (IllegalArgumentException e) {
772794
return null;
773795
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.featurevisor.sdk;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.featurevisor.types.DatafileContent;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
import static org.junit.jupiter.api.Assertions.assertNull;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
public class JsonNodeVariableMethodsTest {
16+
17+
private static final String DATAFILE_JSON = """
18+
{
19+
"schemaVersion": "2",
20+
"revision": "json-node-test",
21+
"segments": {},
22+
"features": {
23+
"json_feature": {
24+
"key": "json_feature",
25+
"bucketBy": "userId",
26+
"variablesSchema": {
27+
"jsonObject": {
28+
"type": "json",
29+
"defaultValue": {}
30+
},
31+
"jsonArray": {
32+
"type": "json",
33+
"defaultValue": []
34+
},
35+
"malformedJson": {
36+
"type": "json",
37+
"defaultValue": {}
38+
},
39+
"regularString": {
40+
"type": "string",
41+
"defaultValue": "default-value"
42+
}
43+
},
44+
"traffic": [
45+
{
46+
"key": "rule-1",
47+
"segments": "*",
48+
"percentage": 100000,
49+
"variables": {
50+
"jsonObject": "{\\"a\\":1,\\"nested\\":{\\"b\\":\\"x\\"}}",
51+
"jsonArray": "[{\\"id\\":1},2,\\"x\\"]",
52+
"malformedJson": "{ invalid json",
53+
"regularString": "plain-value"
54+
}
55+
}
56+
]
57+
}
58+
}
59+
}
60+
""";
61+
62+
@Test
63+
public void testJsonNodeMethodsAndMalformedJsonHandling() throws Exception {
64+
Featurevisor sdk = Featurevisor.createInstance(
65+
new Featurevisor.Options().datafile(DatafileContent.fromJson(DATAFILE_JSON))
66+
);
67+
Map<String, Object> context = Map.of("userId", "123");
68+
69+
JsonNode objectNode = sdk.getVariableJSONNode("json_feature", "jsonObject", context);
70+
assertNotNull(objectNode);
71+
assertTrue(objectNode.isObject());
72+
assertEquals(1, objectNode.get("a").asInt());
73+
assertEquals("x", objectNode.get("nested").get("b").asText());
74+
75+
JsonNode arrayNode = sdk.getVariableJSONNode("json_feature", "jsonArray", context);
76+
assertNotNull(arrayNode);
77+
assertTrue(arrayNode.isArray());
78+
assertEquals(1, arrayNode.get(0).get("id").asInt());
79+
assertEquals(2, arrayNode.get(1).asInt());
80+
assertEquals("x", arrayNode.get(2).asText());
81+
82+
Map<String, Object> jsonAsMap = sdk.getVariableJSON("json_feature", "jsonObject", context);
83+
assertNotNull(jsonAsMap);
84+
assertEquals(1, ((Number) jsonAsMap.get("a")).intValue());
85+
assertEquals("x", ((Map<String, Object>) jsonAsMap.get("nested")).get("b"));
86+
87+
assertNull(sdk.getVariable("json_feature", "malformedJson", context));
88+
assertNull(sdk.getVariableJSONNode("json_feature", "malformedJson", context));
89+
assertNull(sdk.getVariableJSON("json_feature", "malformedJson", context));
90+
91+
assertEquals("plain-value", sdk.getVariable("json_feature", "regularString", context));
92+
assertEquals("plain-value", sdk.getVariableString("json_feature", "regularString", context));
93+
}
94+
95+
@Test
96+
public void testChildInstanceJsonNodeParity() throws Exception {
97+
Featurevisor sdk = Featurevisor.createInstance(
98+
new Featurevisor.Options().datafile(DatafileContent.fromJson(DATAFILE_JSON))
99+
);
100+
ChildInstance child = sdk.spawn(Map.of("userId", "123"));
101+
102+
JsonNode objectNode = child.getVariableJSONNode("json_feature", "jsonObject");
103+
assertNotNull(objectNode);
104+
assertEquals("x", objectNode.get("nested").get("b").asText());
105+
106+
JsonNode arrayNode = child.getVariableJSONNode("json_feature", "jsonArray");
107+
assertNotNull(arrayNode);
108+
assertEquals(3, arrayNode.size());
109+
110+
assertNull(child.getVariable("json_feature", "malformedJson"));
111+
assertNull(child.getVariableJSONNode("json_feature", "malformedJson"));
112+
assertNull(child.getVariableJSON("json_feature", "malformedJson"));
113+
114+
List<String> none = child.getVariableArray("json_feature", "jsonObject");
115+
assertNull(none);
116+
}
117+
}

0 commit comments

Comments
 (0)