Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
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;
Copy link
Contributor

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 Gson is available. I see two options:

  1. We mimic the approach taken in DefaultConfigParser
  2. Take a stance on the serializer and include as a shadow dependency

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

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;
}

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;
}

}

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);
}
}