automatically convert ParameterMap entries to JSON

This commit is contained in:
Jonathan Shook 2022-06-09 15:01:49 -05:00
parent 308f26e220
commit cce97d1c89
7 changed files with 220 additions and 7 deletions

View File

@ -16,13 +16,14 @@
package io.nosqlbench.engine.api.activityimpl;
import io.nosqlbench.nb.api.config.params.ParamsParser;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.nosqlbench.engine.api.util.Unit;
import io.nosqlbench.nb.api.config.params.ParamsParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyObject;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import javax.script.Bindings;
import java.util.*;
@ -44,6 +45,7 @@ import java.util.stream.Collectors;
*/
public class ParameterMap extends ConcurrentHashMap<String,Object> implements Bindings, ProxyObject {
private final static Logger logger = LogManager.getLogger("PARAMS");
private final static Gson gson = new GsonBuilder().create();
// private final ConcurrentHashMap<String, String> paramMap = new ConcurrentHashMap<>(10);
@ -200,7 +202,15 @@ public class ParameterMap extends ConcurrentHashMap<String,Object> implements Bi
@Override
public void putAll(Map<? extends String, ? extends Object> toMerge) {
for (Entry<? extends String, ? extends Object> entry : toMerge.entrySet()) {
super.put(entry.getKey(),String.valueOf(entry.getValue()));
Object raw = entry.getValue();
String value = null;
if (raw instanceof Map || raw instanceof Set || raw instanceof List) {
value = gson.toJson(raw);
} else {
value = raw.toString();
}
super.put(entry.getKey(), value);
}
markMutation();
}

View File

@ -78,6 +78,9 @@ public interface Element {
* defined at the root level, so it is what will be found first. All implementations
* should ensure that this order is preserved.</p>
*
* <p>This method requires a type which will be given to the underlying {@link ElementData}
* implementation for contextual type conversion.</p>
*
* @param name The simple or hierarchic variable name to resolve
* @param classOfT The type of value which the resolved value is required to be assignable to
* @param <T> The value type parameter

View File

@ -16,6 +16,9 @@
package io.nosqlbench.nb.api.config.params;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
@ -28,6 +31,25 @@ import java.util.Set;
*/
public interface ElementData {
String NAME = "name";
List<Class<?>> COMMON_TYPES = List.of(
String.class,
byte.class, Byte.class,
short.class, Short.class,
int.class, Integer.class,
long.class, Long.class,
double.class, Double.class,
float.class, Float.class,
Map.class, Set.class, List.class
);
static Optional<Object> asCommonType(Object src) {
for (Class<?> commonType : COMMON_TYPES) {
if (commonType.isAssignableFrom(src.getClass())) {
return Optional.of(commonType.cast(src));
}
}
return Optional.empty();
}
Object get(String name);
@ -110,4 +132,23 @@ public interface ElementData {
return get(name,type);
}
/**
* <p>Get the value for the key, but ensure that the type of value that is returned
* is in one of the sanctioned {@link #COMMON_TYPES}.
*
* <p>If possible, the value provided should be a wrapper type around the actual backing
* type, such that mutability is preserved.</p>
*
* <p>If the backing type is a structured type object graph which defies direct
* conversion to one of the types above, then an error should be thrown.</p>
*
* <p>If the type is a collection type, then type conversion should be provided all the way
* down to each primitive value.</p>
*
* <p>If no value by the given name exists, the null should be returned.</p>
*
* @param key The key of the value to retrieve
* @return The value as a Java primitive, Boxed primitive, or Set, List, or Map of String to Object.
*/
Object getAsCommon(String key);
}

View File

@ -80,7 +80,7 @@ public class ElementImpl implements Element {
Map<String, Object> map = new LinkedHashMap<>();
for (String key : keys) {
Object value = this.data.get(key);
Object value = this.data.getAsCommon(key);
map.put(key, value);
}

View File

@ -18,7 +18,7 @@ package io.nosqlbench.nb.api.config.params;
import com.google.gson.*;
import java.util.Set;
import java.util.*;
public class JsonBackedConfigElement implements ElementData {
@ -62,6 +62,79 @@ public class JsonBackedConfigElement implements ElementData {
}
}
@Override
public Object getAsCommon(String key) {
Object found = get(key);
if (found==null) {
return null;
}
Optional<Object> converted = ElementData.asCommonType(found);
if (converted.isPresent()) {
return converted.get();
}
if (found instanceof JsonObject jo) {
Map<String,Object> values = new LinkedHashMap<>();
for (String s : jo.keySet()) {
values.put(s, toCommon(jo.get(s)));
}
return values;
}
return toCommon(found);
}
public Object toCommon(Object srcValue) {
Optional<Object> standards = ElementData.asCommonType(srcValue);
if (standards.isPresent()) {
return Optional.of(standards);
}
if (srcValue instanceof JsonElement e) {
if (e.isJsonPrimitive()) {
JsonPrimitive jp = e.getAsJsonPrimitive();
if (jp.isBoolean()) {
return jp.getAsBoolean();
} else if (jp.isNumber()) {
Number number = jp.getAsNumber();
return number.doubleValue();
} else if (jp.isString()) {
return jp.getAsString();
} else if (jp.isJsonNull()) {
return null;
} else {
throw new RuntimeException("Unknown JSON primitive type for element: '" +
e
+ "' type:'"
+e.getClass().getCanonicalName()+"'"
);
}
} else if (e.isJsonObject()) {
JsonObject jo = e.getAsJsonObject();
Map<String,Object> valueMap = new LinkedHashMap<>();
for (String s : jo.keySet()) {
valueMap.put(s, toCommon(jo.get(s)));
}
return valueMap;
} else if (e.isJsonArray()) {
List<Object> valueList = new ArrayList<>();
JsonArray ja = e.getAsJsonArray();
for (JsonElement jsonElement : ja) {
valueList.add(toCommon(jsonElement));
}
return valueList;
}
} else {
throw new RuntimeException("Error traversing JSONElement structure. Unknown type: '"
+ srcValue.getClass().getCanonicalName()
+ "'");
}
throw new RuntimeException("Unable to convert value type from '"
+ srcValue.getClass().getCanonicalName() + "' to a common type.");
}
@Override
public String toString() {
return getGivenName() + "(" + (extractElementName()!=null ? extractElementName() : "null" ) +"):" + jsonObject.toString();
@ -74,4 +147,6 @@ public class JsonBackedConfigElement implements ElementData {
}
return null;
}
}

View File

@ -17,6 +17,7 @@
package io.nosqlbench.nb.api.config.params;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class MapBackedElement implements ElementData {
@ -49,6 +50,20 @@ public class MapBackedElement implements ElementData {
return this.elementName;
}
@Override
public Object getAsCommon(String key) {
Object found = get(key);
if (found==null) {
return null;
}
Optional<Object> converted = ElementData.asCommonType(found);
if (converted.isPresent()) {
return converted.get();
}
throw new RuntimeException("Unable to convert type '" + found.getClass().getCanonicalName() + "' to a common type.");
}
@Override
public String toString() {
return this.getGivenName() + "(" + (this.extractElementName() != null ? this.extractElementName() : "null") + "):" + map.toString();

View File

@ -122,5 +122,74 @@ public class NBParamsTest {
assertThat(e.get("a5.b5")).isEmpty();
}
@Test
public void testNestedJsonObjectsToCommonTypes() {
String maxdouble = String.valueOf(Double.MAX_VALUE);
String maxfloat = String.valueOf(Float.MAX_VALUE);
var json = """
{
"level1key1": {
"level2key1": {
"afloat": %s
}
}
}
""".formatted("thisisit");
Element elements = NBParams.one(json);
Map<String, Object> commonform = elements.getMap();
assertThat(commonform).isEqualTo(
Map.of(
"level1key1", Map.of(
"level2key1", Map.of(
"afloat", "thisisit"
)
)
)
);
}
@Test
public void testNestedJsonObjectsToCommonTypesDeep() {
var json = """
{
"level1key1": {
"level2key1": {
"anint": %s,
"along": %s
},
"level2key2": {
"alist": ["a","b","c"],
"abool": true
}
},
"level1key2": {
"name": "myname"
}
}
""".formatted(3,4L);
Element elements = NBParams.one(json);
Map<String, Object> commonform = elements.getMap();
assertThat(commonform).isEqualTo(
Map.of(
"level1key1", Map.of(
"level2key1", Map.of(
"anint", 3.0d,
"along", 4.0d
),
"level2key2", Map.of(
"alist",List.of("a","b","c"),
"abool",true
)
),
"level1key2", Map.of(
"name","myname"
)
)
);
}
}