Skip to content

Commit 8609f3d

Browse files
authored
feature: add OptimizelyJSON (#371)
1 parent b08b7a5 commit 8609f3d

19 files changed

+1425
-40
lines changed

core-api/src/main/java/com/optimizely/ab/config/parser/ConfigParser.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,10 @@ public interface ConfigParser {
3838
* @throws ConfigParseException when there's an issue parsing the provided project config
3939
*/
4040
ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException;
41+
42+
/**
43+
* OptimizelyJSON parsing
44+
*/
45+
String toJson(Object src) throws JsonParseException;
46+
<T> T fromJson(String json, Class<T> clazz) throws JsonParseException;
4147
}

core-api/src/main/java/com/optimizely/ab/config/parser/GsonConfigParser.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2017, 2019, Optimizely and contributors
3+
* Copyright 2016-2017, 2019-2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -27,7 +27,23 @@
2727
/**
2828
* {@link Gson}-based config parser implementation.
2929
*/
30-
final class GsonConfigParser implements ConfigParser {
30+
final public class GsonConfigParser implements ConfigParser {
31+
private Gson gson;
32+
33+
public GsonConfigParser() {
34+
this(new GsonBuilder()
35+
.registerTypeAdapter(Audience.class, new AudienceGsonDeserializer())
36+
.registerTypeAdapter(TypedAudience.class, new AudienceGsonDeserializer())
37+
.registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer())
38+
.registerTypeAdapter(FeatureFlag.class, new FeatureFlagGsonDeserializer())
39+
.registerTypeAdapter(Group.class, new GroupGsonDeserializer())
40+
.registerTypeAdapter(DatafileProjectConfig.class, new DatafileGsonDeserializer())
41+
.create());
42+
}
43+
44+
GsonConfigParser(Gson gson) {
45+
this.gson = gson;
46+
}
3147

3248
@Override
3349
public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException {
@@ -37,19 +53,24 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
3753
if (json.length() == 0) {
3854
throw new ConfigParseException("Unable to parse empty json.");
3955
}
40-
Gson gson = new GsonBuilder()
41-
.registerTypeAdapter(Audience.class, new AudienceGsonDeserializer())
42-
.registerTypeAdapter(TypedAudience.class, new AudienceGsonDeserializer())
43-
.registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer())
44-
.registerTypeAdapter(FeatureFlag.class, new FeatureFlagGsonDeserializer())
45-
.registerTypeAdapter(Group.class, new GroupGsonDeserializer())
46-
.registerTypeAdapter(DatafileProjectConfig.class, new DatafileGsonDeserializer())
47-
.create();
4856

4957
try {
5058
return gson.fromJson(json, DatafileProjectConfig.class);
5159
} catch (Exception e) {
5260
throw new ConfigParseException("Unable to parse datafile: " + json, e);
5361
}
5462
}
63+
64+
public String toJson(Object src) {
65+
return gson.toJson(src);
66+
}
67+
68+
public <T> T fromJson(String json, Class<T> clazz) throws JsonParseException {
69+
try {
70+
return gson.fromJson(json, clazz);
71+
} catch (Exception e) {
72+
throw new JsonParseException("Unable to parse JSON string: " + e.toString());
73+
}
74+
}
75+
5576
}

core-api/src/main/java/com/optimizely/ab/config/parser/JacksonConfigParser.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2018, Optimizely and contributors
3+
* Copyright 2016-2018, 2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616
*/
1717
package com.optimizely.ab.config.parser;
1818

19+
import com.fasterxml.jackson.core.JsonProcessingException;
1920
import com.fasterxml.jackson.databind.ObjectMapper;
2021
import com.fasterxml.jackson.databind.module.SimpleModule;
2122
import com.optimizely.ab.config.DatafileProjectConfig;
@@ -25,11 +26,12 @@
2526
import com.optimizely.ab.config.audience.TypedAudience;
2627

2728
import javax.annotation.Nonnull;
29+
import java.io.IOException;
2830

2931
/**
3032
* {@code Jackson}-based config parser implementation.
3133
*/
32-
final class JacksonConfigParser implements ConfigParser {
34+
final public class JacksonConfigParser implements ConfigParser {
3335
private ObjectMapper objectMapper;
3436

3537
public JacksonConfigParser() {
@@ -61,4 +63,23 @@ public ProjectConfigModule() {
6163
addDeserializer(Condition.class, new ConditionJacksonDeserializer(objectMapper));
6264
}
6365
}
66+
67+
@Override
68+
public String toJson(Object src) throws JsonParseException {
69+
try {
70+
return objectMapper.writeValueAsString(src);
71+
} catch (JsonProcessingException e) {
72+
throw new JsonParseException("Serialization failed: " + e.toString());
73+
}
74+
}
75+
76+
@Override
77+
public <T> T fromJson(String json, Class<T> clazz) throws JsonParseException {
78+
try {
79+
return objectMapper.readValue(json, clazz);
80+
} catch (IOException e) {
81+
throw new JsonParseException("Unable to parse JSON string: " + e.toString());
82+
}
83+
}
84+
6485
}

core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2019, Optimizely and contributors
3+
* Copyright 2016-2019, 2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -28,17 +28,12 @@
2828
import org.json.JSONTokener;
2929

3030
import javax.annotation.Nonnull;
31-
import java.util.ArrayList;
32-
import java.util.Collections;
33-
import java.util.HashMap;
34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Set;
31+
import java.util.*;
3732

3833
/**
3934
* {@code org.json}-based config parser implementation.
4035
*/
41-
final class JsonConfigParser implements ConfigParser {
36+
final public class JsonConfigParser implements ConfigParser {
4237

4338
@Override
4439
public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException {
@@ -389,4 +384,22 @@ private List<Rollout> parseRollouts(JSONArray rolloutsJson) {
389384

390385
return rollouts;
391386
}
387+
388+
@Override
389+
public String toJson(Object src) {
390+
JSONObject json = (JSONObject)JsonHelpers.convertToJsonObject(src);
391+
return json.toString();
392+
}
393+
394+
@Override
395+
public <T> T fromJson(String json, Class<T> clazz) throws JsonParseException {
396+
if (Map.class.isAssignableFrom(clazz)) {
397+
JSONObject obj = new JSONObject(json);
398+
return (T)JsonHelpers.jsonObjectToMap(obj);
399+
}
400+
401+
// org.json parser does not support parsing to user objects
402+
throw new JsonParseException("Parsing fails with a unsupported type");
403+
}
404+
392405
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.parser;
18+
19+
import org.json.JSONArray;
20+
import org.json.JSONObject;
21+
22+
import java.util.*;
23+
24+
final class JsonHelpers {
25+
26+
static Object convertToJsonObject(Object obj) {
27+
if (obj instanceof Map) {
28+
Map<Object, Object> map = (Map)obj;
29+
JSONObject jObj = new JSONObject();
30+
for (Map.Entry entry : map.entrySet()) {
31+
jObj.put(entry.getKey().toString(), convertToJsonObject(entry.getValue()));
32+
}
33+
return jObj;
34+
} else if (obj instanceof List) {
35+
List list = (List)obj;
36+
JSONArray jArray = new JSONArray();
37+
for (Object value : list) {
38+
jArray.put(convertToJsonObject(value));
39+
}
40+
return jArray;
41+
} else {
42+
return obj;
43+
}
44+
}
45+
46+
static Map<String, Object> jsonObjectToMap(JSONObject jObj) {
47+
Map<String, Object> map = new HashMap<>();
48+
49+
Iterator<String> keys = jObj.keys();
50+
while(keys.hasNext()) {
51+
String key = keys.next();
52+
Object value = jObj.get(key);
53+
54+
if (value instanceof JSONArray) {
55+
value = jsonArrayToList((JSONArray)value);
56+
} else if (value instanceof JSONObject) {
57+
value = jsonObjectToMap((JSONObject)value);
58+
}
59+
60+
map.put(key, value);
61+
}
62+
63+
return map;
64+
}
65+
66+
static List<Object> jsonArrayToList(JSONArray array) {
67+
List<Object> list = new ArrayList<>();
68+
for(Object value : array) {
69+
if (value instanceof JSONArray) {
70+
value = jsonArrayToList((JSONArray)value);
71+
} else if (value instanceof JSONObject) {
72+
value = jsonObjectToMap((JSONObject)value);
73+
}
74+
75+
list.add(value);
76+
}
77+
78+
return list;
79+
}
80+
81+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
*
3+
* Copyright 2020, Optimizely and contributors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.config.parser;
18+
19+
public final class JsonParseException extends Exception {
20+
public JsonParseException(String message) {
21+
super(message);
22+
}
23+
24+
public JsonParseException(String message, Throwable cause) {
25+
super(message, cause);
26+
}
27+
}

core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2016-2019, Optimizely and contributors
3+
* Copyright 2016-2019, 2020, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -26,22 +26,20 @@
2626
import com.optimizely.ab.internal.ConditionUtils;
2727
import org.json.simple.JSONArray;
2828
import org.json.simple.JSONObject;
29+
import org.json.simple.JSONValue;
30+
import org.json.simple.parser.ContainerFactory;
2931
import org.json.simple.parser.JSONParser;
3032
import org.json.simple.parser.ParseException;
3133

3234
import javax.annotation.Nonnull;
33-
import java.util.ArrayList;
34-
import java.util.Collections;
35-
import java.util.HashMap;
36-
import java.util.List;
37-
import java.util.Map;
35+
import java.util.*;
3836
import java.util.logging.Level;
3937
import java.util.logging.Logger;
4038

4139
/**
4240
* {@code json-simple}-based config parser implementation.
4341
*/
44-
final class JsonSimpleConfigParser implements ConfigParser {
42+
final public class JsonSimpleConfigParser implements ConfigParser {
4543

4644
@Override
4745
public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException {
@@ -372,5 +370,35 @@ private List<Rollout> parseRollouts(JSONArray rolloutsJson) {
372370

373371
return rollouts;
374372
}
373+
374+
@Override
375+
public String toJson(Object src) {
376+
return JSONValue.toJSONString(src);
377+
}
378+
379+
@Override
380+
public <T> T fromJson(String json, Class<T> clazz) throws JsonParseException {
381+
if (Map.class.isAssignableFrom(clazz)) {
382+
try {
383+
return (T)new JSONParser().parse(json, new ContainerFactory() {
384+
@Override
385+
public Map createObjectContainer() {
386+
return new HashMap();
387+
}
388+
389+
@Override
390+
public List creatArrayContainer() {
391+
return new ArrayList();
392+
}
393+
});
394+
} catch (ParseException e) {
395+
throw new JsonParseException("Unable to parse JSON string: " + e.toString());
396+
}
397+
}
398+
399+
// org.json.simple does not support parsing to user objects
400+
throw new JsonParseException("Parsing fails with a unsupported type");
401+
}
402+
375403
}
376404

0 commit comments

Comments
 (0)