diff --git a/benchmark/pom.xml b/benchmark/pom.xml
index d723304..f25a1d5 100644
--- a/benchmark/pom.xml
+++ b/benchmark/pom.xml
@@ -14,7 +14,7 @@
UTF-8
- 1.25.2
+ 1.37
benchmarks
@@ -58,15 +58,38 @@
com.fasterxml.jackson.module
jackson-module-afterburner
+
+ com.fasterxml.jackson.module
+ jackson-module-blackbird
+
com.google.code.gson
gson
- 2.8.6
+ 2.13.2
compile
+
+ com.dslplatform
+ dsl-json
+ 2.0.2
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.12.1
+
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh.version}
+
+
+
+
org.apache.maven.plugins
maven-shade-plugin
@@ -84,6 +107,12 @@
org.openjdk.jmh.Main
+
+ META-INF/BenchmarkList
+
+
+ META-INF/CompilerHints
+
diff --git a/benchmark/src/main/java/org/sample/MyBenchmark.java b/benchmark/src/main/java/org/sample/MyBenchmark.java
index 7afaf3e..239938b 100644
--- a/benchmark/src/main/java/org/sample/MyBenchmark.java
+++ b/benchmark/src/main/java/org/sample/MyBenchmark.java
@@ -31,11 +31,15 @@
package org.sample;
+import com.dslplatform.json.DslJson;
+import com.dslplatform.json.runtime.Settings;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
+import com.fasterxml.jackson.module.blackbird.BlackbirdModule;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
@@ -50,6 +54,7 @@
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
+import org.sample.jackson.Person2Deserializer;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -180,6 +185,24 @@ public void setup() {
}
}
+ @State(Scope.Benchmark)
+ public static class BlackbirdWriter {
+ public com.fasterxml.jackson.databind.ObjectWriter objectWriter;
+ public Person2 person;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false);
+ mapper.registerModule(new BlackbirdModule());
+ objectWriter = mapper.writerFor(Person2.class);
+ try {
+ person = mapper.readerFor(Person2.class).readValue(new StringReader(json));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
@State(Scope.Benchmark)
public static class SimpleQsonWriter {
@@ -290,6 +313,62 @@ public void setup() {
}
}
+ @State(Scope.Benchmark)
+ public static class BlackbirdParser {
+ public ObjectReader reader;
+ public byte[] jsonBytes;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false);
+ mapper.registerModule(new BlackbirdModule());
+ reader = mapper.readerFor(Person2.class);
+ try {
+ jsonBytes = json.getBytes("UTF-8");
+ } catch (Exception e) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class JacksonCustomParser {
+ public ObjectReader reader;
+ public byte[] jsonBytes;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, false);
+ SimpleModule module = new SimpleModule();
+ module.addDeserializer(Person2.class, new Person2Deserializer());
+ mapper.registerModule(module);
+ reader = mapper.readerFor(Person2.class);
+ try {
+ jsonBytes = json.getBytes("UTF-8");
+ } catch (Exception e) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ @State(Scope.Benchmark)
+ public static class DslJsonParser {
+ public DslJson reader;
+ public byte[] jsonBytes;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ reader = new DslJson<>(Settings.basicSetup());
+ try {
+ jsonBytes = json.getBytes("UTF-8");
+ } catch (Exception e) {
+ throw new RuntimeException();
+ }
+ }
+ }
+
@State(Scope.Benchmark)
public static class GsonParser {
public Gson gson;
@@ -349,7 +428,32 @@ public Object testParserAfterburner(AfterburnerParser a) {
}
}
+ @Benchmark
+ public Object testParserBlackbird(BlackbirdParser a) {
+ try {
+ return a.reader.readValue(a.jsonBytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Benchmark
+ public Object testParserJacksonCustom(JacksonCustomParser a) {
+ try {
+ return a.reader.readValue(a.jsonBytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ @Benchmark
+ public Object testParserDslJson(DslJsonParser a) {
+ try {
+ return a.reader.deserialize(Person2.class, new ByteArrayInputStream(a.jsonBytes));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
@Benchmark
public Object testWriterQson(QsonWriter q) {
@@ -373,4 +477,13 @@ public Object testWriterAfterburner(AfterburnerWriter q) {
throw new RuntimeException(e);
}
}
+
+ @Benchmark
+ public Object testWriterBlackbird(BlackbirdWriter q) {
+ try {
+ return q.objectWriter.writeValueAsBytes(q.person);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/benchmark/src/main/java/org/sample/jackson/Person2Deserializer.java b/benchmark/src/main/java/org/sample/jackson/Person2Deserializer.java
new file mode 100644
index 0000000..89d8df0
--- /dev/null
+++ b/benchmark/src/main/java/org/sample/jackson/Person2Deserializer.java
@@ -0,0 +1,220 @@
+package org.sample.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import org.sample.Person2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom Jackson deserializer for Person2 using streaming API for better performance.
+ */
+public class Person2Deserializer extends JsonDeserializer {
+
+ @Override
+ public Person2 deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ Person2 person = new Person2();
+
+ if (p.currentToken() != JsonToken.START_OBJECT) {
+ p.nextToken();
+ }
+
+ while (p.nextToken() != JsonToken.END_OBJECT) {
+ String fieldName = p.currentName();
+ p.nextToken(); // move to value
+
+ if (fieldName == null) continue;
+
+ switch (fieldName) {
+ case "name":
+ person.setName(p.getValueAsString());
+ break;
+ case "age":
+ person.setAge(p.getIntValue());
+ break;
+ case "married":
+ person.setMarried(p.getBooleanValue());
+ break;
+ case "money":
+ person.setMoney(p.getFloatValue());
+ break;
+ case "intMap":
+ person.setIntMap(deserializeIntMap(p));
+ break;
+ case "dad":
+ person.setDad(deserialize(p, ctxt));
+ break;
+ case "kids":
+ person.setKids(deserializePersonMap(p, ctxt));
+ break;
+ case "siblings":
+ person.setSiblings(deserializePersonList(p, ctxt));
+ break;
+ case "pets":
+ person.setPets(deserializeStringList(p));
+ break;
+ case "genericMap":
+ person.setGenericMap(deserializeGenericMap(p, ctxt));
+ break;
+ case "genericBag":
+ person.setGenericBag(deserializeGenericValue(p, ctxt));
+ break;
+ case "genericList":
+ person.setGenericList(deserializeGenericList(p, ctxt));
+ break;
+ case "nested":
+ person.setNested(deserializeNestedMap(p, ctxt));
+ break;
+ default:
+ p.skipChildren();
+ break;
+ }
+ }
+
+ return person;
+ }
+
+ private Map deserializeIntMap(JsonParser p) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ Map map = new LinkedHashMap<>();
+
+ while (p.nextToken() != JsonToken.END_OBJECT) {
+ String key = p.currentName();
+ p.nextToken();
+ map.put(key, p.getIntValue());
+ }
+
+ return map;
+ }
+
+ private Map deserializePersonMap(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ Map map = new LinkedHashMap<>();
+
+ while (p.nextToken() != JsonToken.END_OBJECT) {
+ String key = p.currentName();
+ p.nextToken();
+ map.put(key, deserialize(p, ctxt));
+ }
+
+ return map;
+ }
+
+ private List deserializePersonList(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ List list = new ArrayList<>();
+
+ while (p.nextToken() != JsonToken.END_ARRAY) {
+ list.add(deserialize(p, ctxt));
+ }
+
+ return list;
+ }
+
+ private List deserializeStringList(JsonParser p) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ List list = new ArrayList<>();
+
+ while (p.nextToken() != JsonToken.END_ARRAY) {
+ list.add(p.getValueAsString());
+ }
+
+ return list;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Map deserializeGenericMap(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ Map map = new LinkedHashMap<>();
+
+ while (p.nextToken() != JsonToken.END_OBJECT) {
+ String key = p.currentName();
+ p.nextToken();
+ map.put(key, deserializeGenericValue(p, ctxt));
+ }
+
+ return map;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private List deserializeGenericList(JsonParser p, DeserializationContext ctxt) throws IOException {
+ if (p.currentToken() == JsonToken.VALUE_NULL) {
+ return null;
+ }
+
+ List