diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000..db98e0a
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,35 @@
+# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
+
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+name: Java CI with Maven
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 11
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ cache: maven
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+
+ # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
+# - name: Update dependency graph
+# uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
diff --git a/.gitignore b/.gitignore
index 560d08c..d8b2bee 100755
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ build.xml
nb-configuration.xml
*.versionsBackup
.gradle
+.factorypath
+.nondex
diff --git a/CliArg/pom.xml b/CliArg/pom.xml
index d96d0a4..6a72d51 100644
--- a/CliArg/pom.xml
+++ b/CliArg/pom.xml
@@ -6,7 +6,7 @@
org.jsonex
jcParent
- 0.1.21
+ 0.1.27
../pom.xml
CliArg
diff --git a/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java b/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
index 783c386..27ff251 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/CLIParser.java
@@ -1,20 +1,26 @@
package org.jsonex.cliarg;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
import org.jsonex.core.util.BeanConvertContext;
import org.jsonex.core.util.ClassUtil;
+import static org.jsonex.core.util.LangUtil.doIf;
+import static org.jsonex.core.util.LangUtil.doIfNotNull;
+import static org.jsonex.core.util.ListUtil.isIn;
import org.jsonex.jsoncoder.JSONCoder;
import org.jsonex.jsoncoder.JSONCoderOption;
import org.jsonex.treedoc.TDNode.Type;
import org.jsonex.treedoc.json.TDJSONOption;
-import lombok.Data;
-import lombok.RequiredArgsConstructor;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import java.util.*;
-
-import static org.jsonex.core.util.LangUtil.doIf;
-import static org.jsonex.core.util.ListUtil.isIn;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Parse the input command line arguments against the {@link CLISpec}. The parsed result will be stored in the target
@@ -106,7 +112,7 @@ private void parseArg(String arg) {
}
Param param = spec.indexedParams.get(paramIndex++);
missingParams.remove(param.name);
- param.property.set(target, parseValue(param, arg));
+ doIfNotNull(parseValue(param, arg), v -> param.property.set(target, v));
}
private Object parseValue(Param param, String value) {
@@ -124,7 +130,8 @@ private Object parseValue(Param param, String value) {
? JSONCoder.decode(value, cls, opt)
: JSONCoder.decodeTo(value, param.getProperty().get(target), opt.setMergeArray(true));
} catch (Exception e) {
- log.error("Error parsing parameter:" + param.name, e);
+ errorMessages.put(param.name, value + ";" + e.toString());
+ // log.error("Error parsing parameter:" + param.name, e);
}
return null;
}
@@ -133,9 +140,9 @@ private Object parseValue(Param param, String value) {
public String getErrorsAsString() {
StringBuilder sb = new StringBuilder();
- doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:" + missingParams));
- doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:" + extraArgs));
- doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:" + errorMessages));
+ doIf(!missingParams.isEmpty(), () -> sb.append("\nMissing required arguments:").append(missingParams));
+ doIf(!extraArgs.isEmpty(), () -> sb.append("\nUnexpected arguments:").append(extraArgs));
+ doIf(!errorMessages.isEmpty(), () -> sb.append("\nError parsing following arguments:").append(errorMessages));
return sb.toString();
}
}
diff --git a/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java b/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
index ed069f8..b4155b4 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/CLISpec.java
@@ -20,6 +20,7 @@
import static org.jsonex.core.util.ListUtil.setAt;
/**
+ *
* CLI specification based on annotated java bean of `cls`. Following annotations will be processed:
*
* Class level:
@@ -27,9 +28,10 @@
* {@link Summary}: Summary of the command (Optional)
* {@link Description}: Description of the command (Optional)
* {@link Examples}: Array of string representation of samples usages (Optional)
- *
* For field level annotations, please refer to class {@link Param}
*
+ *
+ *
* @param
*/
@Data
diff --git a/CliArg/src/main/java/org/jsonex/cliarg/Param.java b/CliArg/src/main/java/org/jsonex/cliarg/Param.java
index 54abbda..6cadbbc 100644
--- a/CliArg/src/main/java/org/jsonex/cliarg/Param.java
+++ b/CliArg/src/main/java/org/jsonex/cliarg/Param.java
@@ -11,27 +11,28 @@
import static org.jsonex.core.util.StringUtil.noNull;
/**
- * Represent an command line parameter, it can be either argument or option
- * If index is not null indicate it's argument
- * Argument default to required unless explicitly specified.
- * required argument can't follow non-required argument which index less than it
- * If index is null indicates it's an option, option default to not required, unless specified
- *
- * For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
*
+ *
+ * Represent a command line parameter, it can be either argument or option
+ * If index is not null indicate it is an argument.
+ * Argument default to required unless explicitly specified.
+ * Required argument can't follow non-required argument which index less than it
+ * If index is null indicates it's an option, option default to not required, unless specified
+ * For option of Boolean type, it will be mapped as flag, that means the value of the option can be omitted.
+
* For Param of complex type or array/list, the value can be specified as JSON(ex) string, the top level "{" or "[",
* can be, omitted. The quote for key and value can be omitted.
- *
- * For array parameters, it also possible to specify the values as separate options. The values will be merged
- *
+
+ * For array parameters, it is also possible to specify the values as separate options. The values will be merged
+
* Following Annotation will be processed for each parameter:
- *
* {@link Name} Name of the parameter, optional, default to field name
* {@link ShortName} The optional short name
* {@link Description} The optional description
* {@link Index} Indicate this an indexed parameter
* {@link Required} Indicate if this field is required. all the index fields are required unless explicitly indicated.
* All the non-index fields are not required unless explicitly indicated.
+ *
*/
@Data
public class Param {
diff --git a/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java b/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
index a3ed5a7..2bee19b 100644
--- a/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
+++ b/CliArg/src/test/java/org/jsonex/cliarg/CliParserTest.java
@@ -49,7 +49,7 @@ public static class Arg1 {
@Test
public void testParse() {
- CLISpec spec = new CLISpec(Arg1.class);
+ CLISpec spec = new CLISpec<>(Arg1.class);
assertMatchesSnapshot("spec", spec);
log.info("spec:\n" + spec.printUsage());
@@ -57,7 +57,7 @@ public void testParse() {
String[] args = { "abc", "10", "name:n1,x:1,y:2", "-o", "VAL2", "--optInt", "100",
"--arrayArg", "str1,str2,'It\\'s escapted'", "--arrayArg", "array as separate option"};
- CLIParser parser = spec.parse(args, 0);
+ CLIParser parser = spec.parse(args, 0);
log.info("parsedValue:\n" + parser.target);
assertMatchesSnapshot("parserTarget", parser.target);
diff --git a/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_parserTarget.json b/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_parserTarget.json
index 775cbae..b714c60 100644
--- a/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_parserTarget.json
+++ b/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_parserTarget.json
@@ -1,18 +1,18 @@
{
- "strParam":"abc",
+ "arrayArg":[
+ "str1",
+ "str2",
+ "It's escapted",
+ "array as separate option"
+ ],
+ "noShortName":0,
"numParam":10,
+ "opt":"VAL2",
+ "optInt":100,
"point":{
"name":"n1",
"x":1,
"y":2
},
- "opt":"VAL2",
- "optInt":100,
- "noShortName":0,
- "arrayArg":[
- "str1",
- "str2",
- "It's escapted",
- "array as separate option"
- ]
+ "strParam":"abc"
}
\ No newline at end of file
diff --git a/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_spec.json b/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_spec.json
index efee3d4..f90e309 100644
--- a/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_spec.json
+++ b/CliArg/src/test/resources/org/jsonex/cliarg/__snapshot__/CliParserTest_testParse_spec.json
@@ -1,58 +1,57 @@
{
"cls":"org.jsonex.cliarg.CliParserTest.Arg1",
"defVal":{
+ "noShortName":0,
"numParam":0,
"opt":"VAL1",
- "optInt":10,
- "noShortName":0
+ "optInt":10
},
- "name":"TestArg1",
- "summary":"This is a test arg1",
"description":"Description of test Args",
"examples":[
"arg1 2",
"arg1 4"
],
"firstOptionalIndex":2,
- "optionParams":[
- {
- "name":"opt",
- "shortName":"o",
- "description":"Opt",
- "defVal":"VAL1",
- "required":true
- },
+ "indexedParams":[
{
- "name":"optInt",
- "shortName":"i",
- "defVal":10
+ "description":"Str parameter",
+ "index":0,
+ "name":"strParam"
},
{
- "name":"noShortName",
- "defVal":0
+ "defVal":0,
+ "description":"number parameter",
+ "index":1,
+ "name":"numParam"
},
{
- "name":"arrayArg",
- "description":"array of string, you can specify either use ',' separated string (jsonex array, with escape supported), or pass as multiple options"
+ "description":"Object Point",
+ "index":2,
+ "name":"point",
+ "required":false
}
],
- "indexedParams":[
+ "name":"TestArg1",
+ "optionParams":[
{
- "name":"strParam",
- "index":0,
- "description":"Str parameter"
+ "defVal":"VAL1",
+ "description":"Opt",
+ "name":"opt",
+ "required":true,
+ "shortName":"o"
},
{
- "name":"numParam",
- "index":1,
- "description":"number parameter",
- "defVal":0
+ "defVal":10,
+ "name":"optInt",
+ "shortName":"i"
},
{
- "name":"point",
- "index":2,
- "description":"Object Point",
- "required":false
+ "defVal":0,
+ "name":"noShortName"
+ },
+ {
+ "description":"array of string, you can specify either use ',' separated string (jsonex array, with escape supported), or pass as multiple options",
+ "name":"arrayArg"
}
],
"requiredParams":[
@@ -60,5 +59,6 @@
"numParam",
"opt"
],
+ "summary":"This is a test arg1",
"usage":"TestArg1 -o {opt} [-i {optInt}] [--noShortName {noShortName}] [--arrayArg {arrayArg}] [point]"
}
\ No newline at end of file
diff --git a/HiveUDF/pom.xml b/HiveUDF/pom.xml
new file mode 100644
index 0000000..cc080dc
--- /dev/null
+++ b/HiveUDF/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+
+ org.jsonex
+ jcParent
+ 0.1.27
+ ../pom.xml
+
+ HiveUDF
+ HiveUDF
+
+
+ org.projectlombok
+ lombok
+
+
+ junit
+ junit
+ test
+
+
+ ${project.groupId}
+ JSONCoder
+
+
+ ${project.groupId}
+ csv
+
+
+ ${project.groupId}
+ SnapshotTest
+ test
+
+
+ ${project.groupId}
+ SnapshotTest
+ test
+
+
+ org.apache.hive
+ hive-exec
+ 4.0.0-alpha-2
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+
diff --git a/HiveUDF/src/main/java/org/jsonex/hiveudf/ToCSVUDF.java b/HiveUDF/src/main/java/org/jsonex/hiveudf/ToCSVUDF.java
new file mode 100644
index 0000000..142c146
--- /dev/null
+++ b/HiveUDF/src/main/java/org/jsonex/hiveudf/ToCSVUDF.java
@@ -0,0 +1,88 @@
+package org.jsonex.hiveudf;
+
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hadoop.hive.ql.exec.Description;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.jsonex.core.util.ListUtil;
+import org.jsonex.core.util.StringUtil;
+import org.jsonex.csv.CSVOption;
+import org.jsonex.csv.CSVWriter;
+import org.jsonex.jsoncoder.JSONCoder;
+import org.jsonex.jsoncoder.JSONCoderOption;
+
+import java.util.*;
+
+import static org.jsonex.core.util.ListUtil.map;
+
+
+@Description(name = "to_csv",
+ value = "to_csv([opts], col1, col2, ...) - Serialize to CSV, if column value is not simple object, the value will be " +
+ "serialized as json. If can provide an optional opts parameter as the first parameters in a JSON format to " +
+ "specify field separator or quote. \n" +
+ "Avaliable options: fieldSep:char, quoteChar:char, noHeader:boolean, headers:array",
+ extended = "Example:\n"
+ + " > SELECT to_csv(*) FROM someTable \n"
+ + " > SELECT to_csv('{fieldSep:|,quoteChar:\"\\'\"}', *) FROM someTable \n"
+ + " > SELECT to_csv('{noHead:true}', *) FROM someTable \n"
+ + " > SELECT to_csv('{headers:[,,,col3,]}', *) FROM someTable \n"
+)
+@Slf4j
+public class ToCSVUDF extends GenericUDF {
+ @Data
+ public static class CSVOpt extends CSVOption {
+ String[] headers;
+ boolean noHeader;
+ }
+
+ ObjectInspector[] inspectors;
+ int recordNum;
+
+ @Override
+ public ObjectInspector initialize(ObjectInspector[] inspectors) throws UDFArgumentException {
+ this.inspectors = inspectors;
+ recordNum = 0;
+ return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
+ }
+
+ // @Override public String[] getRequiredJars() { return new String[]{"ivy://org.jsonex:JSONCoder:0.1.21?transitive=true"}; }
+
+ @Override
+ public Object evaluate(DeferredObject[] arguments) throws HiveException {
+ StringBuilder sb = new StringBuilder();
+ CSVOpt opt = new CSVOpt();
+ Map map = UDFUtil.toJavaMap(arguments, inspectors);
+ Optional firstKeyOpt = ListUtil.first(map.keySet());
+ if (!firstKeyOpt.isPresent())
+ return "";
+ String firstKey = firstKeyOpt.get();
+ Object firstVal = map.get(firstKey);
+ if (firstKey.equals("0") && firstVal instanceof String && ((String) firstVal).startsWith("{")) { // It's csv opt parameter.
+ JSONCoder.get().decodeTo((String) firstVal, opt);
+ map.remove(firstKey);
+ }
+ if (recordNum++ == 0 && !opt.noHeader) {
+ List headers = new ArrayList<>(map.keySet());
+ if (opt.headers != null) {
+ for (int i = 0; i < headers.size(); i++)
+ if (i < opt.headers.length && !StringUtil.isEmpty(opt.headers[i]))
+ headers.set(i, opt.headers[i]);
+ }
+
+ sb.append(CSVWriter.get().encodeRecord(headers, opt)).append("\n");
+ }
+ List values = map(map.values(), v -> v instanceof List || v instanceof Map
+ ? JSONCoder.get().encode(v, new JSONCoderOption().setStrictOrdering(true)) : v);
+ sb.append(CSVWriter.get().encodeRecord(values, opt));
+ return sb.toString();
+ }
+
+ @Override
+ public String getDisplayString(String[] children) {
+ return getStandardDisplayString("ToCSVUDF", children);
+ }
+}
\ No newline at end of file
diff --git a/HiveUDF/src/main/java/org/jsonex/hiveudf/ToJsonUDF.java b/HiveUDF/src/main/java/org/jsonex/hiveudf/ToJsonUDF.java
new file mode 100644
index 0000000..a2187d0
--- /dev/null
+++ b/HiveUDF/src/main/java/org/jsonex/hiveudf/ToJsonUDF.java
@@ -0,0 +1,65 @@
+package org.jsonex.hiveudf;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.hadoop.hive.ql.exec.Description;
+import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
+import org.jsonex.jsoncoder.JSONCoder;
+import org.jsonex.jsoncoder.JSONCoderOption;
+
+
+/**
+ add jar ivy://org.jsonex:HiveUDF:0.1.23?transitive=true;
+ add jar file:///Users/jianwche/opensource/jsonex/HiveUDF/target/HiveUDF-0.1.23.jar;
+ add jar file:///Users/jianwche/opensource/jsonex/JSONCoder/target/JSONCoder-0.1.23.jar;
+ add jar file:///Users/jianwche/opensource/jsonex/core/target/core-0.1.23.jar;
+ add jar file:///Users/jianwche/opensource/jsonex/treedoc/target/treedoc-0.1.23.jar;
+ add jar file:///Users/jianwche/opensource/jsonex/csv/target/csv-0.1.23.jar;
+ CREATE TEMPORARY FUNCTION to_json AS 'org.jsonex.hiveudf.ToJsonUDF';
+ CREATE TEMPORARY FUNCTION to_csv AS 'org.jsonex.hiveudf.ToCSVUDF';
+
+ create table a(i int, m MAP>, s STRUCT);
+ insert into a values(1, map('ab',named_struct('gender', 'm', 'age', 10), 'cd', named_struct('gender', 'f', 'age', 11)), named_struct('address', 'ca', 'zip', '123'));
+ create table a(i int, m ARRAY>);
+ select to_json(*) from a;
+ select to_json(s) from a;
+ */
+@Description(name = "to_json",
+ value = "to_json(obj1, obj2,...) - Serialize to json. \n",
+ extended = "Example:\n"
+ + " > select to_json(*) from tbl")
+@Slf4j
+public class ToJsonUDF extends GenericUDF {
+ ObjectInspector[] inspectors;
+
+ @Override
+ public ObjectInspector initialize(ObjectInspector[] inspectors) throws UDFArgumentException {
+ this.inspectors = inspectors;
+ return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
+ }
+
+ // @Override public String[] getRequiredJars() { return new String[]{"ivy://org.jsonex:JSONCoder:0.1.21?transitive=true"}; }
+
+ @Override
+ public Object evaluate(DeferredObject[] arguments) throws HiveException {
+// log.info("args=" + toJson(arguments) + "\ninspects:" + toJson(inspectors) + "\nchildren:" + toJson(children));
+// log.info("args=" + toJson(arguments) + "\ninspects:" + toJson(inspectors));
+ return arguments.length == 1
+ ? toJson(UDFUtil.toJavaObj(arguments[0].get(), inspectors[0]))
+ : toJson(UDFUtil.toJavaMap(arguments, inspectors));
+ }
+
+ private static String toJson(Object obj) {
+// JSONCoderOption opt = JSONCoderOption.of().setShowType(true).setShowPrivateField(true).setDedupWithRef(true)
+// .setShowTransientField(true).addSkippedClasses(HiveConf.class);
+ return JSONCoder.get().encode(obj, JSONCoderOption.of().setStrictOrdering(true));
+ }
+
+ @Override
+ public String getDisplayString(String[] children) {
+ return getStandardDisplayString("ToJsonUDF", children);
+ }
+}
\ No newline at end of file
diff --git a/HiveUDF/src/main/java/org/jsonex/hiveudf/UDFUtil.java b/HiveUDF/src/main/java/org/jsonex/hiveudf/UDFUtil.java
new file mode 100644
index 0000000..5983a10
--- /dev/null
+++ b/HiveUDF/src/main/java/org/jsonex/hiveudf/UDFUtil.java
@@ -0,0 +1,59 @@
+package org.jsonex.hiveudf;
+
+import lombok.SneakyThrows;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.serde2.objectinspector.*;
+import org.jsonex.core.util.ClassUtil;
+
+import java.util.*;
+
+public class UDFUtil {
+ @SneakyThrows
+ public static Map toJavaMap(GenericUDF.DeferredObject[] arguments, ObjectInspector[] inspectors) {
+ Map map = new LinkedHashMap<>();
+ for (int i = 0; i < arguments.length; i++)
+ map.put(getColumnName(arguments, i),
+ toJavaObj(
+ arguments[i].get(),
+ inspectors[i]));
+ return map;
+ }
+
+ public static String getColumnName(GenericUDF.DeferredObject[] arguments, int i) {
+ try {
+ // Use undocumented attributes, may not work for different version
+ // Defined in object: ExprNodeGenericFuncEvaluator$DeferredExprObject
+ return (String) ClassUtil.getObjectByPath(null, arguments[i], "eval.expr.column");
+ } catch (Exception e) {
+ return String.valueOf(i);
+ }
+ }
+
+ public static Object toJavaObj(Object val, ObjectInspector inspector) {
+ if (inspector instanceof PrimitiveObjectInspector)
+ return ((PrimitiveObjectInspector)inspector).getPrimitiveJavaObject(val);
+ if (inspector instanceof MapObjectInspector) {
+ Map result = new HashMap<>();
+ MapObjectInspector mapInspector = (MapObjectInspector) inspector;
+ for (Map.Entry,?> entry : mapInspector.getMap(val).entrySet())
+ result.put(toJavaObj(entry.getKey(), mapInspector.getMapKeyObjectInspector()),
+ toJavaObj(entry.getValue(), mapInspector.getMapValueObjectInspector()));
+ return result;
+ } else if (inspector instanceof ListObjectInspector) {
+ List result = new ArrayList<>();
+ ListObjectInspector listInspector = (ListObjectInspector) inspector;
+ for (Object item : listInspector.getList(val))
+ result.add(toJavaObj(item, listInspector.getListElementObjectInspector()));
+ return result;
+ } else if (inspector instanceof StructObjectInspector) {
+ Map result = new HashMap<>();
+ StructObjectInspector structInspector = (StructObjectInspector) inspector;
+ List list = structInspector.getStructFieldsDataAsList(val);
+ int i = 0;
+ for (StructField field : structInspector.getAllStructFieldRefs())
+ result.put(field.getFieldName(), toJavaObj(list.get(i++), field.getFieldObjectInspector()));
+ return result;
+ }
+ return val;
+ }
+}
diff --git a/HiveUDF/src/test/java/org/jsonex/hiveudf/TestUtil.java b/HiveUDF/src/test/java/org/jsonex/hiveudf/TestUtil.java
new file mode 100644
index 0000000..9eef7bb
--- /dev/null
+++ b/HiveUDF/src/test/java/org/jsonex/hiveudf/TestUtil.java
@@ -0,0 +1,34 @@
+package org.jsonex.hiveudf;
+
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.io.IntWritable;
+import org.apache.hadoop.io.Text;
+import org.jsonex.core.util.MapBuilder;
+import org.jsonex.jsoncoder.JSONCoder;
+import org.jsonex.jsoncoder.JSONCoderOption;
+
+import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardMapObjectInspector;
+import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector;
+import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.writableIntObjectInspector;
+import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.writableStringObjectInspector;
+import static org.jsonex.core.util.ListUtil.listOf;
+
+public class TestUtil {
+ public static Object buildMapArgs() {
+ return MapBuilder.mapOf(new Text("key"), listOf(new Text("m"), new IntWritable(10))).build();
+ }
+
+ public static ObjectInspector buildMapOI() {
+ return getStandardMapObjectInspector(
+ writableStringObjectInspector,
+ getStandardStructObjectInspector(
+ listOf("gender", "age"),
+ listOf(writableStringObjectInspector, writableIntObjectInspector))
+ );
+ }
+
+ public static String toJson(Object obj) {
+ JSONCoderOption opt = JSONCoderOption.of().setJsonOption(false, '\'', 0).setStrictOrdering(true);
+ return JSONCoder.encode(obj, opt);
+ }
+}
diff --git a/HiveUDF/src/test/java/org/jsonex/hiveudf/ToCSVUDFTest.java b/HiveUDF/src/test/java/org/jsonex/hiveudf/ToCSVUDFTest.java
new file mode 100644
index 0000000..cd21193
--- /dev/null
+++ b/HiveUDF/src/test/java/org/jsonex/hiveudf/ToCSVUDFTest.java
@@ -0,0 +1,42 @@
+package org.jsonex.hiveudf;
+
+import lombok.SneakyThrows;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF.DeferredObject;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.io.IntWritable;
+import org.apache.hadoop.io.Text;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.writableIntObjectInspector;
+import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.writableStringObjectInspector;
+import static org.jsonex.hiveudf.TestUtil.buildMapArgs;
+import static org.jsonex.hiveudf.TestUtil.buildMapOI;
+import static org.jsonex.snapshottest.Snapshot.assertMatchesSnapshot;
+
+// Reference: https://github.com/apache/hive/blob/master/ql/src/test/org/apache/hadoop/hive/ql/udf/generic/TestGenericUDFSortArray.java
+@Ignore("Failed to run on Java17 with error: java.lang.NoClassDefFoundError: Could not initialize class org.apache.hadoop.hive.common.StringInternUtils\n")
+public class ToCSVUDFTest {
+ ToCSVUDF udf = new ToCSVUDF();
+
+ @SneakyThrows
+ @Test public void testEvaluateWithoutOption() {
+ udf.initialize(new ObjectInspector[]{ buildMapOI(), writableIntObjectInspector });
+ assertMatchesSnapshot(udf.evaluate(new DeferredObject[]{
+ new GenericUDF.DeferredJavaObject(buildMapArgs()),
+ new GenericUDF.DeferredJavaObject(new IntWritable(100))
+ }));
+ }
+
+ @SneakyThrows
+ @Test public void testEvaluateWithOptions() {
+ udf.initialize(new ObjectInspector[]{writableStringObjectInspector, buildMapOI(), writableIntObjectInspector});
+ udf.getDisplayString(new String[]{ "string", "struct" });
+ assertMatchesSnapshot(udf.evaluate(new DeferredObject[]{
+ new GenericUDF.DeferredJavaObject(new Text("{fieldSep:|,quoteChar:\"\\'\"}")),
+ new GenericUDF.DeferredJavaObject(buildMapArgs()),
+ new GenericUDF.DeferredJavaObject(new IntWritable(100))
+ }));
+ }
+}
diff --git a/HiveUDF/src/test/java/org/jsonex/hiveudf/ToJsonUDFTest.java b/HiveUDF/src/test/java/org/jsonex/hiveudf/ToJsonUDFTest.java
new file mode 100644
index 0000000..d1dfeb4
--- /dev/null
+++ b/HiveUDF/src/test/java/org/jsonex/hiveudf/ToJsonUDFTest.java
@@ -0,0 +1,37 @@
+package org.jsonex.hiveudf;
+
+import lombok.SneakyThrows;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
+import org.apache.hadoop.hive.ql.udf.generic.GenericUDF.DeferredObject;
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.io.IntWritable;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.writableIntObjectInspector;
+import static org.jsonex.hiveudf.TestUtil.buildMapArgs;
+import static org.jsonex.hiveudf.TestUtil.buildMapOI;
+import static org.jsonex.snapshottest.Snapshot.assertMatchesSnapshot;
+
+// Reference: https://github.com/apache/hive/blob/master/ql/src/test/org/apache/hadoop/hive/ql/udf/generic/TestGenericUDFSortArray.java
+@Ignore("Failed to run on Java17 with error: java.lang.NoClassDefFoundError: Could not initialize class org.apache.hadoop.hive.common.StringInternUtils\n")
+public class ToJsonUDFTest {
+ ToJsonUDF udf = new ToJsonUDF();
+
+ @SneakyThrows
+ @Test public void testEvaluateSingleArg() {
+ udf.initialize(new ObjectInspector[]{ buildMapOI() });
+ assertMatchesSnapshot(udf.evaluate(new DeferredObject[]{ new GenericUDF.DeferredJavaObject(buildMapArgs()) }));
+ }
+
+ @SneakyThrows
+ @Test public void testEvaluateMultiArg() {
+ udf.initialize(new ObjectInspector[]{ buildMapOI(), writableIntObjectInspector});
+ udf.getDisplayString(new String[]{ "struct", "int" });
+ assertMatchesSnapshot(
+ udf.evaluate(new DeferredObject[]{
+ new GenericUDF.DeferredJavaObject(buildMapArgs()),
+ new GenericUDF.DeferredJavaObject(new IntWritable(100))
+ }));
+ }
+}
diff --git a/HiveUDF/src/test/java/org/jsonex/hiveudf/UDFUtilTest.java b/HiveUDF/src/test/java/org/jsonex/hiveudf/UDFUtilTest.java
new file mode 100644
index 0000000..2f0df05
--- /dev/null
+++ b/HiveUDF/src/test/java/org/jsonex/hiveudf/UDFUtilTest.java
@@ -0,0 +1,15 @@
+package org.jsonex.hiveudf;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.jsonex.hiveudf.TestUtil.*;
+import static org.junit.Assert.assertEquals;
+
+// Reference: https://github.com/apache/hive/blob/master/ql/src/test/org/apache/hadoop/hive/ql/udf/generic/TestGenericUDFSortArray.java
+@Ignore("Failed to run on Java17 with error: java.lang.NoClassDefFoundError: Could not initialize class org.apache.hadoop.hive.common.StringInternUtils\n")
+public class UDFUtilTest {
+ @Test public void testToJavaObj() {
+ assertEquals("{key:{age:10,gender:'m'}}", toJson(UDFUtil.toJavaObj(buildMapArgs(), buildMapOI())));
+ }
+}
diff --git a/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithOptions.txt b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithOptions.txt
new file mode 100644
index 0000000..e399bf1
--- /dev/null
+++ b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithOptions.txt
@@ -0,0 +1,2 @@
+'1'|'2'
+{"key":{"age":10,"gender":"m"}}|100
\ No newline at end of file
diff --git a/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithoutOption.txt b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithoutOption.txt
new file mode 100644
index 0000000..f85eb63
--- /dev/null
+++ b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToCSVUDFTest_testEvaluateWithoutOption.txt
@@ -0,0 +1,2 @@
+"0","1"
+"{""key"":{""age"":10,""gender"":""m""}}",100
\ No newline at end of file
diff --git a/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateMultiArg.txt b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateMultiArg.txt
new file mode 100644
index 0000000..5f5afe1
--- /dev/null
+++ b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateMultiArg.txt
@@ -0,0 +1 @@
+{"0":{"key":{"age":10,"gender":"m"}},"1":100}
\ No newline at end of file
diff --git a/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateSingleArg.txt b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateSingleArg.txt
new file mode 100644
index 0000000..a7e1484
--- /dev/null
+++ b/HiveUDF/src/test/resources/org/jsonex/hiveudf/__snapshot__/ToJsonUDFTest_testEvaluateSingleArg.txt
@@ -0,0 +1 @@
+{"key":{"age":10,"gender":"m"}}
\ No newline at end of file
diff --git a/JSONCoder/pom.xml b/JSONCoder/pom.xml
index 0b41e8b..3e64c9b 100644
--- a/JSONCoder/pom.xml
+++ b/JSONCoder/pom.xml
@@ -6,7 +6,7 @@
org.jsonex
jcParent
- 0.1.21
+ 0.1.27
../pom.xml
JSONCoder
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/FieldSelectOption.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/FieldSelectOption.java
new file mode 100644
index 0000000..04740ff
--- /dev/null
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/FieldSelectOption.java
@@ -0,0 +1,18 @@
+package org.jsonex.jsoncoder;
+
+import lombok.Data;
+
+@Data
+public class FieldSelectOption {
+ /** If true, when convert from a java bean, the readonly field will be ignored */
+ boolean ignoreReadOnly;
+
+ /** If true, subclass field won't be encoded */
+ boolean ignoreSubClassFields;
+
+ /** If true, for java bean type, only field include private will be returned, no setter getter method will be returned */
+ boolean showPrivateField;
+
+ /** by default, transientField won't be serialized. Set this to true will serialize it */
+ boolean showTransientField;
+}
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoder.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoder.java
index 826bc3b..ee47c57 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoder.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoder.java
@@ -29,6 +29,9 @@ public class JSONCoder {
public static JSONCoder get() { return getGlobal(); }
+ public static String stringify(Object obj) { return get().encode(obj); }
+ public static String stringify(Object obj, int indentFact) { return encode(obj, JSONCoderOption.ofIndentFactor(indentFact)); }
+ public static T parse(String str, Class cls) { return get().decode(str, cls); }
@SuppressWarnings("unchecked")
public static T decode(DecodeReq req, JSONCoderOption opt) {
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoderOption.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoderOption.java
index 6a4b9a0..5008805 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoderOption.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/JSONCoderOption.java
@@ -13,36 +13,54 @@
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
-import org.jsonex.core.factory.CacheThreadLocal;
import org.jsonex.core.factory.InjectableFactory;
+import org.jsonex.core.factory.ScopeThreadLocal;
import org.jsonex.core.type.Tuple;
import org.jsonex.core.type.Tuple.Pair;
+import org.jsonex.core.type.Union.Union2;
import org.jsonex.core.util.ClassUtil;
-import org.jsonex.jsoncoder.coder.*;
+import static org.jsonex.core.util.LangUtil.doIfElse;
+import static org.jsonex.core.util.LangUtil.orElse;
+import static org.jsonex.core.util.LangUtil.safe;
+import static org.jsonex.core.util.ListUtil.listOf;
+import org.jsonex.jsoncoder.coder.CoderAtomicBoolean;
+import org.jsonex.jsoncoder.coder.CoderAtomicInteger;
+import org.jsonex.jsoncoder.coder.CoderBigInteger;
+import org.jsonex.jsoncoder.coder.CoderClass;
+import org.jsonex.jsoncoder.coder.CoderDate;
+import org.jsonex.jsoncoder.coder.CoderEnum;
+import org.jsonex.jsoncoder.coder.CoderURI;
+import org.jsonex.jsoncoder.coder.CoderURL;
+import org.jsonex.jsoncoder.coder.CoderXMLGregorianCalendar;
import org.jsonex.jsoncoder.fieldTransformer.FieldTransformer;
import org.jsonex.jsoncoder.fieldTransformer.FieldTransformer.FieldInfo;
+import static org.jsonex.jsoncoder.fieldTransformer.FieldTransformer.exclude;
+import org.jsonex.treedoc.TDNode;
import org.jsonex.treedoc.json.TDJSONOption;
import org.slf4j.Logger;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
-import static org.jsonex.core.util.LangUtil.*;
-import static org.jsonex.core.util.ListUtil.listOf;
-import static org.jsonex.jsoncoder.fieldTransformer.FieldTransformer.exclude;
-
@SuppressWarnings("UnusedReturnValue")
@Accessors(chain=true) @Slf4j
public class JSONCoderOption {
@Getter final static JSONCoderOption global = new JSONCoderOption(null);
static {
global.addCoder(CoderDate.get(), CoderEnum.get(), CoderXMLGregorianCalendar.get(), CoderAtomicInteger.get(),
- CoderBigInteger.get(), CoderClass.get(), CoderURI.get(), CoderURL.get());
+ CoderAtomicBoolean.get(), CoderBigInteger.get(), CoderClass.get(), CoderURI.get(), CoderURL.get());
- global.addSkippedClasses(Format.class);
+ global.addSkippedClasses(Format.class, ClassLoader.class);
global.fallbackDateFormats.add("yyyy-MM-dd HH:mm:ss.SSS.Z"); //Just for backward compatibility.
global.fallbackDateFormats.add("yyyy/MM/dd HH:mm:ss.SSS.Z");
global.fallbackDateFormats.add("yyyy-MM-dd HH:mm:ss.SSS");
@@ -61,16 +79,6 @@ public class JSONCoderOption {
@Getter @Setter int maxDepth = 30;
@Getter @Setter int maxElementsPerNode = 2000;
- /**
- * If true, when convert from an java bean, the readonly field will be ignored
- */
- @Getter @Setter boolean ignoreReadOnly;
-
- /**
- * If true, subclass field won't be encoded
- */
- @Getter @Setter boolean ignoreSubClassFields;
-
/**
* If true, enum name will be encoded
*/
@@ -85,12 +93,7 @@ public class JSONCoderOption {
* If true, duplicated object will be serialized as a reference to existing object's hash
*/
@Getter @Setter boolean dedupWithRef;
-
- /**
- * If true, for java bean type, only field include private will be returned, no setter getter method will be returned.
- */
- @Getter @Setter boolean showPrivateField;
-
+
@Getter @Setter String parsingDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
/**
* Used by {@link CoderDate}.encode()}, If Date format is null, date will be encoded as long with value of Date.getTime()
@@ -102,7 +105,7 @@ public class JSONCoderOption {
// For performance reason, we need to cache SimpleDateFormat in the same thread as SimpleDateFormat is not threadsafe
private static final InjectableFactory._2 dateFormatCache =
- InjectableFactory._2.of(JSONCoderOption::buildDateFormat, CacheThreadLocal.get());
+ InjectableFactory._2.of(JSONCoderOption::buildDateFormat, ScopeThreadLocal.get());
public SimpleDateFormat getCachedParsingDateFormat() { return getCachedDateFormat(parsingDateFormat); }
public SimpleDateFormat getCachedDateFormat() { return getCachedDateFormat(dateFormat); }
@@ -135,6 +138,8 @@ private static SimpleDateFormat buildDateFormat(String format, TimeZone timeZone
@Getter private final List, FieldTransformer>> filters = new ArrayList<>();
+ @Getter private final List, String>, FieldSelectOption>> fieldSelectOptions = new ArrayList<>();
+ @Getter FieldSelectOption defaultFieldSelectOption = new FieldSelectOption();
@Getter private final List> coderList = new ArrayList<>();
/**
@@ -142,14 +147,14 @@ private static SimpleDateFormat buildDateFormat(String format, TimeZone timeZone
* e.g. BO object as there are no proper implementation of equals and hashCode
* which could cause duplicated copy of Object to be output.
*
- * The priority is based on the index of the wrapper. So if want to add highest priority
- * need to use equalsWrapper.add(0, wrapper).
+ * The priority is based on the index of the wrapper. So to add for the highest priority
+ * it needs to use equalsWrapper.add(0, wrapper).
*
*/
@Getter private final List> equalsWrapper = new ArrayList<>();
// JSON coder config
- @Getter @Setter private TDJSONOption jsonOption = new TDJSONOption();
+ @Getter @Setter private TDJSONOption jsonOption = TDJSONOption.ofDefaultRootType(TDNode.Type.SIMPLE);
public enum LogLevel {
OFF { public void log(Logger log, String msg, Throwable e) { /* Noop */ }},
@@ -163,21 +168,27 @@ public enum LogLevel {
@Getter @Setter private LogLevel warnLogLevel = LogLevel.INFO;
/**
- * Accept specified sub-class using `$type` attribute. This feature is disabled by default for security reason
+ * Accept specified subclass using `$type` attribute. This feature is disabled by default for security reason
*/
@Getter @Setter private boolean allowPolymorphicClasses = false;
/**
- * Merge array. By default, when decode to exiting object, array or collection will be override instead of merge.
+ * Merge array. By default, when decode to exiting object, array or collection will be overridden instead of merge.
* If this set true, it will merge the array (concatenation)
*/
@Getter @Setter private boolean mergeArray = false;
/**
* As Java Map and Set implementation, the order may not be strictly consistent cross JVM implementation
- * set this to true, it will sort the keys in a predicated order
+ * set this to true, it will sort the keys in a deterministic order
*/
- @Getter @Setter private boolean strictOrder = false;
+ @Getter @Setter private boolean sortMapAndSet = false;
+
+ /**
+ * In JVM implementation, Object getter method iteration order is not deterministic. Set this to true, it will
+ * order the object keys. It won't sort Map's key order, for map or set ordering, please use sortMapAndSet
+ */
+ @Getter @Setter private boolean sortObjectKeys = false;
public JSONCoderOption() { this(global); }
private JSONCoderOption(JSONCoderOption parent) { this.parent = parent; }
@@ -185,7 +196,11 @@ public enum LogLevel {
public static JSONCoderOption ofIndentFactor(int factor) {
return new JSONCoderOption().setJsonOption(TDJSONOption.ofIndentFactor(factor));
}
-
+
+ public JSONCoderOption setStrictOrdering(boolean value) {
+ return setSortMapAndSet(value).setSortObjectKeys(value);
+ }
+
ICoder> findCoder(Class> cls){
for (ICoder> bc : coderList){
if(bc.getType().isAssignableFrom(cls))
@@ -210,10 +225,10 @@ public boolean isClassSkipped(Class> cls) {
public FieldInfo transformField(Class> cls, FieldInfo fieldInfo, BeanCoderContext ctx) {
for (Pair, FieldTransformer> filter : filters) {
- if (!filter._1.isAssignableFrom(cls))
+ if (!filter._0.isAssignableFrom(cls))
continue;
// TODO: Fix when to stop the filter chain strategy
- fieldInfo = filter._2.apply(fieldInfo, ctx);
+ fieldInfo = filter._1.apply(fieldInfo, ctx);
}
return parent == null ? fieldInfo : parent.transformField(cls, fieldInfo, ctx);
@@ -221,9 +236,9 @@ public FieldInfo transformField(Class> cls, FieldInfo fieldInfo, BeanCoderCont
public boolean isExcluded(Class> cls, String name, BeanCoderContext ctx) {
for (Pair, FieldTransformer> filter : filters) {
- if (!filter._1.isAssignableFrom(cls))
+ if (!filter._0.isAssignableFrom(cls))
continue;
- if (!filter._2.shouldInclude(name, ctx))
+ if (!filter._1.shouldInclude(name, ctx))
return true;
}
return parent == null ? false : parent.isExcluded(cls, name, ctx);
@@ -250,16 +265,6 @@ public Date parseDateFullback(String dateStr) throws ParseException {
throw exp == null ? new ParseException(dateStr, 0) : exp;
return parent.parseDateFullback(dateStr);
}
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public boolean isIgnoreSubClassFields(Class> cls){
- if(ignoreSubClassFields)
- return true;
- for(Class iCls : ignoreSubClassFieldsClasses)
- if(iCls.isAssignableFrom(cls))
- return true;
- return parent != null && parent.isIgnoreSubClassFields(cls);
- }
public JSONCoderOption addDefaultFilter(FieldTransformer filter) {
return addFilterFor(Object.class, filter, false);
@@ -306,10 +311,58 @@ public JSONCoderOption addCoder(ICoder>... codes) {
return this;
}
+ // Keep this for backward compatible
public JSONCoderOption setJsonOption(boolean alwaysQuoteName, char quoteChar, int indentFactor) {
- jsonOption.setAlwaysQuoteName(alwaysQuoteName)
- .setQuoteChar(quoteChar)
+ return setJsonOption(alwaysQuoteName, String.valueOf(quoteChar), indentFactor);
+ }
+
+ public JSONCoderOption setJsonOption(boolean alwaysQuoteName, String quoteChars, int indentFactor) {
+ jsonOption.setAlwaysQuoteKey(alwaysQuoteName)
+ .setQuoteChars(quoteChars)
.setIndentFactor(indentFactor);
return this;
}
+
+ public FieldSelectOption getFieldSelectOption(Class> cls) {
+ for (Pair, String>, FieldSelectOption> opt : fieldSelectOptions) {
+ if (matches(opt._0, cls))
+ return opt._1;
+ }
+ return defaultFieldSelectOption;
+ }
+
+ private static boolean matches(Union2, String> key, Class> cls) {
+ if (key._0 != null)
+ return key._0.isAssignableFrom(cls);
+ // Package
+ String pkg = key._1;
+ String clsPkg = cls.getPackage().getName();
+ // TODO: optimize to avoid create lots of string object with substring
+ return pkg.endsWith("*") ? clsPkg.startsWith(pkg.substring(0, pkg.length() - 1)) : clsPkg == pkg;
+ }
+
+ public JSONCoderOption addFieldSelectOptionFor(Class> cls, FieldSelectOption filter) {
+ return addFieldSelectOptionFor(cls, filter, false);
+ }
+
+ public JSONCoderOption addFieldSelectOptionFor(Class> cls, FieldSelectOption opt, boolean last) {
+ Pair, String>, FieldSelectOption> clsOpt = Pair.of(Union2.of_0(cls), opt);
+ doIfElse(last, () -> fieldSelectOptions.add(clsOpt), () -> fieldSelectOptions.add(0, clsOpt));
+ return this;
+ }
+
+ public JSONCoderOption addFieldSelectOptionForPackage(String pkg, FieldSelectOption filter) {
+ return addFieldSelectOptionForPackage(pkg, filter, false);
+ }
+
+ public JSONCoderOption addFieldSelectOptionForPackage(String pkg, FieldSelectOption opt, boolean last) {
+ Pair, String>, FieldSelectOption> clsOpt = Pair.of(Union2.of_1(pkg), opt);
+ doIfElse(last, () -> fieldSelectOptions.add(clsOpt), () -> fieldSelectOptions.add(0, clsOpt));
+ return this;
+ }
+
+ public JSONCoderOption setIgnoreReadOnly(boolean v) { defaultFieldSelectOption.setIgnoreReadOnly(v); return this;}
+ public JSONCoderOption setShowPrivateField(boolean v) { defaultFieldSelectOption.setShowPrivateField(v); return this;}
+ public JSONCoderOption setShowTransientField(boolean v) { defaultFieldSelectOption.setShowTransientField(v); return this;}
+ public JSONCoderOption setIgnoreSubClassFields(boolean v) { defaultFieldSelectOption.setIgnoreSubClassFields(v); return this;}
}
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderArray.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderArray.java
index 933882d..87cab2c 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderArray.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderArray.java
@@ -31,6 +31,8 @@ public class CoderArray implements ICoder {
public TDNode encode(Object obj, Type type, BeanCoderContext ctx, TDNode target) {
target.setType(TDNode.Type.ARRAY);
Class> cls = ClassUtil.getGenericClass(type);
+ if (cls == null)
+ cls = obj.getClass();
for (int i = 0; i < Array.getLength(obj) && i < ctx.getOption().getMaxElementsPerNode(); i++)
ctx.encode(Array.get(obj, i), cls.getComponentType(), target.createChild());
return target;
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderAtomicBoolean.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderAtomicBoolean.java
new file mode 100644
index 0000000..9f335e4
--- /dev/null
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderAtomicBoolean.java
@@ -0,0 +1,31 @@
+/*************************************************************
+ Copyright 2018-2019 eBay Inc.
+ Author/Developer: Jianwu Chen
+
+ Use of this source code is governed by an MIT-style
+ license that can be found in the LICENSE file or at
+ https://opensource.org/licenses/MIT.
+ ************************************************************/
+
+package org.jsonex.jsoncoder.coder;
+
+import org.jsonex.core.factory.InjectableInstance;
+import org.jsonex.jsoncoder.BeanCoderContext;
+import org.jsonex.jsoncoder.ICoder;
+import org.jsonex.treedoc.TDNode;
+
+import java.lang.reflect.Type;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CoderAtomicBoolean implements ICoder {
+ public static final InjectableInstance it = InjectableInstance.of(CoderAtomicBoolean.class);
+ public static CoderAtomicBoolean get() { return it.get(); }
+
+ @Override public Class getType() {return AtomicBoolean.class;}
+
+ @Override public TDNode encode(AtomicBoolean obj, Type type, BeanCoderContext context, TDNode target) { return target.setValue(obj.get()); }
+
+ @Override public AtomicBoolean decode(TDNode tdNode, Type type, Object targetObj, BeanCoderContext context) {
+ return new AtomicBoolean((boolean)tdNode.getValue());
+ }
+}
\ No newline at end of file
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderCollection.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderCollection.java
index 454440b..5d518cb 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderCollection.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderCollection.java
@@ -31,7 +31,7 @@ public class CoderCollection implements ICoder {
@Override public TDNode encode(Collection obj, Type type, BeanCoderContext ctx, TDNode target) {
target.setType(TDNode.Type.ARRAY);
- if (ctx.getOption().isStrictOrder()
+ if (ctx.getOption().isSortMapAndSet()
&& obj instanceof Set && !(obj instanceof SortedSet || obj instanceof LinkedHashSet || obj instanceof EnumSet)) {
Set set = new TreeSet(FullbackComparator.it); // Due to instability of Set iteration order, we copy it to TreeSet to make iteration stable
set.addAll(obj);
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderMap.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderMap.java
index 0fa3c99..3f3d7e3 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderMap.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderMap.java
@@ -40,7 +40,7 @@ public class CoderMap implements ICoder {
JSONCoderOption opt = ctx.getOption();
Map,?> map = (Map,?>)obj;
- if (opt.isStrictOrder()
+ if (opt.isSortMapAndSet()
&& !(map instanceof SortedMap) && ! (map instanceof LinkedHashMap) && ! (map instanceof EnumMap)) {
TreeMap treeMap = new TreeMap<>(FullbackComparator.it); // Due to instability of map iterator, we copy it to TreeMap to make it in stable order.
treeMap.putAll(map);
diff --git a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderObject.java b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderObject.java
index ba34e41..3e8e644 100644
--- a/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderObject.java
+++ b/JSONCoder/src/main/java/org/jsonex/jsoncoder/coder/CoderObject.java
@@ -15,10 +15,7 @@
import org.jsonex.core.util.BeanProperty;
import org.jsonex.core.util.ClassUtil;
import org.jsonex.core.util.StringUtil;
-import org.jsonex.jsoncoder.BeanCoderContext;
-import org.jsonex.jsoncoder.BeanCoderException;
-import org.jsonex.jsoncoder.ICoder;
-import org.jsonex.jsoncoder.JSONCoderOption;
+import org.jsonex.jsoncoder.*;
import org.jsonex.jsoncoder.fieldTransformer.FieldTransformer.FieldInfo;
import org.jsonex.treedoc.TDNode;
import org.jsonex.treedoc.json.TDJSONWriter;
@@ -30,6 +27,7 @@
import java.util.Date;
import java.util.Map;
import java.util.Objects;
+import java.util.TreeMap;
import java.util.function.Function;
import static org.jsonex.core.util.LangUtil.getIfInstanceOf;
@@ -49,21 +47,24 @@ public class CoderObject implements ICoder {
JSONCoderOption opt = ctx.getOption();
Class> cls = obj.getClass(); // Use the real object;
- if (opt.isIgnoreSubClassFields(cls) && type != null)
+ FieldSelectOption selectOpt = opt.getFieldSelectOption(cls);
+ if (selectOpt.isIgnoreSubClassFields() && type != null)
cls = ClassUtil.getGenericClass(type);
if (opt.isShowType() || cls != obj.getClass())
target.createChild(TYPE_KEY).setValue(obj.getClass().getName());
Map pds = ClassUtil.getProperties(cls);
+ if (opt.isSortObjectKeys())
+ pds = new TreeMap<>(pds);
for (BeanProperty pd : pds.values()) {
- if (!pd.isReadable(opt.isShowPrivateField()))
+ if (!pd.isReadable(selectOpt.isShowPrivateField()))
continue;
- if (pd.isImmutable(opt.isShowPrivateField()) && opt.isIgnoreReadOnly())
+ if (pd.isImmutable(selectOpt.isShowPrivateField()) && selectOpt.isIgnoreReadOnly())
continue; // Only mutable attribute will be encoded
- if (pd.isTransient())
+ if (pd.isTransient() && !selectOpt.isShowTransientField())
continue;
if (opt.isExcluded(cls, pd.getName(), ctx))
diff --git a/JSONCoder/src/test/java/org/jsonex/jsoncoder/JSONCoderTest.java b/JSONCoder/src/test/java/org/jsonex/jsoncoder/JSONCoderTest.java
index 8af349f..6041825 100644
--- a/JSONCoder/src/test/java/org/jsonex/jsoncoder/JSONCoderTest.java
+++ b/JSONCoder/src/test/java/org/jsonex/jsoncoder/JSONCoderTest.java
@@ -43,7 +43,8 @@ public class JSONCoderTest {
private static String toJSONString(Object obj, JSONCoderOption option) { return JSONCoder.encode(obj, option); }
private static String toJSONString(Object obj) {
- return JSONCoder.global.encode(obj, JSONCoderOption.ofIndentFactor(2).setWarnLogLevel(JSONCoderOption.LogLevel.DEBUG));
+ return JSONCoder.global.encode(obj, JSONCoderOption.ofIndentFactor(2).
+ setStrictOrdering(true).setWarnLogLevel(JSONCoderOption.LogLevel.DEBUG));
}
@Before public void before() {
@@ -123,10 +124,14 @@ private TestBean buildTestBean() {
assertEquals(str, toJSONString(tb1));
}
+ @Test public void testEncodeArray() {
+ assertEquals("[1,2,3]", JSONCoder.get().encode(new int[]{1,2,3}));
+ }
+
@Test public void testCyclicReference() {
TestBean tb = new TestBean().setBean2(new TestBean2());
tb.getBean2().testBean = tb;
- String str = toJSONString(tb, new JSONCoderOption().setJsonOption(false, '`', 2));
+ String str = toJSONString(tb, new JSONCoderOption().setJsonOption(false, '`', 2).setStrictOrdering(true));
assertMatchesSnapshot(str);
assertTrue("Cyclic reference should be encoded as $ref:str=" + str, str.indexOf("testBean:{\n $ref:`../../`\n") > 0);
@@ -140,7 +145,8 @@ private TestBean buildTestBean() {
tb.bean2List = Collections.singletonList(tb2);
tb2.setInts(tb.getInts()); // Duplicated arrays
- String str = toJSONString(tb, JSONCoderOption.of().setDedupWithRef(true).setJsonOption(false, '"', 2));
+ String str = toJSONString(tb, JSONCoderOption.of().setDedupWithRef(true)
+ .setJsonOption(false, '"', 2).setStrictOrdering(true));
assertMatchesSnapshot(str);
assertTrue("Generate ref if dedupWithRef is set", str.contains("$ref"));
@@ -150,7 +156,7 @@ private TestBean buildTestBean() {
}
@Test public void testEnumNameOption() {
- JSONCoderOption codeOption = JSONCoderOption.ofIndentFactor(2).setShowEnumName(true);
+ JSONCoderOption codeOption = JSONCoderOption.ofIndentFactor(2).setShowEnumName(true).setStrictOrdering(true);
String str = toJSONString(buildTestBean(), codeOption);
assertMatchesSnapshot(str);
assertTrue("Should contain both enum id and name when showEnumName is set", str.indexOf("12345-value1") > 0);
@@ -158,7 +164,7 @@ private TestBean buildTestBean() {
}
@Test public void testCustomQuote() {
- JSONCoderOption codeOption = JSONCoderOption.ofIndentFactor(2);
+ JSONCoderOption codeOption = JSONCoderOption.ofIndentFactor(2).setStrictOrdering(true);
codeOption.getJsonOption().setQuoteChar('\'');
String str = toJSONString(buildTestBean(), codeOption);
assertMatchesSnapshot("strWithSingleQuote", str);
@@ -166,7 +172,7 @@ private TestBean buildTestBean() {
str.indexOf("'String1: \\'\"'") > 0);
assertEquals(toJSONString(JSONCoder.global.decode(str, TestBean.class), codeOption), str);
- codeOption.getJsonOption().setAlwaysQuoteName(false); // Make quote optional for attribute names
+ codeOption.getJsonOption().setAlwaysQuoteKey(false); // Make quote optional for attribute names
str = toJSONString(buildTestBean(), codeOption);
assertMatchesSnapshot("strWithNoKeyQuote", str);
@@ -348,7 +354,7 @@ private SimpleDateFormat buildDateFormat(String format) {
bean.testBean.setStrField("str2");
bean.testBean.publicStrField = "publicStr";
- JSONCoderOption opt = JSONCoderOption.ofIndentFactor(2);
+ JSONCoderOption opt = JSONCoderOption.ofIndentFactor(2).setStrictOrdering(true);
opt.addFilterFor(TestBean2.class, include("ints", "enumField2", "testBean", "strField"));
@@ -487,21 +493,25 @@ static class ClsWithTypeVar {
ClsWithTypeVar bean = new ClsWithTypeVar();
bean.listOfMap.add(new MapBuilder("str1", 1).getMap());
bean.refOfMap.set(new MapBuilder("str1", 1).getMap());
- String json = JSONCoder.global.encode(bean);
+ String json = toJSONString(bean);
assertMatchesSnapshot(json);
ClsWithTypeVar bean1 = JSONCoder.global.decode(json, ClsWithTypeVar.class);
- String json1 = JSONCoder.global.encode(bean1);
+ String json1 = toJSONString(bean1);
assertEquals(json, json1);
}
- @Test public void testStrictOrder() {
+ @Test public void testSortMapAndSet() {
Set set = new HashSet<>(listOf(new NoneComparable("a"), new NoneComparable("b"), new NoneComparable("c"), new NoneComparable("d")));
- assertMatchesSnapshot("set", JSONCoder.encode(set, JSONCoderOption.of().setStrictOrder(true)));
+ assertMatchesSnapshot("set", JSONCoder.encode(set, JSONCoderOption.of().setStrictOrdering(true)));
Map map = new HashMap<>();
map.put(new NoneComparable("a"), "value 1");
map.put(new NoneComparable("b"), "value 2");
- assertMatchesSnapshot("map", JSONCoder.encode(map, JSONCoderOption.of().setStrictOrder(true)));
+ assertMatchesSnapshot("map", JSONCoder.encode(map, JSONCoderOption.of().setStrictOrdering(true)));
+ }
+
+ @Test public void testSortObjectKey() {
+ assertMatchesSnapshot(JSONCoder.encode(buildTestBean(), JSONCoderOption.of().setStrictOrdering(true)));
}
private void expectDecodeWithException(String str, Class> cls, String expectedError) {
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding.txt
index 9685d98..a43483e 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding.txt
@@ -1,75 +1,75 @@
{
- "fieldInBaseClass":"Overridden Value",
- "treeMap":{
- "key1":"value1"
- },
- "linkedList1":[
- "value1"
- ],
- "intField":100,
- "floatField":1.4,
- "charField":"A",
- "booleanField":false,
- "strField":"String1: '\"",
- "dateField":"1970-01-01T03:23:32.121Z",
+ "atomicInteger":1001,
"bean2":{
"enumField":"value2",
"enumField2":12345,
- "strEnum":"strEnumV1",
+ "enumMap":{
+ "value1":"MapValue1"
+ },
"enumSet":[
"value1",
"value2"
],
- "enumMap":{
- "value1":"MapValue1"
- },
+ "strEnum":"strEnumV1",
"testBean":{
"$ref":"../../"
}
},
- "ints":[
- 4,
- 3,
- 2,
- 1
+ "bean2List":[
+ {
+ "enumMap":{},
+ "enumSet":[
+ "value1",
+ "value2"
+ ],
+ "strField":"AAA"
+ },
+ {
+ "enumMap":{},
+ "enumSet":[
+ "value1",
+ "value2"
+ ],
+ "strField":"BBB"
+ }
],
"bean2s":[
{
- "strField":"1",
+ "enumMap":{},
"enumSet":[
"value1",
"value2"
],
- "enumMap":{}
+ "strField":"1"
},
{
- "strField":"2",
+ "enumMap":{},
"enumSet":[
"value1",
"value2"
],
- "enumMap":{}
+ "strField":"2"
}
],
- "readonlyField":"This's a readonly field",
- "atomicInteger":1001,
"bigInteger":"123456789012345678901234567890",
- "someClass":"java.util.Date",
- "xmlCalendar":"1970-01-01T00:00:00.000Z",
+ "booleanField":false,
+ "charField":"A",
+ "dateField":"1970-01-01T03:23:32.121Z",
"dateNumberMap":[
null
],
- "publicLinkedList":[
+ "fieldInBaseClass":"Overridden Value",
+ "floatField":1.4,
+ "intField":100,
+ "ints":[
+ 4,
+ 3,
+ 2,
+ 1
+ ],
+ "linkedList1":[
"value1"
],
- "publicTreeMap":{
- "key1":"value1"
- },
- "publicStrField":"PublicString",
- "publicMap":{
- "key1":"1970-01-01T00:00:00.000Z",
- "key2":"1970-01-01T00:20:12.121Z"
- },
"publicBigDecimal1":"123456789012345678901234567",
"publicBigDecimal2":"12",
"publicInts":[
@@ -78,26 +78,26 @@
2,
1
],
- "bean2List":[
- {
- "strField":"AAA",
- "enumSet":[
- "value1",
- "value2"
- ],
- "enumMap":{}
- },
- {
- "strField":"BBB",
- "enumSet":[
- "value1",
- "value2"
- ],
- "enumMap":{}
- }
+ "publicLinkedList":[
+ "value1"
],
+ "publicMap":{
+ "key1":"1970-01-01T00:00:00.000Z",
+ "key2":"1970-01-01T00:20:12.121Z"
+ },
+ "publicStrField":"PublicString",
"publicStringSet":[
"str1",
"str2"
- ]
+ ],
+ "publicTreeMap":{
+ "key1":"value1"
+ },
+ "readonlyField":"This's a readonly field",
+ "someClass":"java.util.Date",
+ "strField":"String1: '\"",
+ "treeMap":{
+ "key1":"value1"
+ },
+ "xmlCalendar":"1970-01-01T00:00:00.000Z"
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding_testBean.toString.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding_testBean.toString.txt
new file mode 100644
index 0000000..39a75fa
--- /dev/null
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testBasicEncoding_testBean.toString.txt
@@ -0,0 +1 @@
+org.jsonex.jsoncoder.TestBean@6a396c1e
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithBackQuote.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithBackQuote.txt
index 4f464f4..b030d7c 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithBackQuote.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithBackQuote.txt
@@ -1,75 +1,75 @@
{
- fieldInBaseClass:`Overridden Value`,
- treeMap:{
- key1:`value1`
- },
- linkedList1:[
- `value1`
- ],
- intField:100,
- floatField:1.4,
- charField:`A`,
- booleanField:false,
- strField:`String1: '"`,
- dateField:`1970-01-01T03:23:32.121Z`,
+ atomicInteger:1001,
bean2:{
enumField:`value2`,
enumField2:12345,
- strEnum:`strEnumV1`,
+ enumMap:{
+ value1:`MapValue1`
+ },
enumSet:[
`value1`,
`value2`
],
- enumMap:{
- value1:`MapValue1`
- },
+ strEnum:`strEnumV1`,
testBean:{
$ref:`../../`
}
},
- ints:[
- 4,
- 3,
- 2,
- 1
+ bean2List:[
+ {
+ enumMap:{},
+ enumSet:[
+ `value1`,
+ `value2`
+ ],
+ strField:`AAA`
+ },
+ {
+ enumMap:{},
+ enumSet:[
+ `value1`,
+ `value2`
+ ],
+ strField:`BBB`
+ }
],
bean2s:[
{
- strField:`1`,
+ enumMap:{},
enumSet:[
`value1`,
`value2`
],
- enumMap:{}
+ strField:`1`
},
{
- strField:`2`,
+ enumMap:{},
enumSet:[
`value1`,
`value2`
],
- enumMap:{}
+ strField:`2`
}
],
- readonlyField:`This's a readonly field`,
- atomicInteger:1001,
bigInteger:`123456789012345678901234567890`,
- someClass:`java.util.Date`,
- xmlCalendar:`1970-01-01T00:00:00.000Z`,
+ booleanField:false,
+ charField:`A`,
+ dateField:`1970-01-01T03:23:32.121Z`,
dateNumberMap:[
null
],
- publicLinkedList:[
+ fieldInBaseClass:`Overridden Value`,
+ floatField:1.4,
+ intField:100,
+ ints:[
+ 4,
+ 3,
+ 2,
+ 1
+ ],
+ linkedList1:[
`value1`
],
- publicTreeMap:{
- key1:`value1`
- },
- publicStrField:`PublicString`,
- publicMap:{
- key1:`1970-01-01T00:00:00.000Z`,
- key2:`1970-01-01T00:20:12.121Z`
- },
publicBigDecimal1:`123456789012345678901234567`,
publicBigDecimal2:`12`,
publicInts:[
@@ -78,26 +78,26 @@
2,
1
],
- bean2List:[
- {
- strField:`AAA`,
- enumSet:[
- `value1`,
- `value2`
- ],
- enumMap:{}
- },
- {
- strField:`BBB`,
- enumSet:[
- `value1`,
- `value2`
- ],
- enumMap:{}
- }
+ publicLinkedList:[
+ `value1`
],
+ publicMap:{
+ key1:`1970-01-01T00:00:00.000Z`,
+ key2:`1970-01-01T00:20:12.121Z`
+ },
+ publicStrField:`PublicString`,
publicStringSet:[
`str1`,
`str2`
- ]
+ ],
+ publicTreeMap:{
+ key1:`value1`
+ },
+ readonlyField:`This's a readonly field`,
+ someClass:`java.util.Date`,
+ strField:`String1: '"`,
+ treeMap:{
+ key1:`value1`
+ },
+ xmlCalendar:`1970-01-01T00:00:00.000Z`
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithNoKeyQuote.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithNoKeyQuote.txt
index 16eb0a8..7734957 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithNoKeyQuote.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithNoKeyQuote.txt
@@ -1,75 +1,75 @@
{
- fieldInBaseClass:'Overridden Value',
- treeMap:{
- key1:'value1'
- },
- linkedList1:[
- 'value1'
- ],
- intField:100,
- floatField:1.4,
- charField:'A',
- booleanField:false,
- strField:'String1: \'"',
- dateField:'1970-01-01T03:23:32.121Z',
+ atomicInteger:1001,
bean2:{
enumField:'value2',
enumField2:12345,
- strEnum:'strEnumV1',
+ enumMap:{
+ value1:'MapValue1'
+ },
enumSet:[
'value1',
'value2'
],
- enumMap:{
- value1:'MapValue1'
- },
+ strEnum:'strEnumV1',
testBean:{
$ref:'../../'
}
},
- ints:[
- 4,
- 3,
- 2,
- 1
+ bean2List:[
+ {
+ enumMap:{},
+ enumSet:[
+ 'value1',
+ 'value2'
+ ],
+ strField:'AAA'
+ },
+ {
+ enumMap:{},
+ enumSet:[
+ 'value1',
+ 'value2'
+ ],
+ strField:'BBB'
+ }
],
bean2s:[
{
- strField:'1',
+ enumMap:{},
enumSet:[
'value1',
'value2'
],
- enumMap:{}
+ strField:'1'
},
{
- strField:'2',
+ enumMap:{},
enumSet:[
'value1',
'value2'
],
- enumMap:{}
+ strField:'2'
}
],
- readonlyField:'This\'s a readonly field',
- atomicInteger:1001,
bigInteger:'123456789012345678901234567890',
- someClass:'java.util.Date',
- xmlCalendar:'1970-01-01T00:00:00.000Z',
+ booleanField:false,
+ charField:'A',
+ dateField:'1970-01-01T03:23:32.121Z',
dateNumberMap:[
null
],
- publicLinkedList:[
+ fieldInBaseClass:'Overridden Value',
+ floatField:1.4,
+ intField:100,
+ ints:[
+ 4,
+ 3,
+ 2,
+ 1
+ ],
+ linkedList1:[
'value1'
],
- publicTreeMap:{
- key1:'value1'
- },
- publicStrField:'PublicString',
- publicMap:{
- key1:'1970-01-01T00:00:00.000Z',
- key2:'1970-01-01T00:20:12.121Z'
- },
publicBigDecimal1:'123456789012345678901234567',
publicBigDecimal2:'12',
publicInts:[
@@ -78,26 +78,26 @@
2,
1
],
- bean2List:[
- {
- strField:'AAA',
- enumSet:[
- 'value1',
- 'value2'
- ],
- enumMap:{}
- },
- {
- strField:'BBB',
- enumSet:[
- 'value1',
- 'value2'
- ],
- enumMap:{}
- }
+ publicLinkedList:[
+ 'value1'
],
+ publicMap:{
+ key1:'1970-01-01T00:00:00.000Z',
+ key2:'1970-01-01T00:20:12.121Z'
+ },
+ publicStrField:'PublicString',
publicStringSet:[
'str1',
'str2'
- ]
+ ],
+ publicTreeMap:{
+ key1:'value1'
+ },
+ readonlyField:'This\'s a readonly field',
+ someClass:'java.util.Date',
+ strField:'String1: \'"',
+ treeMap:{
+ key1:'value1'
+ },
+ xmlCalendar:'1970-01-01T00:00:00.000Z'
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithSingleQuote.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithSingleQuote.txt
index a9b1e5a..5c2809e 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithSingleQuote.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCustomQuote_strWithSingleQuote.txt
@@ -1,75 +1,75 @@
{
- 'fieldInBaseClass':'Overridden Value',
- 'treeMap':{
- 'key1':'value1'
- },
- 'linkedList1':[
- 'value1'
- ],
- 'intField':100,
- 'floatField':1.4,
- 'charField':'A',
- 'booleanField':false,
- 'strField':'String1: \'"',
- 'dateField':'1970-01-01T03:23:32.121Z',
+ 'atomicInteger':1001,
'bean2':{
'enumField':'value2',
'enumField2':12345,
- 'strEnum':'strEnumV1',
+ 'enumMap':{
+ 'value1':'MapValue1'
+ },
'enumSet':[
'value1',
'value2'
],
- 'enumMap':{
- 'value1':'MapValue1'
- },
+ 'strEnum':'strEnumV1',
'testBean':{
'$ref':'../../'
}
},
- 'ints':[
- 4,
- 3,
- 2,
- 1
+ 'bean2List':[
+ {
+ 'enumMap':{},
+ 'enumSet':[
+ 'value1',
+ 'value2'
+ ],
+ 'strField':'AAA'
+ },
+ {
+ 'enumMap':{},
+ 'enumSet':[
+ 'value1',
+ 'value2'
+ ],
+ 'strField':'BBB'
+ }
],
'bean2s':[
{
- 'strField':'1',
+ 'enumMap':{},
'enumSet':[
'value1',
'value2'
],
- 'enumMap':{}
+ 'strField':'1'
},
{
- 'strField':'2',
+ 'enumMap':{},
'enumSet':[
'value1',
'value2'
],
- 'enumMap':{}
+ 'strField':'2'
}
],
- 'readonlyField':'This\'s a readonly field',
- 'atomicInteger':1001,
'bigInteger':'123456789012345678901234567890',
- 'someClass':'java.util.Date',
- 'xmlCalendar':'1970-01-01T00:00:00.000Z',
+ 'booleanField':false,
+ 'charField':'A',
+ 'dateField':'1970-01-01T03:23:32.121Z',
'dateNumberMap':[
null
],
- 'publicLinkedList':[
+ 'fieldInBaseClass':'Overridden Value',
+ 'floatField':1.4,
+ 'intField':100,
+ 'ints':[
+ 4,
+ 3,
+ 2,
+ 1
+ ],
+ 'linkedList1':[
'value1'
],
- 'publicTreeMap':{
- 'key1':'value1'
- },
- 'publicStrField':'PublicString',
- 'publicMap':{
- 'key1':'1970-01-01T00:00:00.000Z',
- 'key2':'1970-01-01T00:20:12.121Z'
- },
'publicBigDecimal1':'123456789012345678901234567',
'publicBigDecimal2':'12',
'publicInts':[
@@ -78,26 +78,26 @@
2,
1
],
- 'bean2List':[
- {
- 'strField':'AAA',
- 'enumSet':[
- 'value1',
- 'value2'
- ],
- 'enumMap':{}
- },
- {
- 'strField':'BBB',
- 'enumSet':[
- 'value1',
- 'value2'
- ],
- 'enumMap':{}
- }
+ 'publicLinkedList':[
+ 'value1'
],
+ 'publicMap':{
+ 'key1':'1970-01-01T00:00:00.000Z',
+ 'key2':'1970-01-01T00:20:12.121Z'
+ },
+ 'publicStrField':'PublicString',
'publicStringSet':[
'str1',
'str2'
- ]
+ ],
+ 'publicTreeMap':{
+ 'key1':'value1'
+ },
+ 'readonlyField':'This\'s a readonly field',
+ 'someClass':'java.util.Date',
+ 'strField':'String1: \'"',
+ 'treeMap':{
+ 'key1':'value1'
+ },
+ 'xmlCalendar':'1970-01-01T00:00:00.000Z'
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCyclicReference.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCyclicReference.txt
index 5ad08a6..532d8c7 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCyclicReference.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testCyclicReference.txt
@@ -1,23 +1,23 @@
{
- fieldInBaseClass:`Overridden Value`,
- treeMap:{},
- linkedList1:[],
- intField:0,
- floatField:0.0,
- charField:`\u0000`,
- booleanField:false,
+ atomicInteger:100,
bean2:{
+ enumMap:{},
enumSet:[
`value1`,
`value2`
],
- enumMap:{},
testBean:{
$ref:`../../`
}
},
- readonlyField:`This's a readonly field`,
- atomicInteger:100,
+ booleanField:false,
+ charField:`\u0000`,
+ fieldInBaseClass:`Overridden Value`,
+ floatField:0.0,
+ intField:0,
+ linkedList1:[],
publicLinkedList:[],
- publicTreeMap:{}
+ publicTreeMap:{},
+ readonlyField:`This's a readonly field`,
+ treeMap:{}
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testDedupWithRef.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testDedupWithRef.txt
index b49b50e..4ac9ddf 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testDedupWithRef.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testDedupWithRef.txt
@@ -1,34 +1,34 @@
{
- fieldInBaseClass:"Overridden Value",
- treeMap:{},
- linkedList1:[],
- intField:0,
- floatField:0.0,
- charField:"\u0000",
- booleanField:false,
+ atomicInteger:100,
bean2:{
+ enumMap:{},
+ enumSet:[
+ "value1",
+ "value2"
+ ],
ints:[
1,
2,
3
],
- enumSet:[
- "value1",
- "value2"
- ],
- enumMap:{},
$id:1
},
+ bean2List:[
+ {
+ $ref:"#1"
+ }
+ ],
+ booleanField:false,
+ charField:"\u0000",
+ fieldInBaseClass:"Overridden Value",
+ floatField:0.0,
+ intField:0,
ints:{
$ref:"/bean2/ints"
},
- readonlyField:"This's a readonly field",
- atomicInteger:100,
+ linkedList1:[],
publicLinkedList:[],
publicTreeMap:{},
- bean2List:[
- {
- $ref:"#1"
- }
- ]
+ readonlyField:"This's a readonly field",
+ treeMap:{}
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testEnumNameOption.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testEnumNameOption.txt
index c741755..04231d2 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testEnumNameOption.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testEnumNameOption.txt
@@ -1,75 +1,75 @@
{
- "fieldInBaseClass":"Overridden Value",
- "treeMap":{
- "key1":"value1"
- },
- "linkedList1":[
- "value1"
- ],
- "intField":100,
- "floatField":1.4,
- "charField":"A",
- "booleanField":false,
- "strField":"String1: '\"",
- "dateField":"1970-01-01T03:23:32.121Z",
+ "atomicInteger":1001,
"bean2":{
"enumField":"value2",
"enumField2":"12345-value1",
- "strEnum":"strEnumV1-value1",
+ "enumMap":{
+ "value1":"MapValue1"
+ },
"enumSet":[
"value1",
"value2"
],
- "enumMap":{
- "value1":"MapValue1"
- },
+ "strEnum":"strEnumV1-value1",
"testBean":{
"$ref":"../../"
}
},
- "ints":[
- 4,
- 3,
- 2,
- 1
+ "bean2List":[
+ {
+ "enumMap":{},
+ "enumSet":[
+ "value1",
+ "value2"
+ ],
+ "strField":"AAA"
+ },
+ {
+ "enumMap":{},
+ "enumSet":[
+ "value1",
+ "value2"
+ ],
+ "strField":"BBB"
+ }
],
"bean2s":[
{
- "strField":"1",
+ "enumMap":{},
"enumSet":[
"value1",
"value2"
],
- "enumMap":{}
+ "strField":"1"
},
{
- "strField":"2",
+ "enumMap":{},
"enumSet":[
"value1",
"value2"
],
- "enumMap":{}
+ "strField":"2"
}
],
- "readonlyField":"This's a readonly field",
- "atomicInteger":1001,
"bigInteger":"123456789012345678901234567890",
- "someClass":"java.util.Date",
- "xmlCalendar":"1970-01-01T00:00:00.000Z",
+ "booleanField":false,
+ "charField":"A",
+ "dateField":"1970-01-01T03:23:32.121Z",
"dateNumberMap":[
null
],
- "publicLinkedList":[
+ "fieldInBaseClass":"Overridden Value",
+ "floatField":1.4,
+ "intField":100,
+ "ints":[
+ 4,
+ 3,
+ 2,
+ 1
+ ],
+ "linkedList1":[
"value1"
],
- "publicTreeMap":{
- "key1":"value1"
- },
- "publicStrField":"PublicString",
- "publicMap":{
- "key1":"1970-01-01T00:00:00.000Z",
- "key2":"1970-01-01T00:20:12.121Z"
- },
"publicBigDecimal1":"123456789012345678901234567",
"publicBigDecimal2":"12",
"publicInts":[
@@ -78,26 +78,26 @@
2,
1
],
- "bean2List":[
- {
- "strField":"AAA",
- "enumSet":[
- "value1",
- "value2"
- ],
- "enumMap":{}
- },
- {
- "strField":"BBB",
- "enumSet":[
- "value1",
- "value2"
- ],
- "enumMap":{}
- }
+ "publicLinkedList":[
+ "value1"
],
+ "publicMap":{
+ "key1":"1970-01-01T00:00:00.000Z",
+ "key2":"1970-01-01T00:20:12.121Z"
+ },
+ "publicStrField":"PublicString",
"publicStringSet":[
"str1",
"str2"
- ]
+ ],
+ "publicTreeMap":{
+ "key1":"value1"
+ },
+ "readonlyField":"This's a readonly field",
+ "someClass":"java.util.Date",
+ "strField":"String1: '\"",
+ "treeMap":{
+ "key1":"value1"
+ },
+ "xmlCalendar":"1970-01-01T00:00:00.000Z"
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFieldWithTypeVariable.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFieldWithTypeVariable.txt
index 443b67d..5d09342 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFieldWithTypeVariable.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFieldWithTypeVariable.txt
@@ -1 +1,13 @@
-{"listOfMap":[{"str1":1}],"refOfMap":{"^":{"str1":1}},"value":"abc"}
\ No newline at end of file
+{
+ "listOfMap":[
+ {
+ "str1":1
+ }
+ ],
+ "refOfMap":{
+ "^":{
+ "str1":1
+ }
+ },
+ "value":"abc"
+}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFilter.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFilter.txt
index f5e6a1c..80ade9f 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFilter.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testFilter.txt
@@ -1,22 +1,22 @@
{
- "strField":"str1",
+ "enumField2":12345,
"ints":[
1,
2
],
- "enumField2":12345,
+ "strField":"str1",
"testBean":{
+ "atomicInteger":100,
+ "booleanField":false,
+ "charField":"\u0000",
"fieldInBaseClass":"Overridden Value",
- "treeMap":{},
- "linkedList1":[],
- "intField":0,
"floatField":0.0,
- "charField":"\u0000",
- "booleanField":false,
- "strField":"str2",
- "readonlyField":"This's a readonly field",
- "atomicInteger":100,
+ "intField":0,
+ "linkedList1":[],
"publicLinkedList":[],
- "publicTreeMap":{}
+ "publicTreeMap":{},
+ "readonlyField":"This's a readonly field",
+ "strField":"str2",
+ "treeMap":{}
}
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withMergeArrayOption.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withMergeArrayOption.txt
index 5c4ab55..50ba70f 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withMergeArrayOption.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withMergeArrayOption.txt
@@ -1,35 +1,35 @@
{
- "strField":"strVal1",
"enumField":"value1",
+ "enumMap":{},
+ "enumSet":[
+ "value1",
+ "value2"
+ ],
"ints":[
1,
2,
3,
4
],
- "enumSet":[
- "value1",
- "value2"
- ],
- "enumMap":{},
+ "strField":"strVal1",
"testBean":{
+ "atomicInteger":100,
+ "booleanField":false,
+ "charField":"\u0000",
+ "dateField":"2017-10-01T00:00:00.000Z",
"fieldInBaseClass":"Overridden Value",
- "treeMap":{},
+ "floatField":2.0,
+ "intField":0,
"linkedList1":[
"a",
"b",
"c",
"d"
],
- "intField":0,
- "floatField":2.0,
- "charField":"\u0000",
- "booleanField":false,
- "dateField":"2017-10-01T00:00:00.000Z",
- "readonlyField":"This's a readonly field",
- "atomicInteger":100,
"publicLinkedList":[],
+ "publicStrField":"publicStrVal",
"publicTreeMap":{},
- "publicStrField":"publicStrVal"
+ "readonlyField":"This's a readonly field",
+ "treeMap":{}
}
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withoutMergeArrayOption.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withoutMergeArrayOption.txt
index cc92c20..ce7afcf 100644
--- a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withoutMergeArrayOption.txt
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testIncrementDecode_withoutMergeArrayOption.txt
@@ -1,31 +1,31 @@
{
- "strField":"strVal1",
"enumField":"value1",
- "ints":[
- 3,
- 4
- ],
+ "enumMap":{},
"enumSet":[
"value1",
"value2"
],
- "enumMap":{},
+ "ints":[
+ 3,
+ 4
+ ],
+ "strField":"strVal1",
"testBean":{
+ "atomicInteger":100,
+ "booleanField":false,
+ "charField":"\u0000",
+ "dateField":"2017-10-01T00:00:00.000Z",
"fieldInBaseClass":"Overridden Value",
- "treeMap":{},
+ "floatField":2.0,
+ "intField":0,
"linkedList1":[
"c",
"d"
],
- "intField":0,
- "floatField":2.0,
- "charField":"\u0000",
- "booleanField":false,
- "dateField":"2017-10-01T00:00:00.000Z",
- "readonlyField":"This's a readonly field",
- "atomicInteger":100,
"publicLinkedList":[],
+ "publicStrField":"publicStrVal",
"publicTreeMap":{},
- "publicStrField":"publicStrVal"
+ "readonlyField":"This's a readonly field",
+ "treeMap":{}
}
}
\ No newline at end of file
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testStrictOrder_map.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortMapAndSet_map.txt
similarity index 100%
rename from JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testStrictOrder_map.txt
rename to JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortMapAndSet_map.txt
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testStrictOrder_set.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortMapAndSet_set.txt
similarity index 100%
rename from JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testStrictOrder_set.txt
rename to JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortMapAndSet_set.txt
diff --git a/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortObjectKey.txt b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortObjectKey.txt
new file mode 100644
index 0000000..00c0a23
--- /dev/null
+++ b/JSONCoder/src/test/resources/org/jsonex/jsoncoder/__snapshot__/JSONCoderTest_testSortObjectKey.txt
@@ -0,0 +1 @@
+{"atomicInteger":1001,"bean2":{"enumField":"value2","enumField2":12345,"enumMap":{"value1":"MapValue1"},"enumSet":["value1","value2"],"strEnum":"strEnumV1","testBean":{"$ref":"../../"}},"bean2List":[{"enumMap":{},"enumSet":["value1","value2"],"strField":"AAA"},{"enumMap":{},"enumSet":["value1","value2"],"strField":"BBB"}],"bean2s":[{"enumMap":{},"enumSet":["value1","value2"],"strField":"1"},{"enumMap":{},"enumSet":["value1","value2"],"strField":"2"}],"bigInteger":"123456789012345678901234567890","booleanField":false,"charField":"A","dateField":"1970-01-01T03:23:32.121Z","dateNumberMap":[null],"fieldInBaseClass":"Overridden Value","floatField":1.4,"intField":100,"ints":[4,3,2,1],"linkedList1":["value1"],"publicBigDecimal1":"123456789012345678901234567","publicBigDecimal2":"12","publicInts":[4,3,2,1],"publicLinkedList":["value1"],"publicMap":{"key1":"1970-01-01T00:00:00.000Z","key2":"1970-01-01T00:20:12.121Z"},"publicStrField":"PublicString","publicStringSet":["str1","str2"],"publicTreeMap":{"key1":"value1"},"readonlyField":"This's a readonly field","someClass":"java.util.Date","strField":"String1: '\"","treeMap":{"key1":"value1"},"xmlCalendar":"1970-01-01T00:00:00.000Z"}
\ No newline at end of file
diff --git a/JSONEX.md b/JSONEX.md
index 96df3df..6e94b49 100644
--- a/JSONEX.md
+++ b/JSONEX.md
@@ -10,16 +10,29 @@ JSON is a popular format for data serialization and configuration, but due to th
* **Does not support multi-line String literal**: As configuration, we often need to embedded structured text
* **No comma allowed at end of last element**: This causes many of merge issues and make comment out a single line difficult. In javascript, this is allowed
+## Design Goal
+* **Easy to use** for many kinds of use cases
+ * configure files
+ * command line arguments
+ * URL parameters
+ * Data storage. Serialize/Deserialize objects
+ * Data transportation through network.
+* **Storage efficient**: remove as much redundancy as possible
+* **Easy to parse** The parser should be straight forward to implement
+
## Proposal
To solve the above limitations, we propose **JSONEX** with following extensions
-* Fully compatible with ES6 object literal syntax (So no need specific parser for Javascript)
+* Fully compatible with ES6 object literal syntax for majority variables (So no need specific parser for Javascript)
* Standard JSON is a validate JSONEX
* Support line/block comments as Javascript
-* Quote for Key is optional, only if key is not a valid javascript identifier, quote is mandatory
+* Quote for Key is optional, required only if key is not a valid javascript identifier, quote is mandatory
+* Quote for value is optional, required only if value has special characters confuses parser (Non ES6 compatible)
* Quote can use either ("), (') or (\`)
+* Optional top level brace (Non ES6 compatible)
* Multi-line String literal support with back quote (`).
* Commas are allowed for last element (Make it merge friendly)
* Object attributes order matters, those order will be persisted
+* Path compression such as a:b:c (Non ES6 compabile)
## Examples
@@ -56,6 +69,120 @@ To solve the above limitations, we propose **JSONEX** with following extensions
line2`,
}
```
+## Comparison with JSON for some features
+
+
+Feature JSONex JSON
+
+ optional json top level braces - object
+ a:1,b:2
+
+
+```json
+{
+ "a": 1,
+ "b": 2
+}
+```
+
+
+
+ optional json top level braces - array
+ a,b,1
+
+
+```json
+["a", "b", 1]
+```
+
+
+
+ optional json top level braces - array of objects
+ {a:1},{b:2},c
+
+
+```json
+[
+ {"a": 1},
+ {"b": 2},
+ "c"
+]
+```
+
+
+
+
+
+ Path compression
+ a:b:{c:1, d:2}
+
+
+```json
+{
+ "a":{
+ "b":{
+ "c":1,
+ "d":2
+ }
+ }
+}
+```
+
+
+
+ Type wrapper
+ buyer:user{name:abc, age:10}
+
+
+```json
+{
+ "buyer":{
+ "$type":"user",
+ "name":"abc",
+ "age":10
+ }
+}
+```
+
+
+
+ Optional Quotes
+ {a: value1, b: value2}
+
+
+```json
+{"a": "value1", "b": "value2"}
+```
+
+
+
+ Commas are allowed for last element
+ {a: 1, b: 2, }
+
+
+```json
+{"a": 1, "b": 2}
+```
+
+
+
+ Multi-line value
+ {
+ a:
+ `abc
+ line2`,
+ b: 2
+ }
+
+
+```json
+{"a": "abc\nline2", "b": 2}
+```
+
+
+
+
+
## Other similar effort
- [Json5](https://json5.org/)
diff --git a/README.md b/README.md
index 06a30a9..8ab5c48 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# JSONCoder
-[![Build Status](https://travis-ci.org/eBay/jsonex.svg?branch=master)](https://travis-ci.org/eBay/jsonex)
+[![Build Status](https://github.com/jianwu/jsonex/actions/workflows/maven.yml/badge.svg)](https://github.com/jianwu/jsonex/actions/)
[![codecov](https://codecov.io/gh/eBay/jsonex/branch/master/graph/badge.svg)](https://codecov.io/gh/eBay/jsonex)
## Description
Jsonex JSONCoder is a light-weight generic object serialization / deserialization library similar to Jackson, GSON or FastJson. This library has been widely used in various eBay projects for years. It's not a replacement for other popular libraries. But it solves some specific problems which are not available or not well-supported in other alternatives.
@@ -55,14 +55,14 @@ Please refer the unit test class for more detailed usage patterns:
You can get current version by searching [maven central](https://search.maven.org/search?q=g:org.jsonex)
- Simple Serialization / Deserialization
- ```java
+ ```java
// serialization
JSONCoder.global.encode(o)
// de-serialization
SomeClass obj = JSONCoder.global.decode(str, SomeClass.class);
```
- Filter out fields and classes
- ```java
+ ```java
JSONCoderOption opt = new JSONCoderOption();
// For TestBean2 and it's sub-classes, only include field: "enumField2", "testBean"
opt.addFilterFor(TestBean2.class, include("enumField2", "testBean"));("field1ForClass1", "field2ForClass1");
@@ -75,21 +75,21 @@ Please refer the unit test class for more detailed usage patterns:
String result = JSONCoder.encode(bean, opt);
```
- Mask out certain fields: for privacy reason, quite often when we serialize an object, we need to maskout certain fields such as emailAddress, here's example to do that:
- ```java
+ ```java
String result = JSONCoder.encode(bean, JSONCoderOption.ofIndentFactor(2).addFilterFor(SomeBean.class, mask("field1", "field2")));
```
- Deserialize with generic types
- ```java
+ ```java
String str = "['str1', 'str2', 'str3']";
List result = JSONCoder.global.decode(new DecodeReq>(){}.setSource(str));
```
- Deserialize and merge to existing object (Incremental decode)
- ```java
+ ```java
TestBean bean = JSONCoder.global.decodeTo(jsonStr, bean);
```
- Set custom Quote and custom indentations
- ```java
+ ```java
JSONCoderOption opt = new JSONCoderOption();
opt.getJsonOption().setQuoteChar('`');
opt.getJsonOption().setIndentFactor(2);
@@ -112,7 +112,19 @@ Please refer the unit test class for more detailed usage patterns:
.addCoder(new CoderBigInteger());
String jsonStr = JSONCoder.global.encode(new BigInteger("1234"), opt);
```
-
+
+## Developer Guild
+- Build with non-determinism check (credit to @chessvivek on PR: https://github.com/eBay/jsonex/pull/47)
+```bash
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl JSONCoder
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl SnapshotTest
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl core
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl csv
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl treedoc
+# mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl CliArg # Fail expected, as Cli Annotation depends on field ordering
+mvn edu.illinois:nondex-maven-plugin:1.1.2:nondex -pl HiveUDF
+```
+
## Limitations and Future enhancements
* Performance improvement
* Support of variable placeholder in JSON doc
diff --git a/SnapshotTest/pom.xml b/SnapshotTest/pom.xml
index 1dfec15..e296aeb 100644
--- a/SnapshotTest/pom.xml
+++ b/SnapshotTest/pom.xml
@@ -6,7 +6,7 @@
org.jsonex
jcParent
- 0.1.21
+ 0.1.27
../pom.xml
SnapshotTest
@@ -24,7 +24,7 @@
${project.groupId}
JSONCoder
-
+
org.slf4j
slf4j-simple
diff --git a/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotOption.java b/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotOption.java
index 24ffd47..269f54d 100644
--- a/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotOption.java
+++ b/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotOption.java
@@ -23,7 +23,7 @@ public class SnapshotOption {
/** This method is only available if the serializer is SnapshotSerializerJsonCoder */
@Transient
public JSONCoderOption getJsonCoderOption() {
- return getIfInstanceOfElseThrow(serializer, SnapshotSerializerJsonCoder.class, s -> s.getOption());
+ return getIfInstanceOfOrElseThrow(serializer, SnapshotSerializerJsonCoder.class, s -> s.getOption());
}
public SnapshotOption mutateJsonCoderOption(Function mutator) {
diff --git a/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotSerializerJsonCoder.java b/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotSerializerJsonCoder.java
index cb9d2e4..f770cee 100644
--- a/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotSerializerJsonCoder.java
+++ b/SnapshotTest/src/main/java/org/jsonex/snapshottest/SnapshotSerializerJsonCoder.java
@@ -6,7 +6,7 @@
import lombok.Setter;
public class SnapshotSerializerJsonCoder implements SnapshotSerializer {
- @Getter @Setter private transient JSONCoderOption option = JSONCoderOption.ofIndentFactor(2).setStrictOrder(true);
+ @Getter @Setter private transient JSONCoderOption option = JSONCoderOption.ofIndentFactor(2).setStrictOrdering(true);
@Override
public String serialize(Object obj) {
diff --git a/SnapshotTest/src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_snapshot.json b/SnapshotTest/src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_snapshot.json
index 132101a..d482e00 100644
--- a/SnapshotTest/src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_snapshot.json
+++ b/SnapshotTest/src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_snapshot.json
@@ -1,13 +1,13 @@
{
- "option":{
- "testResourceRoot":"src/test/resources",
- "serializer":{}
- },
- "testClass":"org.jsonex.snapshottest.SnapshotTest",
- "testMethod":"testSnapshot",
- "name":"test",
"actual":"This is actual value",
"actualString":"This is actual value",
"file":"src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_test.txt",
- "tempFile":"src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_test.txt.tmp"
+ "name":"test",
+ "option":{
+ "serializer":{},
+ "testResourceRoot":"src/test/resources"
+ },
+ "tempFile":"src/test/resources/org/jsonex/snapshottest/__snapshot__/SnapshotTest_testSnapshot_test.txt.tmp",
+ "testClass":"org.jsonex.snapshottest.SnapshotTest",
+ "testMethod":"testSnapshot"
}
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index 345eae4..8aa7f22 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -6,7 +6,7 @@
org.jsonex
jcParent
- 0.1.21
+ 0.1.27
../pom.xml
core
diff --git a/core/src/main/java/org/jsonex/core/charsource/ArrayCharSource.java b/core/src/main/java/org/jsonex/core/charsource/ArrayCharSource.java
index 5bc961f..09cb0b6 100644
--- a/core/src/main/java/org/jsonex/core/charsource/ArrayCharSource.java
+++ b/core/src/main/java/org/jsonex/core/charsource/ArrayCharSource.java
@@ -36,7 +36,7 @@ public class ArrayCharSource extends CharSource {
@Override public boolean isEof(int i) { return startIndex + bookmark.getPos() + i >= endIndex; }
- @Override public boolean readUntil(Predicate predicate, StringBuilder target, int minLen, int maxLen) {
+ @Override public boolean readUntil(StringBuilder target, Predicate predicate, int minLen, int maxLen) {
int startPos = bookmark.getPos();
int len = 0;
boolean matched = false;
diff --git a/core/src/main/java/org/jsonex/core/charsource/CharSource.java b/core/src/main/java/org/jsonex/core/charsource/CharSource.java
index 8517c16..1c0dba8 100644
--- a/core/src/main/java/org/jsonex/core/charsource/CharSource.java
+++ b/core/src/main/java/org/jsonex/core/charsource/CharSource.java
@@ -41,21 +41,21 @@ public abstract class CharSource {
*
* @return true The terminate condition matches. otherwise, could be EOF or length matches
*/
- public abstract boolean readUntil(Predicate predicate, StringBuilder target, int minLen, int maxLen);
+ public abstract boolean readUntil(StringBuilder target, Predicate predicate, int minLen, int maxLen);
public boolean readUntil(Predicate predicate, StringBuilder target) {
- return readUntil(predicate, target, 0, MAX_STRING_LEN);
+ return readUntil(target, predicate, 0, MAX_STRING_LEN);
}
public boolean skipUntil(Predicate predicate) {
- return readUntil(predicate, null, 0, Integer.MAX_VALUE);
+ return readUntil(null, predicate, 0, Integer.MAX_VALUE);
}
/** @return true Terminal conditions matches */
- public boolean readUntil(String chars, @Nullable Collection strs, StringBuilder target, boolean include, int minLen, int maxLen) {
- return readUntil(s -> (chars.indexOf(s.peek(0)) >= 0 || startsWithAny(strs))== include, target, minLen, maxLen);
+ public boolean readUntil(StringBuilder target, String chars, @Nullable Collection strs, boolean include, int minLen, int maxLen) {
+ return readUntil(target, s -> (chars.indexOf(s.peek(0)) >= 0 || startsWithAny(strs)) == include, minLen, maxLen);
}
- public boolean readUntil(String terminator, StringBuilder target) { return readUntil(terminator, null, target); }
+ public boolean readUntil(StringBuilder target, String terminator) { return readUntil(target, terminator, null); }
/** @return true Terminal conditions matches */
- public boolean readUntil(String terminator, Collection strs, StringBuilder target) {
- return readUntil(terminator, strs, target, true, 0, MAX_STRING_LEN);
+ public boolean readUntil(StringBuilder target, String terminator, Collection strs) {
+ return readUntil(target, terminator, strs, true, 0, MAX_STRING_LEN);
}
public String readUntil(String terminator) { return readUntil(terminator, null,0, Integer.MAX_VALUE); }
/** @return true Terminal conditions matches */
@@ -63,13 +63,13 @@ public boolean readUntil(String terminator, Collection strs, StringBuild
/** @return true Terminal conditions matches */
public String readUntil(String terminator, Collection strs, int minLen, int maxLen) {
StringBuilder sb = new StringBuilder();
- readUntil(terminator, strs, sb, true, minLen, maxLen);
+ readUntil(sb, terminator, strs, true, minLen, maxLen);
return sb.toString();
}
/** @return true Indicates more character in the stream */
public boolean skipUntil(String chars, boolean include) {
- return readUntil(chars, null, null, include, 0, Integer.MAX_VALUE);
+ return readUntil(null, chars, null, include, 0, Integer.MAX_VALUE);
}
/** @return true Indicates more character in the stream */
public boolean skipUntil(String terminator) { return skipUntil(terminator, true); }
@@ -79,7 +79,7 @@ public boolean skipUntil(String chars, boolean include) {
/** @return true Indicates more character in the stream */
public boolean skipChars(String chars) { return skipUntil(chars, false); }
- public boolean read(StringBuilder target, int len) { return readUntil(s -> true, target, len, len); }
+ public boolean read(StringBuilder target, int len) { return readUntil(target, s -> true, len, len); }
public String read(int len) {
StringBuilder sb = new StringBuilder();
@@ -90,19 +90,19 @@ public String read(int len) {
public boolean skip() { return read(null, 1); }
public boolean skip(int len) { return read(null, len); }
- public boolean readUntilMatch(String str, boolean skipStr, StringBuilder target, int minLen, int maxLen) {
- boolean matches = readUntil(s -> startsWith(str), target, minLen, maxLen);
+ public boolean readUntilMatch(StringBuilder target, String str, boolean skipStr, int minLen, int maxLen) {
+ boolean matches = readUntil(target, s -> startsWith(str), minLen, maxLen);
if (matches && skipStr)
skip(str.length());
return matches;
}
public boolean readUntilMatch(String str, boolean skipStr, StringBuilder target) {
- return readUntilMatch(str, skipStr, target, 0, MAX_STRING_LEN);
+ return readUntilMatch(target, str, skipStr, 0, MAX_STRING_LEN);
}
public boolean skipUntilMatch(String str, boolean skipStr) {
- return readUntilMatch(str, skipStr, null, 0, Integer.MAX_VALUE);
+ return readUntilMatch(null, str, skipStr, 0, Integer.MAX_VALUE);
}
public String peekString(int len) {
@@ -145,17 +145,17 @@ private String getTermStrWithQuoteAndEscape(char quote) {
}
public String readQuotedString(char quote) {
- return readQuotedString(quote, new StringBuilder()).toString();
+ return readQuotedString( new StringBuilder(), quote).toString();
}
- public StringBuilder readQuotedString(char quote, StringBuilder sb) {
+ public StringBuilder readQuotedString(StringBuilder sb, char quote) {
String terminator = getTermStrWithQuoteAndEscape(quote);
// Not calling getBookmark() to avoid clone an object
int pos = getPos();
int line = bookmark.getLine();
int col = bookmark.getCol();
while (true) {
- if (!readUntil(terminator, sb))
+ if (!readUntil(sb, terminator))
throw new EOFRuntimeException("Can't find matching quote at position:" + pos + ";line:" + line + ";col:" + col);
char c = read();
if (c == quote) {
diff --git a/core/src/main/java/org/jsonex/core/charsource/ReaderCharSource.java b/core/src/main/java/org/jsonex/core/charsource/ReaderCharSource.java
index 99df291..97daae6 100644
--- a/core/src/main/java/org/jsonex/core/charsource/ReaderCharSource.java
+++ b/core/src/main/java/org/jsonex/core/charsource/ReaderCharSource.java
@@ -86,7 +86,7 @@ private boolean fill() {
return p >= loadPos;
}
- @Override public boolean readUntil(Predicate predicate, StringBuilder target, int minLen, int maxLen) {
+ @Override public boolean readUntil(StringBuilder target, Predicate predicate, int minLen, int maxLen) {
if (target != null) {
backupTarget = target;
backupMark = getPos();
diff --git a/core/src/main/java/org/jsonex/core/factory/CacheGlobal.java b/core/src/main/java/org/jsonex/core/factory/CacheGlobal.java
deleted file mode 100644
index ca911be..0000000
--- a/core/src/main/java/org/jsonex/core/factory/CacheGlobal.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.jsonex.core.factory;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class CacheGlobal implements CacheProvider {
- public final static InjectableInstance it = InjectableInstance.of(CacheGlobal.class);
- public static CacheGlobal get() { return it.get(); }
-
- private final Map cache = new ConcurrentHashMap<>();
-
- @Override public ObjectCache getCache(Object key) {
- return cache.computeIfAbsent(key, (k) -> new ObjectCacheMapImpl(new ConcurrentHashMap<>()));
- }
-}
diff --git a/core/src/main/java/org/jsonex/core/factory/CacheThreadLocal.java b/core/src/main/java/org/jsonex/core/factory/CacheThreadLocal.java
deleted file mode 100644
index aaa9b6d..0000000
--- a/core/src/main/java/org/jsonex/core/factory/CacheThreadLocal.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.jsonex.core.factory;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class CacheThreadLocal implements CacheProvider {
- public final static InjectableInstance it = InjectableInstance.of(CacheThreadLocal.class);
- public static CacheThreadLocal get() { return it.get(); }
-
- // Seems ThreadLocal.withInitial() is not synchronized when create initial, so potentially it could be called multiple times in race condition
- private ThreadLocal> cache = ThreadLocal.withInitial(ConcurrentHashMap::new);
-
- @Override public ObjectCache getCache(Object scope) {
- return cache.get().computeIfAbsent(scope, (k) -> new ObjectCacheMapImpl(new ConcurrentHashMap<>()));
- }
-}
diff --git a/core/src/main/java/org/jsonex/core/factory/InjectableFactory.java b/core/src/main/java/org/jsonex/core/factory/InjectableFactory.java
index e880961..0d0164c 100644
--- a/core/src/main/java/org/jsonex/core/factory/InjectableFactory.java
+++ b/core/src/main/java/org/jsonex/core/factory/InjectableFactory.java
@@ -9,8 +9,8 @@
package org.jsonex.core.factory;
-import org.jsonex.core.factory.CacheProvider.NoCache;
-import org.jsonex.core.factory.CacheProvider.ObjectCache;
+import org.jsonex.core.factory.ScopeProvider.NoCache;
+import org.jsonex.core.factory.ScopeProvider.Scope;
import org.jsonex.core.type.Func;
import org.jsonex.core.type.Tuple;
import lombok.Getter;
@@ -33,13 +33,13 @@ public class InjectableFactory {
private Function super TP, ? extends TI> objectCreator;
@Getter private static List> globalCreateHandlers = new ArrayList<>();
@Getter private List> createHandlers = new ArrayList<>();
- private final CacheProvider cacheProvider;
+ private final ScopeProvider scopeProvider;
private final Function super TP, ? extends TI> initialCreator;
- protected InjectableFactory(Function super TP, ? extends TI> creator, CacheProvider cacheProvider) {
+ protected InjectableFactory(Function super TP, ? extends TI> creator, ScopeProvider scopeProvider) {
initialCreator = creator;
- this.cacheProvider = cacheProvider;
+ this.scopeProvider = scopeProvider;
setCreator(creator);
}
@@ -52,8 +52,8 @@ public static InjectableFactory of(Function objectCreat
return of(objectCreator, NoCache.get());
}
- public static InjectableFactory of(Function objectCreator, CacheProvider cacheProvider) {
- return new InjectableFactory<>(objectCreator, cacheProvider);
+ public static InjectableFactory of(Function objectCreator, ScopeProvider scopeProvider) {
+ return new InjectableFactory<>(objectCreator, scopeProvider);
}
public TI get() { return get(null); }
@@ -62,8 +62,8 @@ public TI get(TP param) {
return getCache().get(getCacheKey(param), (key) -> create(param));
}
- protected ObjectCache getCache() {
- return cacheProvider.getCache(this);
+ protected Scope getCache() {
+ return scopeProvider.getCache(this);
}
// Have to use a placeholder for null for ConcurrentHashMap unfortunately
@@ -96,51 +96,51 @@ private TI create(TP param) {
}
public static class _0 extends InjectableFactory {
- public _0(Function creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
+ public _0(Function creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
public static _0 of(Supplier objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _0 of(Supplier objectCreator, CacheProvider cacheProvider) { return new _0<>((Function)(p -> objectCreator.get()), cacheProvider); }
+ public static _0 of(Supplier objectCreator, ScopeProvider scopeProvider) { return new _0<>((Function)(p -> objectCreator.get()), scopeProvider); }
public I get() { return super.get(null); }
}
- public static class _2 extends InjectableFactory, I> {
- public _2(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _2 of(BiFunction objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _2 of(BiFunction objectCreator, CacheProvider cacheProvider) { return new _2<>((Function, I>)(p -> objectCreator.apply(p._1, p._2)), cacheProvider); }
- public I get(P1 p1, P2 p2) { return super.get(Tuple.Pair.of(p1, p2)); }
+ public static class _2 extends InjectableFactory, I> {
+ public _2(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _2 of(BiFunction objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _2 of(BiFunction objectCreator, ScopeProvider scopeProvider) { return new _2<>((Function, I>)(p -> objectCreator.apply(p._0, p._1)), scopeProvider); }
+ public I get(P0 p0, P1 p1) { return super.get(Tuple.Pair.of(p0, p1)); }
}
- public static class _3 extends InjectableFactory, I> {
- public _3(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _3 of(Func._3 objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _3 of(Func._3 objectCreator, CacheProvider cacheProvider) { return new _3<>((Function, I>)(p -> objectCreator.apply(p._1, p._2, p._3)), cacheProvider); }
- public I get(P1 p1, P2 p2, P3 p3) { return super.get(Tuple._3.of(p1, p2, p3)); }
+ public static class _3 extends InjectableFactory, I> {
+ public _3(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _3 of(Func._3 objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _3 of(Func._3 objectCreator, ScopeProvider scopeProvider) { return new _3<>((Function, I>)(p -> objectCreator.apply(p._0, p._1, p._2)), scopeProvider); }
+ public I get(P0 p0, P1 p1, P2 p2) { return super.get(Tuple.Tuple3.of(p0, p1, p2)); }
}
- public static class _4 extends InjectableFactory, I> {
- public _4(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _4 of(Func._4 objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _4 of(Func._4 objectCreator, CacheProvider cacheProvider) { return new _4<>((Function, I>)(p -> objectCreator.apply(p._1, p._2, p._3, p._4)), cacheProvider); }
- public I get(P1 p1, P2 p2, P3 p3, P4 p4) { return super.get(Tuple._4.of(p1, p2, p3, p4)); }
+ public static class _4 extends InjectableFactory, I> {
+ public _4(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _4 of(Func._4 objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _4 of(Func._4 objectCreator, ScopeProvider scopeProvider) { return new _4<>((Function, I>)(p -> objectCreator.apply(p._0, p._1, p._2, p._3)), scopeProvider); }
+ public I get(P0 p0, P1 p1, P2 p2, P3 p3) { return super.get(Tuple.Tuple4.of(p0, p1, p2, p3)); }
}
- public static class _5 extends InjectableFactory, I> {
- public _5(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _5 of(Func._5 objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _5 of(Func._5 objectCreator, CacheProvider cacheProvider) { return new _5<>((Function, I>)(p -> objectCreator.apply(p._1, p._2, p._3, p._4, p._5)), cacheProvider); }
- public I get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { return super.get(Tuple._5.of(p1, p2, p3, p4, p5)); }
+ public static class _5 extends InjectableFactory, I> {
+ public _5(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _5 of(Func._5 objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _5 of(Func._5 objectCreator, ScopeProvider scopeProvider) { return new _5<>((Function, I>)(p -> objectCreator.apply(p._0, p._1, p._2, p._3, p._4)), scopeProvider); }
+ public I get(P0 p0, P1 p1, P2 p2, P3 p3, P4 p4) { return super.get(Tuple.Tuple5.of(p0, p1, p2, p3, p4)); }
}
- public static class _6 extends InjectableFactory, I> {
- public _6(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _6 of(Func._6 objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _6 of(Func._6 objectCreator, CacheProvider cacheProvider) { return new _6<>((Function, I>)(p -> objectCreator.apply(p._1, p._2, p._3, p._4, p._5, p._6)), cacheProvider); }
- public I get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6) { return super.get(Tuple._6.of(p1, p2, p3, p4, p5,p6)); }
+ public static class _6 extends InjectableFactory, I> {
+ public _6(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _6 of(Func._6 objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _6 of(Func._6 objectCreator, ScopeProvider scopeProvider) { return new _6<>((Function, I>)(p -> objectCreator.apply(p._0, p._1, p._2, p._3, p._4, p._5)), scopeProvider); }
+ public I get(P0 p0, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { return super.get(Tuple.Tuple6.of(p0, p1, p2, p3, p4,p5)); }
}
- public static class _7 extends InjectableFactory, I> {
- public _7(Function, I> creator, CacheProvider cacheProvider) { super(creator, cacheProvider); }
- public static _7 of(Func._7 objectCreator) { return of(objectCreator, NoCache.get()); }
- public static _7 of(Func._7 objectCreator, CacheProvider cacheProvider) { return new _7<>((Function, I>)(p -> objectCreator.apply(p._1, p._2, p._3, p._4, p._5, p._6, p._7)), cacheProvider); }
- public I get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7) { return super.get(Tuple._7.of(p1, p2, p3, p4, p5, p6, p7)); }
+ public static class _7 extends InjectableFactory, I> {
+ public _7(Function, I> creator, ScopeProvider scopeProvider) { super(creator, scopeProvider); }
+ public static _7 of(Func._7 objectCreator) { return of(objectCreator, NoCache.get()); }
+ public static _7 of(Func._7 objectCreator, ScopeProvider scopeProvider) { return new _7<>((Function, I>)(p -> objectCreator.apply(p._0, p._1, p._2, p._3, p._4, p._5, p._6)), scopeProvider); }
+ public I get(P0 p0, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6) { return super.get(Tuple.Tuple7.of(p0, p1, p2, p3, p4, p5, p6)); }
}
}
diff --git a/core/src/main/java/org/jsonex/core/factory/ScopeGlobal.java b/core/src/main/java/org/jsonex/core/factory/ScopeGlobal.java
new file mode 100644
index 0000000..a77b2f9
--- /dev/null
+++ b/core/src/main/java/org/jsonex/core/factory/ScopeGlobal.java
@@ -0,0 +1,15 @@
+package org.jsonex.core.factory;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ScopeGlobal implements ScopeProvider {
+ public final static InjectableInstance it = InjectableInstance.of(ScopeGlobal.class);
+ public static ScopeGlobal get() { return it.get(); }
+
+ private final Map cache = new ConcurrentHashMap<>();
+
+ @Override public Scope getCache(Object key) {
+ return cache.computeIfAbsent(key, (k) -> new ScopeMapImpl(new ConcurrentHashMap<>()));
+ }
+}
diff --git a/core/src/main/java/org/jsonex/core/factory/CacheProvider.java b/core/src/main/java/org/jsonex/core/factory/ScopeProvider.java
similarity index 70%
rename from core/src/main/java/org/jsonex/core/factory/CacheProvider.java
rename to core/src/main/java/org/jsonex/core/factory/ScopeProvider.java
index ebef4d2..7f9b789 100644
--- a/core/src/main/java/org/jsonex/core/factory/CacheProvider.java
+++ b/core/src/main/java/org/jsonex/core/factory/ScopeProvider.java
@@ -5,17 +5,17 @@
import java.util.Map;
import java.util.function.Function;
-public interface CacheProvider {
- ObjectCache getCache(Object scope);
+public interface ScopeProvider {
+ Scope getCache(Object scope);
- interface ObjectCache {
+ interface Scope {
V get(K k, Function creator);
V put(K k, V v);
void clear();
}
@RequiredArgsConstructor
- class ObjectCacheMapImpl implements ObjectCache {
+ class ScopeMapImpl implements Scope {
private final Map map;
@Override public V get(K k, Function creator) { return map.computeIfAbsent(k, creator); }
@@ -23,16 +23,16 @@ class ObjectCacheMapImpl implements ObjectCache {
@Override public void clear() { map.clear(); }
}
- class ObjectCachePassThrough implements ObjectCache {
+ class ScopePassThrough implements Scope {
@Override public V get(K k, Function