Finally found and removed the performance bottleneck, so now performance is equal to jackson
updated dependencies and surefire config for java 9+
This commit is contained in:
parent
2a484909ed
commit
8d8160d341
5 changed files with 80 additions and 105 deletions
|
|
@ -4,3 +4,6 @@ a JSON serializer based on bytecode manipulation
|
|||
* creates a Json serializer for a java type using javassist
|
||||
* deserializing not yet implemented
|
||||
* see the unit tests to see how it works
|
||||
|
||||
|
||||
* as of java9 it needs `--add-opens java.base/java.lang=ALL-UNNAMED` as java commandline option.
|
||||
45
pom.xml
45
pom.xml
|
|
@ -15,47 +15,21 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<version>3.26.0-GA</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.30</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>[4.13.1,)</version>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<version>3.23.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
@ -68,8 +42,21 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.10.5.1</version>
|
||||
<version>2.15.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ public abstract class JSONSerializer<T> {
|
|||
if (object == null) {
|
||||
return "";
|
||||
} else if (object instanceof Number || object instanceof Boolean) {
|
||||
return "" + object.toString();
|
||||
return "" + object;
|
||||
} else if (object instanceof CharSequence || object instanceof Character) {
|
||||
return "\"" + object.toString() + "\"";
|
||||
return "\"" + object + "\"";
|
||||
} else {
|
||||
return handle(object);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package nl.jssl.jsontoy.serialize;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -36,7 +37,7 @@ class SynthSerializerFactory {
|
|||
private static final String SET = "java.util.Set";
|
||||
private static final String MAP = "java.util.Map";
|
||||
|
||||
private static final Map<String, JSONSerializer<?>> serializers = new HashMap<>();
|
||||
private static final Map<Class<?>, JSONSerializer<?>> serializers = new HashMap<>();
|
||||
private static final String ROOT_PACKAGE = "serializer.";
|
||||
|
||||
private final ClassPool pool = ClassPool.getDefault();
|
||||
|
|
@ -55,37 +56,29 @@ class SynthSerializerFactory {
|
|||
}
|
||||
}
|
||||
|
||||
<T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> JSONSerializer<T> createSerializer(Class<T> beanjavaClass) {
|
||||
if (serializers.containsKey(beanjavaClass)) {
|
||||
return (JSONSerializer<T>) serializers.get(beanjavaClass);
|
||||
}
|
||||
try {
|
||||
CtClass beanClass = pool.get(beanjavaClass.getName());
|
||||
|
||||
return createSerializer2(beanClass);
|
||||
} catch (NotFoundException e) {
|
||||
return tryCreateSerializer(beanjavaClass, beanClass);
|
||||
} catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException |
|
||||
InvocationTargetException | NoSuchMethodException e) {
|
||||
throw new SerializerCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> JSONSerializer<T> createSerializer2(CtClass beanClass) {
|
||||
if (serializers.containsKey(createSerializerName(beanClass))) {
|
||||
return (JSONSerializer<T>) serializers.get(createSerializerName(beanClass));
|
||||
}
|
||||
try {
|
||||
return tryCreateSerializer(beanClass);
|
||||
} catch (NotFoundException | CannotCompileException | InstantiationException | IllegalAccessException e) {
|
||||
throw new SerializerCreationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> JSONSerializer<T> tryCreateSerializer(CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException,
|
||||
IllegalAccessException {
|
||||
private <T> JSONSerializer<T> tryCreateSerializer(Class<?> javaClass, CtClass beanClass) throws NotFoundException, CannotCompileException, InstantiationException,
|
||||
IllegalAccessException, InvocationTargetException, NoSuchMethodException {
|
||||
CtClass serializerClass = pool.makeClass(createSerializerName(beanClass), serializerBase);
|
||||
|
||||
addToJsonStringMethod(beanClass, serializerClass);
|
||||
|
||||
JSONSerializer<T> jsonSerializer = createSerializerInstance(serializerClass);
|
||||
|
||||
serializers.put(createSerializerName(beanClass), jsonSerializer);
|
||||
serializers.put(javaClass, jsonSerializer);
|
||||
return jsonSerializer;
|
||||
}
|
||||
|
||||
|
|
@ -99,16 +92,16 @@ class SynthSerializerFactory {
|
|||
/*
|
||||
* Creates the source, handling the for JSON different types of classes
|
||||
*/
|
||||
private <T> String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException {
|
||||
private String createToJSONStringMethodSource(CtClass beanClass) throws NotFoundException {
|
||||
String source = "public String handle(Object object){\n";
|
||||
if (beanClass.isArray()) {
|
||||
source += "\tObject[] array=(Object[])object;\n";
|
||||
source += handleArray(beanClass);
|
||||
source += handleArray();
|
||||
} else if (isCollection(beanClass)) {
|
||||
source += "\tObject[] array=((java.util.Collection)object).toArray();\n";
|
||||
source += handleArray(beanClass);
|
||||
source += handleArray();
|
||||
} else if (isMap(beanClass)) {
|
||||
source += handleMap(beanClass);
|
||||
source += handleMap();
|
||||
} else if (!isPrimitiveOrWrapperOrString(beanClass)) {
|
||||
List<CtMethod> getters = getGetters(beanClass);
|
||||
if (shouldAddGetterCallers(getters)) {
|
||||
|
|
@ -117,7 +110,6 @@ class SynthSerializerFactory {
|
|||
} else {
|
||||
source += "\treturn \"\";}";
|
||||
}
|
||||
System.out.println(source);
|
||||
return source;
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +120,7 @@ class SynthSerializerFactory {
|
|||
*
|
||||
* The generated code fills a StringBuilder. The values are generated by the subserializers
|
||||
*/
|
||||
private String handleArray(CtClass beanClass) {
|
||||
private String handleArray() {
|
||||
String source = "\tStringBuilder result=new StringBuilder(\"[\");\n";
|
||||
source += "\tfor (int i=0; i<array.length; i++){\n";
|
||||
source += "\t\tresult.append(" + Serializer.class.getName() + ".toJSONString(array[i]));\n";
|
||||
|
|
@ -141,7 +133,7 @@ class SynthSerializerFactory {
|
|||
return source;
|
||||
}
|
||||
|
||||
private String handleMap(CtClass beanClass) {
|
||||
private String handleMap() {
|
||||
String source = "StringBuilder result=new StringBuilder(\"{\");\n";
|
||||
source += "\tfor (java.util.Iterator entries=((java.util.Map)object).entrySet().iterator();entries.hasNext();){\n";
|
||||
source += "\t\tjava.util.Map.Entry entry=(java.util.Map.Entry)entries.next();\n";
|
||||
|
|
@ -160,7 +152,7 @@ class SynthSerializerFactory {
|
|||
/*
|
||||
* If the class contains fields for which public getters are available, then these will be called in the generated code.
|
||||
*/
|
||||
private String addGetterCallers(CtClass beanClass, String source, List<CtMethod> getters) throws NotFoundException {
|
||||
private String addGetterCallers(CtClass beanClass, String source, List<CtMethod> getters) {
|
||||
int index = 0;
|
||||
source += "\treturn ";
|
||||
source += "\"{";
|
||||
|
|
@ -176,8 +168,8 @@ class SynthSerializerFactory {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> JSONSerializer<T> createSerializerInstance(CtClass serializerClass) throws InstantiationException, IllegalAccessException,
|
||||
CannotCompileException {
|
||||
return (JSONSerializer<T>) serializerClass.toClass().newInstance();
|
||||
CannotCompileException, NoSuchMethodException, InvocationTargetException {
|
||||
return (JSONSerializer<T>) serializerClass.toClass().getConstructor().newInstance();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -218,7 +210,7 @@ class SynthSerializerFactory {
|
|||
/*
|
||||
* The JSON vernacular for key:value is pair...
|
||||
*/
|
||||
private String addPair(CtClass classToSerialize, String source, CtMethod getter) throws NotFoundException {
|
||||
private String addPair(CtClass classToSerialize, String source, CtMethod getter) {
|
||||
source += jsonKey(getter);
|
||||
source += ": "; // what is the rule when it comes to spaces in json?
|
||||
source += jsonValue(classToSerialize, getter);
|
||||
|
|
@ -232,20 +224,14 @@ class SynthSerializerFactory {
|
|||
return "\\\"" + toFieldName(getter.getName()) + "\\\"";
|
||||
}
|
||||
|
||||
private String jsonValue(CtClass classToSerialize, CtMethod getter) throws NotFoundException {
|
||||
String source = "";
|
||||
CtClass returnType = getter.getReturnType();
|
||||
|
||||
private String jsonValue(CtClass classToSerialize, CtMethod getter) {
|
||||
/* primitives are wrapped so the produced methods adhere to the JSONSerializer interface */
|
||||
source = createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter, source, returnType);
|
||||
|
||||
return source;
|
||||
return createSubSerializerForReturnTypeAndAddInvocationToSource(classToSerialize, getter);
|
||||
}
|
||||
|
||||
private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter, String source, CtClass returnType) {
|
||||
private String createSubSerializerForReturnTypeAndAddInvocationToSource(CtClass classToSerialize, CtMethod getter) {
|
||||
/* NB there does not seem to be auto(un))boxing nor generic types (or other jdk1.5 stuff) in javassist compileable code */
|
||||
|
||||
source += "\"+" + Serializer.class.getName() + ".toJSONString(";
|
||||
String source = "\"+" + Serializer.class.getName() + ".toJSONString(";
|
||||
|
||||
// cast because of lack of generics
|
||||
source += "(" + cast(regularClassname(classToSerialize.getName())) + "object)." + getter.getName() + "()";
|
||||
|
|
@ -331,22 +317,22 @@ class SynthSerializerFactory {
|
|||
|
||||
String asPrimitive(String name) {
|
||||
switch (name) {
|
||||
case "int":
|
||||
return "I";
|
||||
case "byte":
|
||||
return "B";
|
||||
case "float":
|
||||
return "F";
|
||||
case "long":
|
||||
return "J";
|
||||
case "boolean":
|
||||
return "Z";
|
||||
case "char":
|
||||
return "C";
|
||||
case "double":
|
||||
return "D";
|
||||
case "short":
|
||||
return "S";
|
||||
case "int":
|
||||
return "I";
|
||||
case "byte":
|
||||
return "B";
|
||||
case "float":
|
||||
return "F";
|
||||
case "long":
|
||||
return "J";
|
||||
case "boolean":
|
||||
return "Z";
|
||||
case "char":
|
||||
return "C";
|
||||
case "double":
|
||||
return "D";
|
||||
case "short":
|
||||
return "S";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,50 +13,49 @@ import org.junit.Test;
|
|||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class Jackson {
|
||||
List<String> trashbin = new ArrayList<String>();
|
||||
public class JacksonComparisonTest {
|
||||
private static final int ITERATIONS = 20;
|
||||
private static final int INNERLOOP_COUNT = 100000;
|
||||
List<String> trashbin = new ArrayList<>();
|
||||
|
||||
@Test
|
||||
public void jackson() throws JsonProcessingException {
|
||||
public void testPerformance() throws JsonProcessingException {
|
||||
System.out.println("jackson,jsontoy");
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Bean1 bean1 = new Bean1();
|
||||
Bean2 bean2 = new Bean2();
|
||||
bean1.setData1(UUID.randomUUID().toString());
|
||||
bean1.setBean2(bean2);
|
||||
bean2.setData2(UUID.randomUUID().toString());
|
||||
String valueAsString = objectMapper.writeValueAsString(bean1);
|
||||
String jsonString = Serializer.toJSONString(bean1);
|
||||
String valueAsString;
|
||||
String jsonString;
|
||||
|
||||
for (int c = 0; c < 20; c++) {
|
||||
for (int c = 0; c < ITERATIONS; c++) {
|
||||
trashbin.clear();
|
||||
System.gc();
|
||||
long t0 = System.currentTimeMillis();
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
for (int i = 0; i < INNERLOOP_COUNT; i++) {
|
||||
bean1 = new Bean1();
|
||||
bean2 = new Bean2();
|
||||
bean1.setData1(UUID.randomUUID().toString());
|
||||
bean1.setBean2(bean2);
|
||||
bean2.setData2(UUID.randomUUID().toString());
|
||||
valueAsString = objectMapper.writeValueAsString(bean1);
|
||||
// System.out.println(valueAsString);
|
||||
trashbin.add(valueAsString);
|
||||
}
|
||||
System.out.print(System.currentTimeMillis() - t0);
|
||||
System.out.printf("% 7d",(System.currentTimeMillis() - t0));
|
||||
System.out.print(",");
|
||||
trashbin.clear();
|
||||
System.gc();
|
||||
long tt0 = System.currentTimeMillis();
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
for (int i = 0; i < INNERLOOP_COUNT; i++) {
|
||||
bean1 = new Bean1();
|
||||
bean2 = new Bean2();
|
||||
bean1.setData1(UUID.randomUUID().toString());
|
||||
bean1.setBean2(bean2);
|
||||
bean2.setData2(UUID.randomUUID().toString());
|
||||
jsonString = Serializer.toJSONString(bean1);
|
||||
// System.out.println(jsonString);
|
||||
trashbin.add(jsonString);
|
||||
}
|
||||
System.out.println(System.currentTimeMillis() - tt0);
|
||||
System.out.printf("% 7d%n",System.currentTimeMillis() - tt0);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue