- Notifications
You must be signed in to change notification settings - Fork 32
feature: add OptimizelyJSON #371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
26 commits Select commit Hold shift + click to select a range
032c270 add OptimizelyJSON
jaeopt 99014d2 clean up
jaeopt 9de2535 add doc comments
jaeopt 641d215 Merge branch 'master' into jae/optimizely-json
jaeopt 69144a0 add more tests
jaeopt f319fdc fix test
jaeopt 096bcb9 (wip) add multiple parser support to OptimizelyJSON
jaeopt 5be5f3f split getvalue tests
jaeopt 01970f5 disable getValue for legacy parsers
jaeopt 2401784 add legacy parser tests
jaeopt afdb8be fix tests for all parsers
jaeopt 2952834 Merge branch 'master' into jae/optimizely-json
jaeopt 24ed0d6 cleanup
jaeopt 5d45085 fix findbugs issues
jaeopt 2af6f14 cleanup
jaeopt b5db429 fix test per review
jaeopt b6fc66c fix JsonSimple parser
jaeopt ef7963b add unit tests for all parsers
jaeopt 880111b add unit tests for JsonHelpers
jaeopt 14ace05 fix test types
jaeopt f0df4e2 refact json parser tests
jaeopt ceddf49 cleanup parser tests
jaeopt 37efc58 cleanup
jaeopt 8cd3b55 change to parameterized tests
jaeopt c92459f fix merge conflict
jaeopt e5d792a fix getValue destroy test
jaeopt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
129 changes: 129 additions & 0 deletions 129 core-api/src/main/java/com/optimizely/ab/optimizelyjson/OptimizelyJSON.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| /** | ||
| * | ||
| * Copyright 2020, Optimizely and contributors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package com.optimizely.ab.optimizelyjson; | ||
| | ||
| import com.google.gson.Gson; | ||
| import com.google.gson.GsonBuilder; | ||
| import com.google.gson.JsonSyntaxException; | ||
| import com.optimizely.ab.Optimizely; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| | ||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.Nullable; | ||
| import java.io.IOException; | ||
| import java.util.Map; | ||
| | ||
| public class OptimizelyJSON { | ||
| @Nullable | ||
| private String payload; | ||
| @Nullable | ||
| private Map<String,Object> map; | ||
| | ||
| private static final Logger logger = LoggerFactory.getLogger(OptimizelyJSON.class); | ||
| | ||
| public OptimizelyJSON(@Nonnull String payload) { | ||
| this.payload = payload; | ||
| } | ||
| | ||
| public OptimizelyJSON(@Nonnull Map<String,Object> map) { | ||
| this.map = map; | ||
| } | ||
| | ||
| public String toString() { | ||
| if (payload == null) { | ||
| if (map == null) return null; | ||
| | ||
| try { | ||
| Gson gson = new Gson(); | ||
| payload = gson.toJson(map); | ||
| } catch (JsonSyntaxException e) { | ||
| logger.error("Provided dictionary could not be converted to string."); | ||
| } | ||
| } | ||
| | ||
| return payload; | ||
| } | ||
| | ||
| public Map<String,Object> toMap() { | ||
| if (map == null) { | ||
| if (payload == null) return null; | ||
| | ||
| try { | ||
| Gson gson = new Gson(); | ||
| map = gson.fromJson(payload, Map.class); | ||
| } catch (JsonSyntaxException e) { | ||
| logger.error("Provided string could not be converted to dictionary."); | ||
| } | ||
| } | ||
| | ||
| return map; | ||
| } | ||
| | ||
| public <T> T getValue(@Nullable String jsonKey, Class<T> clazz) { | ||
| Map<String,Object> subMap = toMap(); | ||
| T result = null; | ||
| | ||
| if (jsonKey == null || jsonKey.isEmpty()) { | ||
| return getValueInternal(subMap, clazz); | ||
| } | ||
| | ||
| String[] keys = jsonKey.split("\\."); | ||
| | ||
| for(int i=0; i<keys.length; i++) { | ||
| if (subMap == null) break; | ||
| | ||
| String key = keys[i]; | ||
| if (key.isEmpty()) break; | ||
| | ||
| if (i == keys.length - 1) { | ||
| result = getValueInternal(subMap.get(key), clazz); | ||
| break; | ||
| } | ||
| | ||
| if (subMap.get(key) instanceof Map) { | ||
| subMap = (Map<String, Object>) subMap.get(key); | ||
| } else { | ||
| logger.error("Value for JSON key ({}) not found.", jsonKey); | ||
| break; | ||
| } | ||
| } | ||
| | ||
| if (result == null) { | ||
| logger.error("Value for path could not be assigned to provided schema."); | ||
| } | ||
| return result; | ||
jaeopt marked this conversation as resolved. Show resolved Hide resolved | ||
| } | ||
| | ||
| private <T> T getValueInternal(@Nullable Object object, Class<T> clazz) { | ||
| if (object == null) return null; | ||
| | ||
| if (clazz.isInstance(object)) return (T)object; // primitive (String, Boolean, Integer, Double) | ||
| | ||
| try { | ||
| Gson gson = new Gson(); | ||
| String payload = gson.toJson(object); | ||
| return gson.fromJson(payload, clazz); | ||
| } catch (Exception e) { | ||
| // | ||
| } | ||
| | ||
| return null; | ||
| } | ||
| | ||
| } | ||
| | ||
212 changes: 212 additions & 0 deletions 212 core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| package com.optimizely.ab.optimizelyjson; | ||
| | ||
| import com.google.gson.annotations.SerializedName; | ||
| import com.optimizely.ab.Optimizely; | ||
| import com.optimizely.ab.config.DatafileProjectConfig; | ||
| import com.optimizely.ab.config.PollingProjectConfigManagerTest; | ||
| import com.sun.org.apache.xpath.internal.operations.Bool; | ||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
| | ||
| import java.util.*; | ||
| | ||
| import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV4; | ||
| import static org.junit.Assert.*; | ||
| | ||
| public class OptimizelyJSONTest { | ||
| | ||
| private String orgJson; | ||
| private Map<String,Object> orgMap; | ||
| | ||
| @Before | ||
| public void setUp() throws Exception { | ||
| orgJson = | ||
| "{ " + | ||
| " \"k1\": \"v1\", " + | ||
| " \"k2\": true, " + | ||
| " \"k3\": { " + | ||
| " \"kk1\": 1.5, " + | ||
| " \"kk2\": { " + | ||
| " \"kkk1\": true, " + | ||
| " \"kkk2\": 3.0, " + | ||
| " \"kkk3\": \"vvv3\", " + | ||
| " \"kkk4\": [5.7, true, \"vvv4\"] " + | ||
| " } " + | ||
| " } " + | ||
| "} "; | ||
| | ||
| Map<String,Object> m3 = new HashMap<String,Object>(); | ||
| m3.put("kkk1", true); | ||
| m3.put("kkk2", 3.0); | ||
| m3.put("kkk3", "vvv3"); | ||
| m3.put("kkk4", new ArrayList(Arrays.asList(5.7, true, "vvv4"))); | ||
| | ||
| Map<String,Object> m2 = new HashMap<String,Object>(); | ||
| m2.put("kk1", 1.5); | ||
| m2.put("kk2", m3); | ||
| | ||
| Map<String,Object> m1 = new HashMap<String, Object>(); | ||
| m1.put("k1", "v1"); | ||
| m1.put("k2", true); | ||
| m1.put("k3", m2); | ||
| | ||
| orgMap = m1; | ||
| } | ||
| | ||
| public class MD1 { | ||
| String k1; | ||
| boolean k2; | ||
| MD2 k3; | ||
| } | ||
| | ||
| public class MD2 { | ||
| double kk1; | ||
| MD3 kk2; | ||
| } | ||
| | ||
| public class MD3 { | ||
| boolean kkk1; | ||
| int kkk2; | ||
| String kkk3; | ||
| Object[] kkk4; | ||
| } | ||
| | ||
| public class NotMatchingType { | ||
| String x99; | ||
| } | ||
| | ||
| private String compact(String str) { | ||
| return str.replaceAll("\\s", ""); | ||
| } | ||
| | ||
| @Test | ||
| public void testOptimizelyJSON() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| Map<String,Object> map = oj1.toMap(); | ||
| | ||
| OptimizelyJSON oj2 = new OptimizelyJSON(map); | ||
| String data = oj2.toString(); | ||
| | ||
| assertEquals(compact(data), compact(orgJson)); | ||
| } | ||
| | ||
| @Test | ||
| public void testToStringFromString() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| assertEquals(compact(oj1.toString()), compact(orgJson)); | ||
| } | ||
| | ||
| @Test | ||
| public void testToStringFromMap() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgMap); | ||
| assertEquals(compact(oj1.toString()), compact(orgJson)); | ||
| } | ||
| | ||
| @Test | ||
| public void testToMapFromString() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| assertEquals(oj1.toMap(), orgMap); | ||
| } | ||
| | ||
| @Test | ||
| public void testToMapFromMap() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgMap); | ||
| assertEquals(oj1.toMap(), orgMap); | ||
| } | ||
| | ||
| | ||
| @Test | ||
| public void testGetValueNullKeyPath() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| MD1 md1 = oj1.getValue(null, MD1.class); | ||
| assertNotNull(md1); | ||
| assertEquals(md1.k1, "v1"); | ||
| assertEquals(md1.k2, true); | ||
| assertEquals(md1.k3.kk2.kkk1, true); | ||
| assertEquals(md1.k3.kk2.kkk4[0], 5.7); | ||
| assertEquals(md1.k3.kk2.kkk4[2], "vvv4"); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueEmptyKeyPath() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| MD1 md1 = oj1.getValue("", MD1.class); | ||
| assertEquals(md1.k1, "v1"); | ||
| assertEquals(md1.k2, true); | ||
| assertEquals(md1.k3.kk2.kkk1, true); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithKeyPathToMapWithLevel1() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| MD2 md2 = oj1.getValue("k3", MD2.class); | ||
| assertNotNull(md2); | ||
| assertEquals(md2.kk1, 1.5, 0.01); | ||
| assertEquals(md2.kk2.kkk1, true); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithKeyPathToMapWithLevel2() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| MD3 md3 = oj1.getValue("k3.kk2", MD3.class); | ||
| assertNotNull(md3); | ||
| assertEquals(md3.kkk1, true); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithKeyPathToBoolean() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| Boolean value = oj1.getValue("k3.kk2.kkk1", Boolean.class); | ||
| assertNotNull(value); | ||
| assertEquals(value, true); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithKeyPathToDouble() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| Double value = oj1.getValue("k3.kk2.kkk2", Double.class); | ||
| assertNotNull(value); | ||
| assertEquals(value.doubleValue(), 3.0, 0.01); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithKeyPathToString() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| String value = oj1.getValue("k3.kk2.kkk3", String.class); | ||
| assertNotNull(value); | ||
| assertEquals(value, "vvv3"); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithInvalidKeyPath() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| Integer value = oj1.getValue("x9", Integer.class); | ||
| assertNull(value); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithWrongType() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| Integer value = oj1.getValue("k3.kk2.kkk3", Integer.class); | ||
| assertNull(value); | ||
| } | ||
| | ||
| @Test | ||
| public void testGetValueWithNotMatchingType() { | ||
| OptimizelyJSON oj1 = new OptimizelyJSON(orgJson); | ||
| | ||
| NotMatchingType md = oj1.getValue(null, NotMatchingType.class); | ||
| assertNull(md.x99); | ||
| } | ||
| } | ||
| | ||
| |
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't assume that
Gsonis available. I see two options:Option 1 is the safer approach, since it has some precedence, but adds SDK complexity.
Option 2 is preferred but has a larger impact to the customer (my main concern is mobile).
cc @mikeng13