├── .github └── workflows │ └── scala.yml ├── .gitignore ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── jsonperf │ ├── BigJsonTest.scala │ ├── Jackson.scala │ ├── JmhBenchmarks.scala │ ├── JsonParsing.scala │ └── JsonTest.scala └── test └── scala └── jsonperf └── UnitTest.scala /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: coursier/cache-action@v3 17 | 18 | - name: Set up Java SDK 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 15 22 | 23 | - name: Run tests 24 | run: sbt test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /RUNNING_PID 2 | /logs/ 3 | /project/*-shim.sbt 4 | /project/project/ 5 | /project/target/ 6 | /target/ 7 | /.idea 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Scala JSON parser performance comparator 2 | ======================================== 3 | 4 | Compares performance of several JSON parsers used in Scala: 5 | 6 | - [argonaut](http://argonaut.io/) 7 | - [borer](https://sirthias.github.io/borer/) 8 | - [circe](https://circe.github.io/circe/) 9 | - [jackson](http://wiki.fasterxml.com/JacksonHome) with [scala module](https://github.com/FasterXML/jackson-module-scala) 10 | - [json4s with jackson](https://github.com/json4s/json4s#jackson) 11 | - [json4s native](https://github.com/json4s/json4s) 12 | - [play-json](https://www.playframework.com/documentation/latest/ScalaJson), used in [Play Framework](https://www.playframework.com/) 13 | - [sphere-json](https://github.com/sphereio/sphere-scala-libs/tree/master/json) 14 | - [spray-json](https://github.com/spray/spray-json) 15 | - [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala) 16 | - [uPickle](http://www.lihaoyi.com/upickle) 17 | - [weePickle](https://github.com/rallyhealth/weePickle) 18 | - [refuel-json](https://github.com/giiita/refuel) 19 | 20 | The test case is to deserialize a json into a case class and to serialize back to json. 21 | 22 | The performances are measured with [JMH](https://github.com/ktoso/sbt-jmh) 23 | 24 | # How to run it yourself? 25 | 26 | Clone the project and start `sbt`. 27 | 28 | 29 | # Performance test 30 | 31 | ## Run with 32 | 33 | jmh:run -i 10 -wi 10 -f1 -t1 34 | 35 | (for a quick feedback:) 36 | 37 | jmh:run -i 2 -wi 2 -f1 -t1 38 | 39 | 40 | ## Deserialization (String -> case class) 41 | 42 | ``` 43 | Benchmark Mode Cnt Score Error Units 44 | BigJsonBenchmarkDeserialize.runJson4sJackson avgt 10 0.810 ± 0.010 ms/op 45 | BigJsonBenchmarkDeserialize.runArgonautJson avgt 10 0.523 ± 0.007 ms/op 46 | BigJsonBenchmarkDeserialize.runPlayJson avgt 10 0.456 ± 0.067 ms/op 47 | BigJsonBenchmarkDeserialize.runCirce avgt 10 0.256 ± 0.029 ms/op 48 | BigJsonBenchmarkDeserialize.runSprayJson avgt 10 0.237 ± 0.021 ms/op 49 | BigJsonBenchmarkDeserialize.runRefuelParsing avgt 10 0.219 ± 0.002 ms/op 50 | BigJsonBenchmarkDeserialize.runSphereJson avgt 10 0.167 ± 0.001 ms/op 51 | BigJsonBenchmarkDeserialize.runWeePickle avgt 10 0.151 ± 0.001 ms/op 52 | BigJsonBenchmarkDeserialize.runUPickle avgt 10 0.141 ± 0.001 ms/op 53 | BigJsonBenchmarkDeserialize.runJacksonParsing avgt 10 0.138 ± 0.001 ms/op 54 | BigJsonBenchmarkDeserialize.runBorer avgt 10 0.105 ± 0.001 ms/op 55 | BigJsonBenchmarkDeserialize.runJsoniter avgt 10 0.060 ± 0.007 ms/op 56 | ``` 57 | _ordered (lower is better)_ 58 | 59 | ## Serialization (case class -> String) 60 | 61 | ``` 62 | Benchmark Mode Cnt Score Error Units 63 | BigJsonBenchmarkSerialize.runJson4sJackson avgt 10 0.847 ± 0.007 ms/op 64 | BigJsonBenchmarkSerialize.runPlayJson avgt 10 0.531 ± 0.003 ms/op 65 | BigJsonBenchmarkSerialize.runArgonautJson avgt 10 0.335 ± 0.030 ms/op 66 | BigJsonBenchmarkSerialize.runRefuelParsing avgt 10 0.271 ± 0.033 ms/op 67 | BigJsonBenchmarkSerialize.runCirce avgt 10 0.237 ± 0.002 ms/op 68 | BigJsonBenchmarkSerialize.runSprayJson avgt 10 0.215 ± 0.001 ms/op 69 | BigJsonBenchmarkSerialize.runBorer avgt 10 0.103 ± 0.001 ms/op 70 | BigJsonBenchmarkSerialize.runUPickle avgt 10 0.098 ± 0.008 ms/op 71 | BigJsonBenchmarkSerialize.runWeePickle avgt 10 0.098 ± 0.001 ms/op 72 | BigJsonBenchmarkSerialize.runSphereJson avgt 10 0.091 ± 0.001 ms/op 73 | BigJsonBenchmarkSerialize.runJacksonParsing avgt 10 0.063 ± 0.004 ms/op 74 | BigJsonBenchmarkSerialize.runJsoniter avgt 10 0.042 ± 0.001 ms/op 75 | 76 | ``` 77 | _ordered (lower is better)_ 78 | 79 | # Pressure on the GC 80 | 81 | ## Run with 82 | 83 | jmh:run -i 10 -wi 10 -f1 -t1 -prof gc 84 | 85 | ## Deserialization (String -> case class) 86 | 87 | ``` 88 | Benchmark Mode Cnt Score Error Units 89 | BigJsonBenchmarkDeserialize.runArgonautJson:·gc.alloc.rate.norm avgt 10 2578769.986 ± 53.779 B/op 90 | BigJsonBenchmarkDeserialize.runArgonautJson:·gc.count avgt 10 96.000 counts 91 | BigJsonBenchmarkDeserialize.runArgonautJson:·gc.time avgt 10 63.000 ms 92 | 93 | BigJsonBenchmarkDeserialize.runJson4sJackson:·gc.alloc.rate.norm avgt 10 2065771.154 ± 15.221 B/op 94 | BigJsonBenchmarkDeserialize.runJson4sJackson:·gc.count avgt 10 62.000 counts 95 | BigJsonBenchmarkDeserialize.runJson4sJackson:·gc.time avgt 10 47.000 ms 96 | 97 | BigJsonBenchmarkDeserialize.runPlayJson:·gc.alloc.rate.norm avgt 10 1979111.057 ± 8.216 B/op 98 | BigJsonBenchmarkDeserialize.runPlayJson:·gc.count avgt 10 107.000 counts 99 | BigJsonBenchmarkDeserialize.runPlayJson:·gc.time avgt 10 53.000 ms 100 | 101 | BigJsonBenchmarkDeserialize.runCirce:·gc.alloc.rate.norm avgt 10 867614.524 ± 9.233 B/op 102 | BigJsonBenchmarkDeserialize.runCirce:·gc.count avgt 10 86.000 counts 103 | BigJsonBenchmarkDeserialize.runCirce:·gc.time avgt 10 47.000 ms 104 | 105 | BigJsonBenchmarkDeserialize.runRefuelParsing:·gc.alloc.rate.norm avgt 10 814906.798 ± 3.588 B/op 106 | BigJsonBenchmarkDeserialize.runRefuelParsing:·gc.count avgt 10 92.000 counts 107 | BigJsonBenchmarkDeserialize.runRefuelParsing:·gc.time avgt 10 44.000 ms 108 | 109 | BigJsonBenchmarkDeserialize.runSprayJson:·gc.alloc.rate.norm avgt 10 511955.300 ± 5.591 B/op 110 | BigJsonBenchmarkDeserialize.runSprayJson:·gc.count avgt 10 51.000 counts 111 | BigJsonBenchmarkDeserialize.runSprayJson:·gc.time avgt 10 27.000 ms 112 | 113 | BigJsonBenchmarkDeserialize.runUPickle:·gc.alloc.rate.norm avgt 10 413386.161 ± 2.526 B/op 114 | BigJsonBenchmarkDeserialize.runUPickle:·gc.count avgt 10 70.000 counts 115 | BigJsonBenchmarkDeserialize.runUPickle:·gc.time avgt 10 31.000 ms 116 | 117 | BigJsonBenchmarkDeserialize.runSphereJson:·gc.alloc.rate.norm avgt 10 371895.585 ± 3.512 B/op 118 | BigJsonBenchmarkDeserialize.runSphereJson:·gc.count avgt 10 54.000 counts 119 | BigJsonBenchmarkDeserialize.runSphereJson:·gc.time avgt 10 34.000 ms 120 | 121 | BigJsonBenchmarkDeserialize.runWeePickle:·gc.alloc.rate.norm avgt 10 139977.664 ± 3.377 B/op 122 | BigJsonBenchmarkDeserialize.runWeePickle:·gc.count avgt 10 23.000 counts 123 | BigJsonBenchmarkDeserialize.runWeePickle:·gc.time avgt 10 10.000 ms 124 | 125 | BigJsonBenchmarkDeserialize.runJsoniter:·gc.alloc.rate.norm avgt 10 129080.363 ± 1.384 B/op 126 | BigJsonBenchmarkDeserialize.runJsoniter:·gc.count avgt 10 53.000 counts 127 | BigJsonBenchmarkDeserialize.runJsoniter:·gc.time avgt 10 23.000 ms 128 | 129 | BigJsonBenchmarkDeserialize.runJacksonParsing:·gc.alloc.rate.norm avgt 10 123744.709 ± 2.402 B/op 130 | BigJsonBenchmarkDeserialize.runJacksonParsing:·gc.count avgt 10 23.000 counts 131 | BigJsonBenchmarkDeserialize.runJacksonParsing:·gc.time avgt 10 11.000 ms 132 | 133 | BigJsonBenchmarkDeserialize.runBorer:·gc.alloc.rate.norm avgt 10 121024.180 ± 2.153 B/op 134 | BigJsonBenchmarkDeserialize.runBorer:·gc.count avgt 10 28.000 counts 135 | BigJsonBenchmarkDeserialize.runBorer:·gc.time avgt 10 12.000 ms 136 | ``` 137 | _ordered (lower is better)_ 138 | 139 | ## Serialization (case class -> String): 140 | 141 | ``` 142 | Benchmark Mode Cnt Score Error Units 143 | BigJsonBenchmarkSerialize.runJson4sJackson:·gc.alloc.rate.norm avgt 10 3153258.449 ± 14.184 B/op 144 | BigJsonBenchmarkSerialize.runJson4sJackson:·gc.count avgt 10 91.000 counts 145 | BigJsonBenchmarkSerialize.runJson4sJackson:·gc.time avgt 10 53.000 ms 146 | 147 | BigJsonBenchmarkSerialize.runPlayJson:·gc.alloc.rate.norm avgt 10 1968760.583 ± 5.757 B/op 148 | BigJsonBenchmarkSerialize.runPlayJson:·gc.count avgt 10 89.000 counts 149 | BigJsonBenchmarkSerialize.runPlayJson:·gc.time avgt 10 50.000 ms 150 | 151 | BigJsonBenchmarkSerialize.runArgonautJson:·gc.alloc.rate.norm avgt 10 1076875.212 ± 5.822 B/op 152 | BigJsonBenchmarkSerialize.runArgonautJson:·gc.count avgt 10 88.000 counts 153 | BigJsonBenchmarkSerialize.runArgonautJson:·gc.time avgt 10 61.000 ms 154 | 155 | BigJsonBenchmarkSerialize.runRefuelParsing:·gc.alloc.rate.norm avgt 10 893111.629 ± 22.419 B/op 156 | BigJsonBenchmarkSerialize.runRefuelParsing:·gc.count avgt 10 80.000 counts 157 | BigJsonBenchmarkSerialize.runRefuelParsing:·gc.time avgt 10 44.000 ms 158 | 159 | BigJsonBenchmarkSerialize.runCirce:·gc.alloc.rate.norm avgt 10 765495.574 ± 3.241 B/op 160 | BigJsonBenchmarkSerialize.runCirce:·gc.count avgt 10 79.000 counts 161 | BigJsonBenchmarkSerialize.runCirce:·gc.time avgt 10 39.000 ms 162 | 163 | BigJsonBenchmarkSerialize.runSprayJson:·gc.alloc.rate.norm avgt 10 643952.034 ± 4.426 B/op 164 | BigJsonBenchmarkSerialize.runSprayJson:·gc.count avgt 10 73.000 counts 165 | BigJsonBenchmarkSerialize.runSprayJson:·gc.time avgt 10 36.000 ms 166 | 167 | BigJsonBenchmarkSerialize.runSphereJson:·gc.alloc.rate.norm avgt 10 222518.314 ± 1.144 B/op 168 | BigJsonBenchmarkSerialize.runSphereJson:·gc.count avgt 10 60.000 counts 169 | BigJsonBenchmarkSerialize.runSphereJson:·gc.time avgt 10 35.000 ms 170 | 171 | BigJsonBenchmarkSerialize.runUPickle:·gc.alloc.rate.norm avgt 10 138161.427 ± 2.101 B/op 172 | BigJsonBenchmarkSerialize.runUPickle:·gc.count avgt 10 35.000 counts 173 | BigJsonBenchmarkSerialize.runUPickle:·gc.time avgt 10 14.000 ms 174 | 175 | BigJsonBenchmarkSerialize.runWeePickle:·gc.alloc.rate.norm avgt 10 106655.199 ± 1.394 B/op 176 | BigJsonBenchmarkSerialize.runWeePickle:·gc.count avgt 10 30.000 counts 177 | BigJsonBenchmarkSerialize.runWeePickle:·gc.time avgt 10 13.000 ms 178 | 179 | BigJsonBenchmarkSerialize.runJsoniter:·gc.alloc.rate.norm avgt 10 97582.325 ± 0.290 B/op 180 | BigJsonBenchmarkSerialize.runJsoniter:·gc.count avgt 10 60.000 counts 181 | BigJsonBenchmarkSerialize.runJsoniter:·gc.time avgt 10 26.000 ms 182 | 183 | BigJsonBenchmarkSerialize.runBorer:·gc.alloc.rate.norm avgt 10 96806.575 ± 2.005 B/op 184 | BigJsonBenchmarkSerialize.runBorer:·gc.count avgt 10 23.000 counts 185 | BigJsonBenchmarkSerialize.runBorer:·gc.time avgt 10 10.000 ms 186 | 187 | BigJsonBenchmarkSerialize.runJacksonParsing:·gc.alloc.rate.norm avgt 10 46387.296 ± 0.916 B/op 188 | BigJsonBenchmarkSerialize.runJacksonParsing:·gc.count avgt 10 19.000 counts 189 | BigJsonBenchmarkSerialize.runJacksonParsing:·gc.time avgt 10 9.000 ms 190 | ``` 191 | _ordered (lower is better)_ 192 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := """json-perf""" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.13.5" 6 | 7 | val json4sVersion = "4.0.3" 8 | val circeVersion = "0.13.0" 9 | 10 | resolvers ++= 11 | Resolver.bintrayRepo("commercetools", "maven") :: 12 | Resolver.bintrayRepo("rallyhealth", "maven") :: 13 | Nil 14 | 15 | libraryDependencies ++= 16 | "io.circe" %% "circe-generic" % circeVersion :: 17 | "io.circe" %% "circe-parser" % circeVersion :: 18 | "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.12.3" :: 19 | "org.json4s" %% "json4s-native" % json4sVersion :: 20 | "org.json4s" %% "json4s-jackson" % json4sVersion :: 21 | "com.commercetools" %% "sphere-json" % "0.12.5" :: 22 | "com.typesafe.play" %% "play-json" % "2.9.2" :: 23 | "io.spray" %% "spray-json" % "1.3.6" :: 24 | "io.argonaut" %% "argonaut" % "6.3.3" :: 25 | "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.8.1" :: 26 | "com.lihaoyi" %% "upickle" % "1.3.12" :: 27 | "com.phylage" %% "refuel-json" % "2.0.1" :: 28 | "io.bullet" %% "borer-derivation" % "1.7.2" :: 29 | "com.rallyhealth" %% "weepickle-v1" % "1.4.1" :: 30 | Nil 31 | 32 | libraryDependencies ++= 33 | "org.scalatest" %% "scalatest" % "3.2.8" :: 34 | Nil map (_ % Test) 35 | 36 | parallelExecution in Test := false 37 | 38 | enablePlugins(JmhPlugin) 39 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.1") 2 | -------------------------------------------------------------------------------- /src/main/scala/jsonperf/BigJsonTest.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | case class Person(name: String, age: Int) 4 | case class BigJson(colleagues: Vector[Person]) 5 | 6 | class BigJsonTest extends JsonTest[BigJson] with Serializable { 7 | 8 | val total = 1000 9 | def colleagues = (for (i ← 1 to 1000) yield s"""{"name": "person-$i", "age": $i}""").mkString(", ") 10 | val json = 11 | s"""{ 12 | | "colleagues": [ 13 | | $colleagues 14 | | ] 15 | |} 16 | """.stripMargin 17 | 18 | override val newA = BigJson(colleagues = (for (i ← 1 to 1000) yield Person(s"person-$i", i)).toVector) 19 | override val clazz = classOf[BigJson] 20 | 21 | override def playFormat = { 22 | import play.api.libs.json.Json 23 | implicit val personReads = Json.format[Person] 24 | Json.format[BigJson] 25 | } 26 | 27 | override def sphereJSON = { 28 | import io.sphere.json.generic._ 29 | implicit val personFromJson = jsonProduct(Person.apply _) 30 | deriveJSON[BigJson] 31 | } 32 | 33 | override def sprayJsonFormat = { 34 | import spray.json.DefaultJsonProtocol._ 35 | implicit val personFormat = spray.json.DefaultJsonProtocol.jsonFormat2(Person) 36 | spray.json.DefaultJsonProtocol.jsonFormat1(BigJson) 37 | } 38 | 39 | override def argonautCodec = { 40 | import argonaut.Argonaut._ 41 | implicit val personCode = casecodec2(Person.apply, Person.unapply)("name", "age") 42 | casecodec1(BigJson.apply, BigJson.unapply)("colleagues") 43 | } 44 | 45 | override def circeEncoder = { 46 | import io.circe.generic.semiauto._ 47 | implicit val personEncoder = deriveEncoder[Person] 48 | deriveEncoder[BigJson] 49 | } 50 | 51 | override def circeDecoder = { 52 | import io.circe.generic.semiauto._ 53 | implicit val personDecoder = deriveDecoder[Person] 54 | deriveDecoder[BigJson] 55 | } 56 | 57 | 58 | override def jsoniterCodec = { 59 | import com.github.plokhotnyuk.jsoniter_scala.macros._ 60 | JsonCodecMaker.make[BigJson](CodecMakerConfig) 61 | } 62 | 63 | override def uPickleRW = { 64 | import upickle.default.{ReadWriter => RW, macroRW} 65 | implicit val personRW: RW[Person] = macroRW 66 | upickle.default.macroRW 67 | } 68 | 69 | override def weePickleFromTo = { 70 | import com.rallyhealth.weepickle.v1.WeePickle._ 71 | implicit val personFromTo: FromTo[Person] = macroFromTo[Person] 72 | macroFromTo[BigJson] 73 | } 74 | 75 | override def borerCodec = { 76 | import io.bullet.borer.derivation.MapBasedCodecs 77 | implicit val personCodec: io.bullet.borer.Codec[Person] = MapBasedCodecs.deriveCodec[Person] 78 | MapBasedCodecs.deriveCodec[BigJson] 79 | } 80 | 81 | override def refuelCodec = { 82 | import refuel.json.JsonTransform._ 83 | Derive[BigJson] 84 | } 85 | 86 | override def checkResult(result: BigJson): Unit = { 87 | assert(result.colleagues.size == total, s"result.colleagues.size(${result.colleagues.size}) != $total") 88 | for (i ← 1 to 1000) { 89 | val c = result.colleagues(i - 1) 90 | assert(c.name == s"person-$i", s"name(${c.name}) != 'person-$i'") 91 | assert(c.age == i) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/scala/jsonperf/Jackson.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.module.scala.DefaultScalaModule 5 | 6 | object Jackson { 7 | 8 | val mapper = new ObjectMapper() 9 | mapper.registerModule(DefaultScalaModule) 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/jsonperf/JmhBenchmarks.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import org.openjdk.jmh.annotations._ 6 | 7 | 8 | @State(Scope.Benchmark) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 11 | @Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) 12 | @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) 13 | @Fork(value = 1, jvmArgs = Array( 14 | "-server", 15 | "-Xms1g", 16 | "-Xmx1g", 17 | "-XX:NewSize=512m", 18 | "-XX:MaxNewSize=512m", 19 | "-XX:InitialCodeCacheSize=256m", 20 | "-XX:ReservedCodeCacheSize=256m", 21 | "-XX:+UseParallelGC", 22 | "-XX:-UseAdaptiveSizePolicy", 23 | "-XX:MaxInlineLevel=18", 24 | "-XX:-UseBiasedLocking", 25 | "-XX:+AlwaysPreTouch", 26 | "-XX:+UseNUMA", 27 | "-XX:-UseAdaptiveNUMAChunkSizing" 28 | )) 29 | abstract class JmhBenchmarks[A <: AnyRef, B](val test: JsonTest[A]) { 30 | import test._ 31 | 32 | def runTest(implementation: JsonParsing[A]): B 33 | 34 | @Benchmark 35 | def runRefuelParsing: B = runTest(refuelParsing) 36 | 37 | @Benchmark 38 | def runNoParsing: B = runTest(noParsing) 39 | 40 | @Benchmark 41 | def runJacksonParsing: B = runTest(jacksonParsing) 42 | 43 | @Benchmark 44 | def runJson4sNative: B = runTest(json4sNative) 45 | 46 | @Benchmark 47 | def runJson4sJackson: B = runTest(json4sJackson) 48 | 49 | @Benchmark 50 | def runSphereJson: B = runTest(sphereJson) 51 | 52 | @Benchmark 53 | def runPlayJson: B = runTest(playJson) 54 | 55 | @Benchmark 56 | def runSprayJson: B = runTest(sprayJson) 57 | 58 | @Benchmark 59 | def runArgonautJson: B = runTest(argonautJson) 60 | 61 | @Benchmark 62 | def runCirce: B = runTest(circeJson) 63 | 64 | @Benchmark 65 | def runJsoniter: B = runTest(jsoniter) 66 | 67 | @Benchmark 68 | def runUPickle: B = runTest(uPickle) 69 | 70 | @Benchmark 71 | def runWeePickle: B = runTest(weePickle) 72 | 73 | @Benchmark 74 | def runBorer: B = runTest(borer) 75 | } 76 | 77 | class BigJsonBenchmarkDeserialize extends JmhBenchmarks[BigJson, BigJson](new BigJsonTest) { 78 | def runTest(implementation: JsonParsing[BigJson]): BigJson = { 79 | implementation.deserialize(test.json) 80 | } 81 | } 82 | 83 | class BigJsonBenchmarkSerialize extends JmhBenchmarks[BigJson, String](new BigJsonTest) { 84 | def runTest(implementation: JsonParsing[BigJson]): String = { 85 | implementation.serialize(test.newA) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/scala/jsonperf/JsonParsing.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | trait JsonParsing[A] extends Serializable { 4 | def deserialize(s: String): A 5 | def serialize(a: A): String 6 | } -------------------------------------------------------------------------------- /src/main/scala/jsonperf/JsonTest.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | import java.nio.charset.StandardCharsets 4 | 5 | abstract class JsonTest[A <: AnyRef](implicit ev: scala.reflect.Manifest[A]) extends Serializable { 6 | 7 | def json: String 8 | def newA: A 9 | def clazz: Class[A] 10 | def refuelCodec: refuel.json.codecs.Codec[A] 11 | def sphereJSON: io.sphere.json.JSON[A] 12 | def playFormat: play.api.libs.json.Format[A] 13 | def sprayJsonFormat: spray.json.JsonFormat[A] 14 | def argonautCodec: argonaut.CodecJson[A] 15 | def circeEncoder: io.circe.Encoder[A] 16 | def circeDecoder: io.circe.Decoder[A] 17 | def jsoniterCodec: com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec[A] 18 | def uPickleRW: upickle.default.ReadWriter[A] 19 | def weePickleFromTo: com.rallyhealth.weepickle.v1.WeePickle.FromTo[A] 20 | def borerCodec: io.bullet.borer.Codec[A] 21 | 22 | def checkResult(result: A): Unit 23 | 24 | 25 | type Parsing = JsonParsing[A] 26 | 27 | val refuelParsing = new Parsing with refuel.json.JsonTransform { 28 | val codec: refuel.json.codecs.Codec[A] = refuelCodec 29 | override def deserialize(json: String): A = json.readAs[A](codec).get 30 | override def serialize(a: A): String = a.writeAsString(codec).get 31 | override def toString: String = "refuelParsing" 32 | } 33 | 34 | val noParsing: Parsing = new Parsing { 35 | override def deserialize(json: String): A = newA 36 | override def serialize(a: A): String = "" 37 | override def toString: String = "noParsing" 38 | } 39 | 40 | val jacksonParsing: Parsing = new Parsing { 41 | override def deserialize(json: String): A = Jackson.mapper.readValue(json, clazz) 42 | override def serialize(a: A): String = Jackson.mapper.writeValueAsString(a) 43 | override def toString: String = "jackson" 44 | } 45 | 46 | val json4sNative: Parsing = new Parsing { 47 | import org.json4s._ 48 | import org.json4s.native.JsonMethods._ 49 | import org.json4s.native.Serialization 50 | implicit val formats = DefaultFormats 51 | override def deserialize(json: String): A = { 52 | org.json4s.native.JsonMethods.parse(json).extract[A] 53 | } 54 | override def serialize(a: A): String = { 55 | Serialization.write(a) 56 | } 57 | override def toString: String = "json4sNative" 58 | } 59 | 60 | val json4sJackson: Parsing = new Parsing { 61 | import org.json4s._ 62 | import org.json4s.jackson.JsonMethods._ 63 | import org.json4s.jackson.Serialization 64 | implicit val formats = DefaultFormats 65 | override def deserialize(json: String): A = { 66 | parse(json).extract[A] 67 | } 68 | override def serialize(a: A): String = { 69 | Serialization.write(a) 70 | } 71 | override def toString: String = "json4sJackson" 72 | } 73 | 74 | val sphereJson: Parsing = new Parsing { 75 | val fromToJson = sphereJSON 76 | override def deserialize(json: String): A = { 77 | io.sphere.json.getFromJSON(json)(fromToJson) 78 | } 79 | override def serialize(a: A): String = { 80 | io.sphere.json.toJSON(a)(fromToJson) 81 | } 82 | override def toString: String = "sphereJson" 83 | } 84 | 85 | val playJson: Parsing = new Parsing { 86 | val format = playFormat 87 | override def deserialize(json: String): A = { 88 | import play.api.libs.json.Json 89 | Json.parse(json).as[A](format) 90 | } 91 | override def serialize(a: A): String = { 92 | import play.api.libs.json.Json 93 | Json.stringify(Json.toJson(a)(format)) 94 | } 95 | override def toString: String = "playJson" 96 | } 97 | 98 | val sprayJson: Parsing = new Parsing { 99 | val format = sprayJsonFormat 100 | override def deserialize(json: String): A = { 101 | spray.json.JsonParser(json).convertTo[A](format) 102 | } 103 | override def serialize(a: A): String = { 104 | import spray.json._ 105 | a.toJson(format).compactPrint 106 | } 107 | override def toString: String = "sprayJson" 108 | } 109 | 110 | val argonautJson: Parsing = new Parsing { 111 | val codec = argonautCodec 112 | override def deserialize(json: String): A = { 113 | import argonaut.Argonaut._ 114 | json.decodeOption[A](codec).get 115 | } 116 | override def serialize(a: A): String = { 117 | import argonaut.Argonaut._ 118 | a.asJson(codec).toString() 119 | } 120 | override def toString: String = "argonaut" 121 | } 122 | 123 | val circeJson: Parsing = new Parsing { 124 | val encoder = circeEncoder 125 | val decoder = circeDecoder 126 | override def deserialize(json: String): A = { 127 | io.circe.parser.decode[A](json)(decoder).getOrElse(throw new Exception) 128 | } 129 | override def serialize(a: A): String = { 130 | import io.circe.syntax._ 131 | a.asJson(encoder).noSpaces 132 | } 133 | override def toString: String = "circe" 134 | } 135 | 136 | val jsoniter: Parsing = new Parsing { 137 | import com.github.plokhotnyuk.jsoniter_scala.core._ 138 | val codec = jsoniterCodec 139 | override def deserialize(s: String): A = { 140 | readFromString(s)(codec) 141 | } 142 | override def serialize(a: A): String = { 143 | writeToString(a)(codec) 144 | } 145 | override def toString: String = "jsoniter" 146 | } 147 | 148 | val uPickle: Parsing = new Parsing { 149 | override def deserialize(s: String): A = { 150 | upickle.default.read[A](s)(uPickleRW) 151 | } 152 | override def serialize(a: A): String = { 153 | upickle.default.write[A](a)(uPickleRW) 154 | } 155 | override def toString: String = "uPickle" 156 | } 157 | 158 | val weePickle: Parsing = new Parsing { 159 | import com.rallyhealth.weejson.v1.jackson._ 160 | import com.rallyhealth.weepickle.v1.WeePickle._ 161 | val fromTo = weePickleFromTo 162 | override def deserialize(s: String): A = { 163 | FromJson(s).transform(ToScala[A](fromTo)) 164 | } 165 | override def serialize(a: A): String = { 166 | FromScala(a)(fromTo).transform(ToJson.string) 167 | } 168 | override def toString: String = "weePickle" 169 | } 170 | 171 | val borer: Parsing = new Parsing { 172 | import io.bullet.borer.{Codec, Decoder, Encoder, Json} 173 | implicit val Codec(encoder: Encoder[A], decoder: Decoder[A]) = borerCodec 174 | override def deserialize(s: String): A = { 175 | Json.decode(s.getBytes(StandardCharsets.UTF_8)).to[A].value 176 | } 177 | override def serialize(a: A): String = { 178 | Json.encode(a).toUtf8String 179 | } 180 | override def toString: String = "borer" 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/test/scala/jsonperf/UnitTest.scala: -------------------------------------------------------------------------------- 1 | package jsonperf 2 | 3 | import org.scalatest.prop.TableDrivenPropertyChecks 4 | import org.scalatest.freespec.AnyFreeSpec 5 | import org.scalatest.matchers.should.Matchers 6 | 7 | class UnitTest extends AnyFreeSpec with Matchers with TableDrivenPropertyChecks { 8 | 9 | val tests = Table( 10 | "test", 11 | new BigJsonTest 12 | ) 13 | 14 | forAll(tests) { test ⇒ 15 | s"with test ${test.getClass.getName}" - { 16 | import test._ 17 | val jsonParsings = Table( 18 | "parser", 19 | argonautJson, jacksonParsing, json4sJackson, json4sNative, playJson, sphereJson, sprayJson, circeJson, jsoniter, uPickle, weePickle, borer) 20 | 21 | forAll(jsonParsings) { jsonParsing ⇒ 22 | s"using parser '$jsonParsing'" - { 23 | "should deserialize json" in { 24 | val json = test.json 25 | val result = jsonParsing.deserialize(json) 26 | result shouldEqual test.newA 27 | } 28 | 29 | "should serialize json" in { 30 | val a = test.newA 31 | val result = jsonParsing.serialize(a) 32 | val aa = jsonParsing.deserialize(result) 33 | aa should be (a) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | } 41 | --------------------------------------------------------------------------------