diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java index eb24b68f3..c8fe74b08 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java @@ -38,4 +38,10 @@ public interface ConfigParser { * @throws ConfigParseException when there's an issue parsing the provided project config */ ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException; + + /** + * OptimizelyJSON parsing + */ + String toJson(Object src) throws JsonParseException; + T fromJson(String json, Class clazz) throws JsonParseException; } diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java index d86f72140..972d76431 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019-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. @@ -27,7 +27,23 @@ /** * {@link Gson}-based config parser implementation. */ -final class GsonConfigParser implements ConfigParser { +final public class GsonConfigParser implements ConfigParser { + private Gson gson; + + public GsonConfigParser() { + this(new GsonBuilder() + .registerTypeAdapter(Audience.class, new AudienceGsonDeserializer()) + .registerTypeAdapter(TypedAudience.class, new AudienceGsonDeserializer()) + .registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer()) + .registerTypeAdapter(FeatureFlag.class, new FeatureFlagGsonDeserializer()) + .registerTypeAdapter(Group.class, new GroupGsonDeserializer()) + .registerTypeAdapter(DatafileProjectConfig.class, new DatafileGsonDeserializer()) + .create()); + } + + GsonConfigParser(Gson gson) { + this.gson = gson; + } @Override public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException { @@ -37,14 +53,6 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse if (json.length() == 0) { throw new ConfigParseException("Unable to parse empty json."); } - Gson gson = new GsonBuilder() - .registerTypeAdapter(Audience.class, new AudienceGsonDeserializer()) - .registerTypeAdapter(TypedAudience.class, new AudienceGsonDeserializer()) - .registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer()) - .registerTypeAdapter(FeatureFlag.class, new FeatureFlagGsonDeserializer()) - .registerTypeAdapter(Group.class, new GroupGsonDeserializer()) - .registerTypeAdapter(DatafileProjectConfig.class, new DatafileGsonDeserializer()) - .create(); try { return gson.fromJson(json, DatafileProjectConfig.class); @@ -52,4 +60,17 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse throw new ConfigParseException("Unable to parse datafile: " + json, e); } } + + public String toJson(Object src) { + return gson.toJson(src); + } + + public T fromJson(String json, Class clazz) throws JsonParseException { + try { + return gson.fromJson(json, clazz); + } catch (Exception e) { + throw new JsonParseException("Unable to parse JSON string: " + e.toString()); + } + } + } diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java index e5c5ca5c0..a9b012807 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2018, Optimizely and contributors + * Copyright 2016-2018, 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. @@ -16,6 +16,7 @@ */ package com.optimizely.ab.config.parser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.optimizely.ab.config.DatafileProjectConfig; @@ -25,11 +26,12 @@ import com.optimizely.ab.config.audience.TypedAudience; import javax.annotation.Nonnull; +import java.io.IOException; /** * {@code Jackson}-based config parser implementation. */ -final class JacksonConfigParser implements ConfigParser { +final public class JacksonConfigParser implements ConfigParser { private ObjectMapper objectMapper; public JacksonConfigParser() { @@ -61,4 +63,23 @@ public ProjectConfigModule() { addDeserializer(Condition.class, new ConditionJacksonDeserializer(objectMapper)); } } + + @Override + public String toJson(Object src) throws JsonParseException { + try { + return objectMapper.writeValueAsString(src); + } catch (JsonProcessingException e) { + throw new JsonParseException("Serialization failed: " + e.toString()); + } + } + + @Override + public T fromJson(String json, Class clazz) throws JsonParseException { + try { + return objectMapper.readValue(json, clazz); + } catch (IOException e) { + throw new JsonParseException("Unable to parse JSON string: " + e.toString()); + } + } + } diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java index 0e33ad4b2..ad0d971bd 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2019, Optimizely and contributors + * Copyright 2016-2019, 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. @@ -28,17 +28,12 @@ import org.json.JSONTokener; import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * {@code org.json}-based config parser implementation. */ -final class JsonConfigParser implements ConfigParser { +final public class JsonConfigParser implements ConfigParser { @Override public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException { @@ -389,4 +384,22 @@ private List parseRollouts(JSONArray rolloutsJson) { return rollouts; } + + @Override + public String toJson(Object src) { + JSONObject json = (JSONObject)JsonHelpers.convertToJsonObject(src); + return json.toString(); + } + + @Override + public T fromJson(String json, Class clazz) throws JsonParseException { + if (Map.class.isAssignableFrom(clazz)) { + JSONObject obj = new JSONObject(json); + return (T)JsonHelpers.jsonObjectToMap(obj); + } + + // org.json parser does not support parsing to user objects + throw new JsonParseException("Parsing fails with a unsupported type"); + } + } diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonHelpers.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonHelpers.java new file mode 100644 index 000000000..405c863c5 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonHelpers.java @@ -0,0 +1,81 @@ +/** + * + * 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.config.parser; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.*; + +final class JsonHelpers { + + static Object convertToJsonObject(Object obj) { + if (obj instanceof Map) { + Map map = (Map)obj; + JSONObject jObj = new JSONObject(); + for (Map.Entry entry : map.entrySet()) { + jObj.put(entry.getKey().toString(), convertToJsonObject(entry.getValue())); + } + return jObj; + } else if (obj instanceof List) { + List list = (List)obj; + JSONArray jArray = new JSONArray(); + for (Object value : list) { + jArray.put(convertToJsonObject(value)); + } + return jArray; + } else { + return obj; + } + } + + static Map jsonObjectToMap(JSONObject jObj) { + Map map = new HashMap<>(); + + Iterator keys = jObj.keys(); + while(keys.hasNext()) { + String key = keys.next(); + Object value = jObj.get(key); + + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray)value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject)value); + } + + map.put(key, value); + } + + return map; + } + + static List jsonArrayToList(JSONArray array) { + List list = new ArrayList<>(); + for(Object value : array) { + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray)value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject)value); + } + + list.add(value); + } + + return list; + } + +} diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonParseException.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonParseException.java new file mode 100644 index 000000000..0e77b7571 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonParseException.java @@ -0,0 +1,27 @@ +/** + * + * 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.config.parser; + +public final class JsonParseException extends Exception { + public JsonParseException(String message) { + super(message); + } + + public JsonParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java index 24180c61a..b6236ffa7 100644 --- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java +++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2019, Optimizely and contributors + * Copyright 2016-2019, 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. @@ -26,22 +26,20 @@ import com.optimizely.ab.internal.ConditionUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.ContainerFactory; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * {@code json-simple}-based config parser implementation. */ -final class JsonSimpleConfigParser implements ConfigParser { +final public class JsonSimpleConfigParser implements ConfigParser { @Override public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException { @@ -372,5 +370,35 @@ private List parseRollouts(JSONArray rolloutsJson) { return rollouts; } + + @Override + public String toJson(Object src) { + return JSONValue.toJSONString(src); + } + + @Override + public T fromJson(String json, Class clazz) throws JsonParseException { + if (Map.class.isAssignableFrom(clazz)) { + try { + return (T)new JSONParser().parse(json, new ContainerFactory() { + @Override + public Map createObjectContainer() { + return new HashMap(); + } + + @Override + public List creatArrayContainer() { + return new ArrayList(); + } + }); + } catch (ParseException e) { + throw new JsonParseException("Unable to parse JSON string: " + e.toString()); + } + } + + // org.json.simple does not support parsing to user objects + throw new JsonParseException("Parsing fails with a unsupported type"); + } + } diff --git a/core-api/src/main/java/com/optimizely/ab/optimizelyjson/OptimizelyJSON.java b/core-api/src/main/java/com/optimizely/ab/optimizelyjson/OptimizelyJSON.java new file mode 100644 index 000000000..811999e24 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/optimizelyjson/OptimizelyJSON.java @@ -0,0 +1,161 @@ +/** + * + * 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.optimizely.ab.config.parser.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Map; + +/** + * OptimizelyJSON is an object for accessing values of JSON-type feature variables + */ +public class OptimizelyJSON { + @Nullable + private String payload; + @Nullable + private Map map; + + private ConfigParser parser; + + private static final Logger logger = LoggerFactory.getLogger(OptimizelyJSON.class); + + public OptimizelyJSON(@Nonnull String payload) { + this(payload, DefaultConfigParser.getInstance()); + } + + public OptimizelyJSON(@Nonnull String payload, ConfigParser parser) { + this.payload = payload; + this.parser = parser; + } + + public OptimizelyJSON(@Nonnull Map map) { + this(map, DefaultConfigParser.getInstance()); + } + + public OptimizelyJSON(@Nonnull Map map, ConfigParser parser) { + this.map = map; + this.parser = parser; + } + + /** + * Returns the string representation of json data + */ + @Nonnull + public String toString() { + if (payload == null && map != null) { + try { + payload = parser.toJson(map); + } catch (JsonParseException e) { + logger.error("Provided map could not be converted to a string ({})", e.toString()); + } + } + + return payload != null ? payload : ""; + } + + /** + * Returns the {@code Map} representation of json data + */ + @Nullable + public Map toMap() { + if (map == null && payload != null) { + try { + map = parser.fromJson(payload, Map.class); + } catch (Exception e) { + logger.error("Provided string could not be converted to a dictionary ({})", e.toString()); + } + } + + return map; + } + + /** + * Populates the schema passed by the user - it takes primitive types and complex struct type + *

+ * Example: + *

+     *  JSON data is {"k1":true, "k2":{"k22":"v22"}}
+     *
+     *  Set jsonKey to "k2" to access {"k22":"v22"} or set it to to "k2.k22" to access "v22".
+     *  Set it to null to access the entire JSON data.
+     * 
+ * + * @param jsonKey The JSON key paths for the data to access + * @param clazz The user-defined class that the json data will be parsed to + * @return an instance of clazz type with the parsed data filled in (or null if parse fails) + */ + @Nullable + public T getValue(@Nullable String jsonKey, Class clazz) throws JsonParseException { + if (!(parser instanceof GsonConfigParser || parser instanceof JacksonConfigParser)) { + throw new JsonParseException("A proper JSON parser is not available. Use Gson or Jackson parser for this operation."); + } + + Map subMap = toMap(); + T result = null; + + if (jsonKey == null || jsonKey.isEmpty()) { + return getValueInternal(subMap, clazz); + } + + String[] keys = jsonKey.split("\\.", -1); // -1 to keep trailing empty fields + + for(int i=0; i) 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 getValueInternal(@Nullable Object object, Class clazz) { + if (object == null) return null; + + if (clazz.isInstance(object)) return (T)object; // primitive (String, Boolean, Integer, Double) + + try { + String payload = parser.toJson(object); + return parser.fromJson(payload, clazz); + } catch (Exception e) { + logger.error("Map to Java Object failed ({})", e.toString()); + } + + return null; + } + +} + diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java index de2ac643a..69ffd81a2 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/GsonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, Optimizely and contributors + * Copyright 2016-2017, 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. @@ -34,7 +34,9 @@ import org.junit.rules.ExpectedException; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.nullFeatureEnabledConfigJsonV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV2; @@ -44,8 +46,7 @@ import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV3; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.verifyProjectConfig; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; /** * Tests for {@link GsonConfigParser}. @@ -308,4 +309,50 @@ public void nullJsonExceptionWrapping() throws Exception { GsonConfigParser parser = new GsonConfigParser(); parser.parseProjectConfig(null); } + + @Test + public void testToJson() { + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", 3.5); + map.put("k3", true); + + String expectedString = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + GsonConfigParser parser = new GsonConfigParser(); + String json = parser.toJson(map); + assertEquals(json, expectedString); + } + + @Test + public void testFromJson() { + String json = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + Map expectedMap = new HashMap<>(); + expectedMap.put("k1", "v1"); + expectedMap.put("k2", 3.5); + expectedMap.put("k3", true); + + GsonConfigParser parser = new GsonConfigParser(); + + Map map = null; + try { + map = parser.fromJson(json, Map.class); + assertEquals(map, expectedMap); + } catch (JsonParseException e) { + fail("Parse to map failed: " + e.getMessage()); + } + + // invalid JSON string + + String invalidJson = "'k1':'v1','k2':3.5"; + try { + map = parser.fromJson(invalidJson, Map.class); + fail("Expected failure for parsing: " + map.toString()); + } catch (JsonParseException e) { + assertTrue(true); + } + + } + } diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java index 61c44e730..896cadcb6 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JacksonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019-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. @@ -30,6 +30,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.HashMap; +import java.util.Map; + import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.nullFeatureEnabledConfigJsonV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV2; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV3; @@ -38,8 +41,7 @@ import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV3; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.verifyProjectConfig; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; /** * Tests for {@link JacksonConfigParser}. @@ -300,4 +302,55 @@ public void nullJsonExceptionWrapping() throws Exception { JacksonConfigParser parser = new JacksonConfigParser(); parser.parseProjectConfig(null); } + + @Test + public void testToJson() { + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", 3.5); + map.put("k3", true); + + String expectedString = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + JacksonConfigParser parser = new JacksonConfigParser(); + String json = null; + try { + json = parser.toJson(map); + assertEquals(json, expectedString); + } catch (JsonParseException e) { + fail("Parse to serialize to a JSON string: " + e.getMessage()); + } + } + + @Test + public void testFromJson() { + String json = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + Map expectedMap = new HashMap<>(); + expectedMap.put("k1", "v1"); + expectedMap.put("k2", 3.5); + expectedMap.put("k3", true); + + JacksonConfigParser parser = new JacksonConfigParser(); + + Map map = null; + try { + map = parser.fromJson(json, Map.class); + assertEquals(map, expectedMap); + } catch (JsonParseException e) { + fail("Parse to map failed: " + e.getMessage()); + } + + // invalid JSON string + + String invalidJson = "'k1':'v1','k2':3.5"; + try { + map = parser.fromJson(invalidJson, Map.class); + fail("Expected failure for parsing: " + map.toString()); + } catch (JsonParseException e) { + assertTrue(true); + } + + } + } diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java index 27995b88f..b155c1954 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019-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. @@ -19,7 +19,6 @@ import com.optimizely.ab.config.FeatureFlag; import com.optimizely.ab.config.FeatureVariable; import com.optimizely.ab.config.ProjectConfig; - import com.optimizely.ab.config.audience.AudienceIdCondition; import com.optimizely.ab.config.audience.Condition; import com.optimizely.ab.config.audience.UserAttribute; @@ -32,6 +31,10 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.nullFeatureEnabledConfigJsonV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV2; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV4; @@ -40,8 +43,7 @@ import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV3; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.verifyProjectConfig; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; /** * Tests for {@link JsonConfigParser}. @@ -252,4 +254,48 @@ public void nullJsonExceptionWrapping() throws Exception { JsonConfigParser parser = new JsonConfigParser(); parser.parseProjectConfig(null); } + + @Test + public void testToJson() { + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", 3.5); + map.put("k3", true); + + String expectedString = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + JsonConfigParser parser = new JsonConfigParser(); + String json = parser.toJson(map); + assertEquals(json, expectedString); + } + + @Test + public void testFromJson() { + String json = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + Map expectedMap = new HashMap<>(); + expectedMap.put("k1", "v1"); + expectedMap.put("k2", 3.5); + expectedMap.put("k3", true); + + JsonConfigParser parser = new JsonConfigParser(); + + Map map = null; + try { + map = parser.fromJson(json, Map.class); + assertEquals(map, expectedMap); + } catch (JsonParseException e) { + fail("Parse to map failed: " + e.getMessage()); + } + + // not-supported parse type + + try { + List value = parser.fromJson(json, List.class); + fail("Unsupported parse target type: " + value.toString()); + } catch (JsonParseException e) { + assertTrue(true); + } + } + } diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonHelpersTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonHelpersTest.java new file mode 100644 index 000000000..330abea75 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonHelpersTest.java @@ -0,0 +1,89 @@ +/** + * + * 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.config.parser; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Tests for {@link JsonHelpers}. + */ +public class JsonHelpersTest { + private Map map; + private JSONArray jsonArray; + private JSONObject jsonObject; + + @Before + public void setUp() throws Exception { + List list = new ArrayList(); + list.add("vv1"); + list.add(true); + + map = new HashMap(); + map.put("k1", "v1"); + map.put("k2", 3.5); + map.put("k3", list); + + jsonArray = new JSONArray(); + jsonArray.put("vv1"); + jsonArray.put(true); + + jsonObject = new JSONObject(); + jsonObject.put("k1", "v1"); + jsonObject.put("k2", 3.5); + jsonObject.put("k3", jsonArray); + } + @Test + public void testConvertToJsonObject() { + JSONObject value = (JSONObject) JsonHelpers.convertToJsonObject(map); + + assertEquals(value.getString("k1"), "v1"); + assertEquals(value.getDouble("k2"), 3.5, 0.01); + JSONArray array = value.getJSONArray("k3"); + assertEquals(array.get(0), "vv1"); + assertEquals(array.get(1), true); + } + + @Test + public void testJsonObjectToMap() { + Map value = JsonHelpers.jsonObjectToMap(jsonObject); + + assertEquals(value.get("k1"), "v1"); + assertEquals((Double) value.get("k2"), 3.5, 0.01); + ArrayList array = (ArrayList) value.get("k3"); + assertEquals(array.get(0), "vv1"); + assertEquals(array.get(1), true); + } + + @Test + public void testJsonArrayToList() { + List value = JsonHelpers.jsonArrayToList(jsonArray); + + assertEquals(value.get(0), "vv1"); + assertEquals(value.get(1), true); + } + +} diff --git a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java index e22192291..86150c51a 100644 --- a/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java +++ b/core-api/src/test/java/com/optimizely/ab/config/parser/JsonSimpleConfigParserTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2017, 2019, Optimizely and contributors + * Copyright 2016-2017, 2019-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. @@ -32,7 +32,9 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.nullFeatureEnabledConfigJsonV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validConfigJsonV2; @@ -42,8 +44,7 @@ import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV3; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.validProjectConfigV4; import static com.optimizely.ab.config.DatafileProjectConfigTestUtils.verifyProjectConfig; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; /** * Tests for {@link JsonSimpleConfigParser}. @@ -254,4 +255,48 @@ public void nullJsonExceptionWrapping() throws Exception { JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); parser.parseProjectConfig(null); } + + @Test + public void testToJson() { + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", 3.5); + map.put("k3", true); + + String expectedString = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); + String json = parser.toJson(map); + assertEquals(json, expectedString); + } + + @Test + public void testFromJson() { + String json = "{\"k1\":\"v1\",\"k2\":3.5,\"k3\":true}"; + + Map expectedMap = new HashMap<>(); + expectedMap.put("k1", "v1"); + expectedMap.put("k2", 3.5); + expectedMap.put("k3", true); + + JsonSimpleConfigParser parser = new JsonSimpleConfigParser(); + + Map map = null; + try { + map = parser.fromJson(json, Map.class); + assertEquals(map, expectedMap); + } catch (JsonParseException e) { + fail("Parse to map failed: " + e.getMessage()); + } + + // not-supported parse type + + try { + List value = parser.fromJson(json, List.class); + fail("Unsupported parse target type: " + value.toString()); + } catch (JsonParseException e) { + assertEquals(e.getMessage(), "Parsing fails with a unsupported type"); + } + } + } diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONTest.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONTest.java new file mode 100644 index 000000000..501c5d17d --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONTest.java @@ -0,0 +1,299 @@ +/** + * + * 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.optimizely.ab.config.parser.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.util.*; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * Common tests for all JSON parsers + */ +@RunWith(Parameterized.class) +public class OptimizelyJSONTest { + + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection data() throws IOException { + return Arrays.asList( + new GsonConfigParser(), + new JacksonConfigParser(), + new JsonConfigParser(), + new JsonSimpleConfigParser() + ); + } + + @Parameterized.Parameter(0) + public ConfigParser parser; + + private String orgJson; + private Map orgMap; + private boolean canSupportGetValue; + + @Before + public void setUp() throws Exception { + Class parserClass = parser.getClass(); + canSupportGetValue = parserClass.equals(GsonConfigParser.class) || + parserClass.equals(JacksonConfigParser.class); + + orgJson = + "{ " + + " \"k1\": \"v1\", " + + " \"k2\": true, " + + " \"k3\": { " + + " \"kk1\": 1.2, " + + " \"kk2\": { " + + " \"kkk1\": true, " + + " \"kkk2\": 3.5, " + + " \"kkk3\": \"vvv3\", " + + " \"kkk4\": [5.7, true, \"vvv4\"] " + + " } " + + " } " + + "} "; + + Map m3 = new HashMap(); + m3.put("kkk1", true); + m3.put("kkk2", 3.5); + m3.put("kkk3", "vvv3"); + m3.put("kkk4", new ArrayList(Arrays.asList(5.7, true, "vvv4"))); + + Map m2 = new HashMap(); + m2.put("kk1", 1.2); + m2.put("kk2", m3); + + Map m1 = new HashMap(); + m1.put("k1", "v1"); + m1.put("k2", true); + m1.put("k3", m2); + + orgMap = m1; + } + + private String compact(String str) { + return str.replaceAll("\\s", ""); + } + + + // Common tests for all parsers (GSON, Jackson, Json, JsonSimple) + @Test + public void testOptimizelyJSON() { + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + Map map = oj1.toMap(); + + OptimizelyJSON oj2 = new OptimizelyJSON(map, parser); + String data = oj2.toString(); + + assertEquals(compact(data), compact(orgJson)); + } + + @Test + public void testToStringFromString() { + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + assertEquals(compact(oj1.toString()), compact(orgJson)); + } + + @Test + public void testToStringFromMap() { + OptimizelyJSON oj1 = new OptimizelyJSON(orgMap, parser); + assertEquals(compact(oj1.toString()), compact(orgJson)); + } + + @Test + public void testToMapFromString() { + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + assertEquals(oj1.toMap(), orgMap); + } + + @Test + public void testToMapFromMap() { + OptimizelyJSON oj1 = new OptimizelyJSON(orgMap, parser); + assertEquals(oj1.toMap(), orgMap); + } + + // GetValue tests + + @Test + public void testGetValueNullKeyPath() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + TestTypes.MD1 md1 = oj1.getValue(null, TestTypes.MD1.class); + assertNotNull(md1); + assertEquals(md1.k1, "v1"); + assertEquals(md1.k2, true); + assertEquals(md1.k3.kk1, 1.2, 0.01); + assertEquals(md1.k3.kk2.kkk1, true); + assertEquals((Double)md1.k3.kk2.kkk4[0], 5.7, 0.01); + assertEquals(md1.k3.kk2.kkk4[2], "vvv4"); + } + + @Test + public void testGetValueEmptyKeyPath() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + TestTypes.MD1 md1 = oj1.getValue("", TestTypes.MD1.class); + assertEquals(md1.k1, "v1"); + assertEquals(md1.k2, true); + assertEquals(md1.k3.kk1, 1.2, 0.01); + assertEquals(md1.k3.kk2.kkk1, true); + assertEquals((Double) md1.k3.kk2.kkk4[0], 5.7, 0.01); + assertEquals(md1.k3.kk2.kkk4[2], "vvv4"); + } + + @Test + public void testGetValueWithKeyPathToMapWithLevel1() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + TestTypes.MD2 md2 = oj1.getValue("k3", TestTypes.MD2.class); + assertNotNull(md2); + assertEquals(md2.kk1, 1.2, 0.01); + assertEquals(md2.kk2.kkk1, true); + } + + @Test + public void testGetValueWithKeyPathToMapWithLevel2() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + TestTypes.MD3 md3 = oj1.getValue("k3.kk2", TestTypes.MD3.class); + assertNotNull(md3); + assertEquals(md3.kkk1, true); + } + + @Test + public void testGetValueWithKeyPathToBoolean() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + Boolean value = oj1.getValue("k3.kk2.kkk1", Boolean.class); + assertNotNull(value); + assertEquals(value, true); + } + + @Test + public void testGetValueWithKeyPathToDouble() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + Double value = oj1.getValue("k3.kk2.kkk2", Double.class); + assertNotNull(value); + assertEquals(value.doubleValue(), 3.5, 0.01); + } + + @Test + public void testGetValueWithKeyPathToString() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + String value = oj1.getValue("k3.kk2.kkk3", String.class); + assertNotNull(value); + assertEquals(value, "vvv3"); + } + + @Test + public void testGetValueNotDestroying() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + TestTypes.MD3 md3 = oj1.getValue("k3.kk2", TestTypes.MD3.class); + assertNotNull(md3); + assertEquals(md3.kkk1, true); + assertEquals(md3.kkk2, 3.5, 0.01); + assertEquals(md3.kkk3, "vvv3"); + assertEquals((Double) md3.kkk4[0], 5.7, 0.01); + assertEquals(md3.kkk4[2], "vvv4"); + + // verify previous getValue does not destroy the data + + TestTypes.MD3 newMd3 = oj1.getValue("k3.kk2", TestTypes.MD3.class); + assertNotNull(newMd3); + assertEquals(newMd3.kkk1, true); + assertEquals(newMd3.kkk2, 3.5, 0.01); + assertEquals(newMd3.kkk3, "vvv3"); + assertEquals((Double) newMd3.kkk4[0], 5.7, 0.01); + assertEquals(newMd3.kkk4[2], "vvv4"); + } + + @Test + public void testGetValueWithInvalidKeyPath() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + String value = oj1.getValue("k3..kkk3", String.class); + assertNull(value); + } + + @Test + public void testGetValueWithInvalidKeyPath2() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + String value = oj1.getValue("k1.", String.class); + assertNull(value); + } + + @Test + public void testGetValueWithInvalidKeyPath3() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + String value = oj1.getValue("x9", String.class); + assertNull(value); + } + + @Test + public void testGetValueWithInvalidKeyPath4() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + String value = oj1.getValue("k3.x9", String.class); + assertNull(value); + } + + @Test + public void testGetValueWithWrongType() throws JsonParseException { + assumeTrue("GetValue API is supported for Gson and Jackson parsers only", canSupportGetValue); + + OptimizelyJSON oj1 = new OptimizelyJSON(orgJson, parser); + + Integer value = oj1.getValue("k3.kk2.kkk3", Integer.class); + assertNull(value); + } + +} + diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithGsonParserTest.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithGsonParserTest.java new file mode 100644 index 000000000..ebbed4bf5 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithGsonParserTest.java @@ -0,0 +1,103 @@ +/** + * + * 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.optimizely.ab.config.parser.ConfigParser; +import com.optimizely.ab.config.parser.GsonConfigParser; +import com.optimizely.ab.config.parser.JsonParseException; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests for GSON parser only + */ +public class OptimizelyJSONWithGsonParserTest { + protected ConfigParser getParser() { + return new GsonConfigParser(); + } + + @Test + public void testGetValueWithNotMatchingType() throws JsonParseException { + OptimizelyJSON oj1 = new OptimizelyJSON("{\"k1\": 3.5}", getParser()); + + // GSON returns non-null object but variable is null (while Jackson returns null object) + + TestTypes.NotMatchingType md = oj1.getValue(null, TestTypes.NotMatchingType.class); + assertNull(md.x99); + } + + // Tests for integer/double processing + + @Test + public void testIntegerProcessing() throws JsonParseException { + + // GSON parser toMap() adds ".0" to all integers + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3.0); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1.0); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + assertEquals(oj1.toMap(), m1); + } + + @Test + public void testIntegerProcessing2() throws JsonParseException { + + // GSON parser toString() keeps ".0" in double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(m1, getParser()); + assertEquals(oj1.toString(), json); + } + + @Test + public void testIntegerProcessing3() throws JsonParseException { + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + TestTypes.MDN1 obj = oj1.getValue(null, TestTypes.MDN1.class); + + assertEquals(obj.k1, 1); + assertEquals(obj.k2, 2.5, 0.01); + assertEquals(obj.k3.kk1, 3); + assertEquals(obj.k3.kk2, 4.0, 0.01); + } + +} diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJacksonParserTest.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJacksonParserTest.java new file mode 100644 index 000000000..c8f800918 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJacksonParserTest.java @@ -0,0 +1,100 @@ +/** + * + * 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.optimizely.ab.config.parser.ConfigParser; +import com.optimizely.ab.config.parser.JacksonConfigParser; +import com.optimizely.ab.config.parser.JsonParseException; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests for Jackson parser only + */ +public class OptimizelyJSONWithJacksonParserTest { + protected ConfigParser getParser() { + return new JacksonConfigParser(); + } + + @Test + public void testGetValueWithNotMatchingType() throws JsonParseException { + OptimizelyJSON oj1 = new OptimizelyJSON("{\"k1\": 3.5}", getParser()); + + // Jackson returns null object when variables not matching (while GSON returns an object with null variables + + TestTypes.NotMatchingType md = oj1.getValue(null, TestTypes.NotMatchingType.class); + assertNull(md); + } + + // Tests for integer/double processing + + @Test + public void testIntegerProcessing() throws JsonParseException { + + // Jackson parser toMap() keeps ".0" in double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + assertEquals(oj1.toMap(), m1); + } + + @Test + public void testIntegerProcessing2() throws JsonParseException { + + // Jackson parser toString() keeps ".0" in double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(m1, getParser()); + assertEquals(oj1.toString(), json); + } + + @Test + public void testIntegerProcessing3() throws JsonParseException { + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + TestTypes.MDN1 obj = oj1.getValue(null, TestTypes.MDN1.class); + + assertEquals(obj.k1, 1); + } + +} diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonParserTest.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonParserTest.java new file mode 100644 index 000000000..05e308a39 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonParserTest.java @@ -0,0 +1,91 @@ +/** + * + * 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.optimizely.ab.config.parser.ConfigParser; +import com.optimizely.ab.config.parser.JsonConfigParser; +import com.optimizely.ab.config.parser.JsonParseException; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Tests for org.json parser only + */ +public class OptimizelyJSONWithJsonParserTest { + protected ConfigParser getParser() { + return new JsonConfigParser(); + } + + @Test + public void testGetValueThrowsException() { + OptimizelyJSON oj1 = new OptimizelyJSON("{\"k1\": 3.5}", getParser()); + + try { + String str = oj1.getValue(null, String.class); + fail("GetValue is not supported for or.json paraser: " + str); + } catch (JsonParseException e) { + assertEquals(e.getMessage(), "A proper JSON parser is not available. Use Gson or Jackson parser for this operation."); + } + } + + // Tests for integer/double processing + + @Test + public void testIntegerProcessing() throws JsonParseException { + + // org.json parser toMap() keeps ".0" in double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + assertEquals(oj1.toMap(), m1); + } + + @Test + public void testIntegerProcessing2() throws JsonParseException { + + // org.json parser toString() drops ".0" from double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(m1, getParser()); + assertEquals(oj1.toString(), json); + } + +} diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonSimpleParserTest.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonSimpleParserTest.java new file mode 100644 index 000000000..d66ca63a1 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/OptimizelyJSONWithJsonSimpleParserTest.java @@ -0,0 +1,93 @@ +/** + * + * 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.optimizely.ab.config.parser.ConfigParser; +import com.optimizely.ab.config.parser.JsonParseException; +import com.optimizely.ab.config.parser.JsonSimpleConfigParser; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Tests for org.json.simple parser only + */ +public class OptimizelyJSONWithJsonSimpleParserTest { + protected ConfigParser getParser() { + return new JsonSimpleConfigParser(); + } + + @Test + public void testGetValueThrowsException() { + OptimizelyJSON oj1 = new OptimizelyJSON("{\"k1\": 3.5}", getParser()); + + try { + String str = oj1.getValue(null, String.class); + fail("GetValue is not supported for or.json paraser: " + str); + } catch (JsonParseException e) { + assertEquals(e.getMessage(), "A proper JSON parser is not available. Use Gson or Jackson parser for this operation."); + } + } + + // Tests for integer/double processing + + @Test + public void testIntegerProcessing() throws JsonParseException { + + // org.json.simple parser toMap() keeps ".0" in double + // org.json.simple parser toMap() return Long type for integer value + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", Long.valueOf(3)); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", Long.valueOf(1)); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(json, getParser()); + assertEquals(oj1.toMap(), m1); + } + + @Test + public void testIntegerProcessing2() throws JsonParseException { + + // org.json.simple parser toString() keeps ".0" in double + + String json = "{\"k1\":1,\"k2\":2.5,\"k3\":{\"kk1\":3,\"kk2\":4.0}}"; + + Map m2 = new HashMap(); + m2.put("kk1", 3); + m2.put("kk2", 4.0); + + Map m1 = new HashMap(); + m1.put("k1", 1); + m1.put("k2", 2.5); + m1.put("k3", m2); + + OptimizelyJSON oj1 = new OptimizelyJSON(m1, getParser()); + assertEquals(oj1.toString(), json); + } + +} diff --git a/core-api/src/test/java/com/optimizely/ab/optimizelyjson/TestTypes.java b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/TestTypes.java new file mode 100644 index 000000000..4fa8260fd --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/optimizelyjson/TestTypes.java @@ -0,0 +1,61 @@ +/** + * + * 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; + +/** + * Test types for parsing JSON strings to Java objects (OptimizelyJSON) + */ +public class TestTypes { + + public static class MD1 { + public String k1; + public boolean k2; + public MD2 k3; + } + + public static class MD2 { + public double kk1; + public MD3 kk2; + } + + public static class MD3 { + public boolean kkk1; + public double kkk2; + public String kkk3; + public Object[] kkk4; + } + + // Invalid parse type + + public static class NotMatchingType { + public String x99; + } + + // Test types for integer parsing tests + + public static class MDN1 { + public int k1; + public double k2; + public MDN2 k3; + } + + public static class MDN2 { + public int kk1; + public double kk2; + } + +}