From 082127bb1adb7fa599065428cb6761da0f784a4a Mon Sep 17 00:00:00 2001 From: Georgii Kovalev Date: Mon, 3 Feb 2025 11:44:47 +0500 Subject: [PATCH 1/4] not important changes like make lazy val plain val --- .../bench/handwritten/HandwrittenBench.scala | 111 ++++++++++-------- .../tethys/OrdinalEnumJsonReader.scala | 29 ++--- .../tethys/OrdinalEnumJsonWriter.scala | 21 ++-- .../scala-3/tethys/StringEnumJsonReader.scala | 31 ++--- .../scala-3/tethys/StringEnumJsonWriter.scala | 21 ++-- .../tethys/derivation/Derivation.scala | 2 +- .../main/scala/tethys/commons/TokenNode.scala | 5 +- .../main/scala/tethys/readers/KeyReader.scala | 19 ++- .../readers/instances/OptionReaders.scala | 14 +-- .../main/scala/tethys/writers/KeyWriter.scala | 19 ++- .../writers/instances/AllJsonWriters.scala | 48 ++++---- .../writers/instances/IterableWriters.scala | 18 +-- .../writers/instances/OptionWriters.scala | 2 +- .../tethys/readers/DefaultReadersTest.scala | 5 +- .../readers/JsonReaderBuilderTest.scala | 2 +- .../derivation/AutoReaderDerivationTest.scala | 2 +- .../derivation/RedundantJsonReaderTest.scala | 2 +- 17 files changed, 185 insertions(+), 166 deletions(-) diff --git a/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala b/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala index 42d27f32..5b822272 100644 --- a/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/handwritten/HandwrittenBench.scala @@ -14,10 +14,10 @@ object HandwrittenBench { val builder = new StringBuilder("[") val dataIterator = seq.iterator - if(dataIterator.hasNext) { + if (dataIterator.hasNext) { writeData(dataIterator.next(), builder) } - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), builder.append(",")) } @@ -45,10 +45,10 @@ object HandwrittenBench { .append('[') val intIter = data.seqInt.iterator - if(intIter.hasNext) { + if (intIter.hasNext) { builder.append(intIter.next()) } - while(intIter.hasNext) { + while (intIter.hasNext) { builder.append(",").append(intIter.next()) } @@ -58,11 +58,11 @@ object HandwrittenBench { .append('{') val mapStringIntIter = data.mapStringInt.iterator - if(mapStringIntIter.hasNext) { + if (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } @@ -73,7 +73,8 @@ object HandwrittenBench { } - private implicit class ScalaBuilderOps(val builder: StringBuilder) extends AnyVal { + private implicit class ScalaBuilderOps(val builder: StringBuilder) + extends AnyVal { def appendName(name: String): StringBuilder = { builder.append('"') appendString(builder, name) @@ -89,32 +90,33 @@ object HandwrittenBench { private def appendString(builder: StringBuilder, s: String): Unit = { var i = 0 - while(i < s.length) { + while (i < s.length) { appendChar(builder, s.charAt(i)) i = i + 1 } } - private def appendChar(builder: StringBuilder, char: Char): Unit = char match { - case '\n' => builder.append("\\n") - case '\r' => builder.append("\\r") - case '\t' => builder.append("\\t") - case '\b' => builder.append("\\b") - case '\f' => builder.append("\\f") - case '\\' => builder.append("\\\\") - case '"' => builder.append("\\\"") - case _ => builder.append(char) - } + private def appendChar(builder: StringBuilder, char: Char): Unit = + char match { + case '\n' => builder.append("\\n") + case '\r' => builder.append("\\r") + case '\t' => builder.append("\\t") + case '\b' => builder.append("\\b") + case '\f' => builder.append("\\f") + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case _ => builder.append(char) + } object HandwrittenJavaDataWriter extends DataWriter { override def write(seq: Seq[Data]): String = { val builder = new java.lang.StringBuilder("[") val dataIterator = seq.iterator - if(dataIterator.hasNext) { + if (dataIterator.hasNext) { writeData(dataIterator.next(), builder) } - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), builder.append(",")) } @@ -123,7 +125,10 @@ object HandwrittenBench { .toString() } - private def writeData(data: Data, builder: java.lang.StringBuilder): Unit = { + private def writeData( + data: Data, + builder: java.lang.StringBuilder + ): Unit = { builder .append("{") .appendName("string") @@ -142,10 +147,10 @@ object HandwrittenBench { .append('[') val intIter = data.seqInt.iterator - if(intIter.hasNext) { + if (intIter.hasNext) { builder.append(intIter.next()) } - while(intIter.hasNext) { + while (intIter.hasNext) { builder.append(",").append(intIter.next()) } @@ -155,11 +160,11 @@ object HandwrittenBench { .append('{') val mapStringIntIter = data.mapStringInt.iterator - if(mapStringIntIter.hasNext) { + if (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() builder.appendName(key).append(value) } @@ -169,7 +174,8 @@ object HandwrittenBench { } } - private implicit class JavaBuilderOps(val builder: java.lang.StringBuilder) extends AnyVal { + private implicit class JavaBuilderOps(val builder: java.lang.StringBuilder) + extends AnyVal { def appendName(name: String): java.lang.StringBuilder = { builder.append('"') appendString(builder, name) @@ -183,24 +189,28 @@ object HandwrittenBench { } } - private def appendString(builder: java.lang.StringBuilder, s: String): Unit = { + private def appendString( + builder: java.lang.StringBuilder, + s: String + ): Unit = { var i = 0 - while(i < s.length) { + while (i < s.length) { appendChar(builder, s.charAt(i)) i = i + 1 } } - private def appendChar(builder: java.lang.StringBuilder, char: Char): Unit = char match { - case '\n' => builder.append("\\n") - case '\r' => builder.append("\\r") - case '\t' => builder.append("\\t") - case '\b' => builder.append("\\b") - case '\f' => builder.append("\\f") - case '\\' => builder.append("\\\\") - case '"' => builder.append("\\\"") - case _ => builder.append(char) - } + private def appendChar(builder: java.lang.StringBuilder, char: Char): Unit = + char match { + case '\n' => builder.append("\\n") + case '\r' => builder.append("\\r") + case '\t' => builder.append("\\t") + case '\b' => builder.append("\\b") + case '\f' => builder.append("\\f") + case '\\' => builder.append("\\\\") + case '"' => builder.append("\\\"") + case _ => builder.append(char) + } object HandwrittenJacksonDataProcessor extends DataWriter with DataReader { private val jsonFactory = { @@ -227,7 +237,7 @@ object HandwrittenBench { generator.writeStartArray() val dataIterator = seq.iterator - while(dataIterator.hasNext) { + while (dataIterator.hasNext) { writeData(dataIterator.next(), generator) } @@ -251,11 +261,10 @@ object HandwrittenBench { generator.writeFieldName("bigDecimal") generator.writeNumber(data.bigDecimal.bigDecimal) - generator.writeFieldName("seqInt") generator.writeStartArray() val intIter = data.seqInt.iterator - while(intIter.hasNext) { + while (intIter.hasNext) { generator.writeNumber(intIter.next) } generator.writeEndArray() @@ -264,7 +273,7 @@ object HandwrittenBench { generator.writeStartObject() val mapStringIntIter = data.mapStringInt.iterator - while(mapStringIntIter.hasNext) { + while (mapStringIntIter.hasNext) { val (key, value) = mapStringIntIter.next() generator.writeFieldName(key) generator.writeNumber(value) @@ -278,7 +287,7 @@ object HandwrittenBench { val parser = jsonParser(json) val builder = Seq.newBuilder[Data] require(parser.nextToken() == JsonToken.START_ARRAY) - while(parser.nextToken() != JsonToken.END_ARRAY) { + while (parser.nextToken() != JsonToken.END_ARRAY) { builder += readDataObject(parser) } builder.result() @@ -303,24 +312,24 @@ object HandwrittenBench { var mapStringIntField: Map[String, Int] = null var mapStringIntFieldInitialized: Boolean = false - while(parser.nextToken() != JsonToken.END_OBJECT) { + while (parser.nextToken() != JsonToken.END_OBJECT) { val field = parser.getCurrentName - if(field == "string") { + if (field == "string") { stringField = parser.nextTextValue() stringFieldInitialized = true - } else if(field == "int") { + } else if (field == "int") { parser.nextToken() intField = parser.getIntValue intFieldInitialized = true - } else if(field == "boolean") { + } else if (field == "boolean") { booleanField = parser.nextBooleanValue() booleanFieldInitialized = true - } else if(field == "bigDecimal") { + } else if (field == "bigDecimal") { parser.nextToken() bigDecimalField = BigDecimal(parser.getNumberValue.doubleValue()) bigDecimalFieldInitialized = true - } else if(field == "seqInt") { + } else if (field == "seqInt") { seqIntField = readSeqOfInt(parser) seqIntFieldInitialized = true } else { @@ -329,7 +338,9 @@ object HandwrittenBench { } } - require(stringFieldInitialized && intFieldInitialized && booleanFieldInitialized && bigDecimalFieldInitialized && mapStringIntFieldInitialized) + require( + stringFieldInitialized && intFieldInitialized && booleanFieldInitialized && bigDecimalFieldInitialized && mapStringIntFieldInitialized + ) Data( string = stringField, diff --git a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala index d59bdd4f..08985aaf 100644 --- a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala +++ b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonReader.scala @@ -3,20 +3,21 @@ package tethys import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.TokenIterator -trait OrdinalEnumJsonReader[A] extends JsonReader[A] +class OrdinalEnumJsonReader[A <: scala.reflect.Enum](getByOrdinal: Int => A) + extends JsonReader[A]: + override def read(it: TokenIterator)(using FieldName): A = + if it.currentToken().isNumberValue then + val res = it.int() + it.next() + try getByOrdinal(res) + catch + case ex: NoSuchElementException => + ReaderError.wrongJson(s"Unknown enum ordinal: $res") + else + ReaderError.wrongJson( + s"Expected int value but found: ${it.currentToken()}" + ) object OrdinalEnumJsonReader: inline def derived[A <: scala.reflect.Enum]: OrdinalEnumJsonReader[A] = - new OrdinalEnumJsonReader[A]: - def read(it: TokenIterator)(implicit fieldName: FieldName): A = - if it.currentToken().isNumberValue then - val res = it.int() - it.next() - try derivation.EnumCompanion.getByOrdinal[A](res) - catch - case ex: NoSuchElementException => - ReaderError.wrongJson(s"Unknown enum ordinal: $res") - else - ReaderError.wrongJson( - s"Expected int value but found: ${it.currentToken()}" - ) + new OrdinalEnumJsonReader[A](derivation.EnumCompanion.getByOrdinal[A]) diff --git a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala index 457d1330..6bf7e7d6 100644 --- a/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala +++ b/modules/core/src/main/scala-3/tethys/OrdinalEnumJsonWriter.scala @@ -2,16 +2,23 @@ package tethys import tethys.writers.tokens.TokenWriter -trait OrdinalEnumJsonWriter[A] extends JsonWriter[A] +class OrdinalEnumJsonWriter[A <: scala.reflect.Enum] extends JsonWriter[A] { + override def write(value: A, tokenWriter: TokenWriter): Unit = + tokenWriter.writeNumber(value.ordinal) +} object OrdinalEnumJsonWriter: inline def derived[A <: scala.reflect.Enum]: OrdinalEnumJsonWriter[A] = - (value: A, tokenWriter: TokenWriter) => - tokenWriter.writeNumber(value.ordinal) + new OrdinalEnumJsonWriter[A] - inline def withLabel[A <: scala.reflect.Enum]( - label: String - ): JsonObjectWriter[A] = - (value: A, tokenWriter: writers.tokens.TokenWriter) => + private class WithLabel[A <: scala.reflect.Enum](label: String) + extends JsonObjectWriter[A] { + def writeValues(value: A, tokenWriter: TokenWriter): Unit = { tokenWriter.writeFieldName(label) tokenWriter.writeNumber(value.ordinal) + } + } + def withLabel[A <: scala.reflect.Enum]( + label: String + ): JsonObjectWriter[A] = + new WithLabel[A](label) diff --git a/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala b/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala index e143d371..857ea7ab 100644 --- a/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala +++ b/modules/core/src/main/scala-3/tethys/StringEnumJsonReader.scala @@ -3,20 +3,23 @@ package tethys import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.TokenIterator -trait StringEnumJsonReader[A] extends JsonReader[A] +class StringEnumJsonReader[A <: scala.reflect.Enum]( + getByName: String => A +) extends JsonReader[A] { + override def read(it: TokenIterator)(using FieldName): A = + if it.currentToken().isStringValue then + val res = it.string() + it.next() + try getByName(res) + catch + case ex: NoSuchElementException => + ReaderError.wrongJson(s"Unknown enum name: $res") + else + ReaderError.wrongJson( + s"Expected string value but found: ${it.currentToken()}" + ) +} object StringEnumJsonReader: inline def derived[A <: scala.reflect.Enum]: StringEnumJsonReader[A] = - new StringEnumJsonReader[A]: - def read(it: TokenIterator)(implicit fieldName: FieldName): A = - if it.currentToken().isStringValue then - val res = it.string() - it.next() - try derivation.EnumCompanion.getByName[A](res) - catch - case ex: NoSuchElementException => - ReaderError.wrongJson(s"Unknown enum name: $res") - else - ReaderError.wrongJson( - s"Expected string value but found: ${it.currentToken()}" - ) + new StringEnumJsonReader[A](derivation.EnumCompanion.getByName[A]) diff --git a/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala b/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala index c63a0b84..1a3da768 100644 --- a/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala +++ b/modules/core/src/main/scala-3/tethys/StringEnumJsonWriter.scala @@ -1,16 +1,23 @@ package tethys import tethys.writers.tokens.TokenWriter -trait StringEnumJsonWriter[A] extends JsonWriter[A] +class StringEnumJsonWriter[A <: scala.reflect.Enum] extends JsonWriter[A] { + def write(value: A, tokenWriter: TokenWriter): Unit = + tokenWriter.writeString(value.toString) +} object StringEnumJsonWriter: inline def derived[A <: scala.reflect.Enum]: StringEnumJsonWriter[A] = - (value: A, tokenWriter: TokenWriter) => - tokenWriter.writeString(value.toString) + new StringEnumJsonWriter[A] - inline def withLabel[A <: scala.reflect.Enum]( - label: String - ): JsonObjectWriter[A] = - (value: A, tokenWriter: writers.tokens.TokenWriter) => + private class WithLabel[A <: scala.reflect.Enum](label: String) + extends JsonObjectWriter[A] { + def writeValues(value: A, tokenWriter: TokenWriter): Unit = { tokenWriter.writeFieldName(label) tokenWriter.writeString(value.toString) + } + } + + def withLabel[A <: scala.reflect.Enum]( + label: String + ): JsonObjectWriter[A] = new WithLabel[A](label) diff --git a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala index 66661169..4eb51562 100644 --- a/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala +++ b/modules/core/src/main/scala-3/tethys/derivation/Derivation.scala @@ -148,7 +148,7 @@ private[derivation] class DerivationMacro(val quotes: Quotes) .get(field.tpe) .fold(lookup[JsonWriter[f]])(_.asExprOf[JsonWriter[f]]) '{ - ${ writer }.write( + $writer.write( ${ field.label }, ${ field.value('{ value }.asTerm).asExprOf[f] }, tokenWriter diff --git a/modules/core/src/main/scala/tethys/commons/TokenNode.scala b/modules/core/src/main/scala/tethys/commons/TokenNode.scala index 306978d4..73d5bca7 100644 --- a/modules/core/src/main/scala/tethys/commons/TokenNode.scala +++ b/modules/core/src/main/scala/tethys/commons/TokenNode.scala @@ -2,9 +2,7 @@ package tethys.commons import tethys.JsonReader import tethys.commons.Token._ -import tethys.readers.ReaderError import tethys.readers.tokens.{QueueIterator, TokenIteratorProducer} - sealed trait TokenNode { def token: Token } @@ -144,7 +142,8 @@ object TokenNode { implicit class TokenListOps(private val tokens: Seq[TokenNode]) extends AnyVal { - import tethys.TokenIteratorOps + import tethys._ + def tokensAs[A: JsonReader]: A = QueueIterator(tokens).readJson[A].fold(throw _, identity) } diff --git a/modules/core/src/main/scala/tethys/readers/KeyReader.scala b/modules/core/src/main/scala/tethys/readers/KeyReader.scala index bcd25dbd..a41b753f 100644 --- a/modules/core/src/main/scala/tethys/readers/KeyReader.scala +++ b/modules/core/src/main/scala/tethys/readers/KeyReader.scala @@ -5,33 +5,33 @@ trait KeyReader[A] { } object KeyReader { - implicit lazy val stringKeyReader: KeyReader[String] = new KeyReader[String] { + implicit val stringKeyReader: KeyReader[String] = new KeyReader[String] { override def read(s: String)(implicit fieldName: FieldName): String = s } - implicit lazy val uuidKeyReader: KeyReader[java.util.UUID] = + implicit val uuidKeyReader: KeyReader[java.util.UUID] = new KeyReader[java.util.UUID] { override def read(s: String)(implicit fieldName: FieldName ): java.util.UUID = java.util.UUID.fromString(s) } - implicit lazy val intKeyReader: KeyReader[Int] = new KeyReader[Int] { + implicit val intKeyReader: KeyReader[Int] = new KeyReader[Int] { override def read(s: String)(implicit fieldName: FieldName): Int = s.toInt } - implicit lazy val longKeyReader: KeyReader[Long] = new KeyReader[Long] { + implicit val longKeyReader: KeyReader[Long] = new KeyReader[Long] { override def read(s: String)(implicit fieldName: FieldName): Long = s.toLong } - implicit lazy val instantKeyReader: KeyReader[java.time.Instant] = + implicit val instantKeyReader: KeyReader[java.time.Instant] = new KeyReader[java.time.Instant] { override def read(s: String)(implicit fieldName: FieldName ): java.time.Instant = java.time.Instant.parse(s) } - implicit lazy val localDateKeyReader: KeyReader[java.time.LocalDate] = + implicit val localDateKeyReader: KeyReader[java.time.LocalDate] = new KeyReader[java.time.LocalDate] { override def read( s: String @@ -40,7 +40,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) } - implicit lazy val localDateTimeKeyReader: KeyReader[java.time.LocalDateTime] = + implicit val localDateTimeKeyReader: KeyReader[java.time.LocalDateTime] = new KeyReader[java.time.LocalDateTime] { override def read( s: String @@ -49,8 +49,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) } - implicit lazy val offsetDateTimeKeyReader - : KeyReader[java.time.OffsetDateTime] = + implicit val offsetDateTimeKeyReader: KeyReader[java.time.OffsetDateTime] = new KeyReader[java.time.OffsetDateTime] { override def read( s: String @@ -59,7 +58,7 @@ object KeyReader { .parse(s, java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) } - implicit lazy val zonedDateTimeKeyReader: KeyReader[java.time.ZonedDateTime] = + implicit val zonedDateTimeKeyReader: KeyReader[java.time.ZonedDateTime] = new KeyReader[java.time.ZonedDateTime] { override def read( s: String diff --git a/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala b/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala index 7c47825f..60f4a209 100644 --- a/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala +++ b/modules/core/src/main/scala/tethys/readers/instances/OptionReaders.scala @@ -5,7 +5,7 @@ import tethys.readers.FieldName import tethys.readers.tokens.TokenIterator private[tethys] trait OptionReaders extends LowPriorityOptionReaders { - implicit lazy val byteOptionReader: JsonReader[Option[Byte]] = + implicit val byteOptionReader: JsonReader[Option[Byte]] = new OptionJsonReader[Byte] { override protected def readSomeValue( it: TokenIterator @@ -14,7 +14,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val shortOptionReader: JsonReader[Option[Short]] = + implicit val shortOptionReader: JsonReader[Option[Short]] = new OptionJsonReader[Short] { override protected def readSomeValue( it: TokenIterator @@ -23,7 +23,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val intOptionReader: JsonReader[Option[Int]] = + implicit val intOptionReader: JsonReader[Option[Int]] = new OptionJsonReader[Int] { override protected def readSomeValue( it: TokenIterator @@ -32,7 +32,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val longOptionReader: JsonReader[Option[Long]] = + implicit val longOptionReader: JsonReader[Option[Long]] = new OptionJsonReader[Long] { override protected def readSomeValue( it: TokenIterator @@ -41,7 +41,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val floatOptionReader: JsonReader[Option[Float]] = + implicit val floatOptionReader: JsonReader[Option[Float]] = new OptionJsonReader[Float] { override protected def readSomeValue( it: TokenIterator @@ -50,7 +50,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val doubleOptionReader: JsonReader[Option[Double]] = + implicit val doubleOptionReader: JsonReader[Option[Double]] = new OptionJsonReader[Double] { override protected def readSomeValue( it: TokenIterator @@ -59,7 +59,7 @@ private[tethys] trait OptionReaders extends LowPriorityOptionReaders { } } - implicit lazy val booleanOptionReader: JsonReader[Option[Boolean]] = + implicit val booleanOptionReader: JsonReader[Option[Boolean]] = new OptionJsonReader[Boolean] { override protected def readSomeValue( it: TokenIterator diff --git a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala index c624e110..17a58752 100644 --- a/modules/core/src/main/scala/tethys/writers/KeyWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/KeyWriter.scala @@ -6,26 +6,25 @@ trait KeyWriter[A] { } object KeyWriter { - implicit lazy val stringKeyWriter: KeyWriter[String] = identity + implicit val stringKeyWriter: KeyWriter[String] = identity - implicit lazy val uuidKeyWriter: KeyWriter[java.util.UUID] = _.toString + implicit val uuidKeyWriter: KeyWriter[java.util.UUID] = _.toString - implicit lazy val intKeyWriter: KeyWriter[Int] = _.toString + implicit val intKeyWriter: KeyWriter[Int] = _.toString - implicit lazy val longKeyWriter: KeyWriter[Long] = _.toString + implicit val longKeyWriter: KeyWriter[Long] = _.toString - implicit lazy val instantKeyWriter: KeyWriter[java.time.Instant] = _.toString + implicit val instantKeyWriter: KeyWriter[java.time.Instant] = _.toString - implicit lazy val localDateKeyWriter: KeyWriter[java.time.LocalDate] = + implicit val localDateKeyWriter: KeyWriter[java.time.LocalDate] = _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE) - implicit lazy val localDateTimeKeyWriter: KeyWriter[java.time.LocalDateTime] = + implicit val localDateTimeKeyWriter: KeyWriter[java.time.LocalDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME) - implicit lazy val offsetDateTimeKeyWriter - : KeyWriter[java.time.OffsetDateTime] = + implicit val offsetDateTimeKeyWriter: KeyWriter[java.time.OffsetDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME) - implicit lazy val zonedDateTimeKeyWriter: KeyWriter[java.time.ZonedDateTime] = + implicit val zonedDateTimeKeyWriter: KeyWriter[java.time.ZonedDateTime] = _.format(java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME) } diff --git a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala index 1ceb1704..422b8914 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala @@ -4,64 +4,64 @@ import tethys.JsonWriter import tethys.writers.tokens.TokenWriter trait AllJsonWriters extends OptionWriters with EitherWriters { - implicit lazy val intWriter: JsonWriter[Int] = new JsonWriter[Int] { + implicit val intWriter: JsonWriter[Int] = new JsonWriter[Int] { override def write(value: Int, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val longWriter: JsonWriter[Long] = new JsonWriter[Long] { + implicit val longWriter: JsonWriter[Long] = new JsonWriter[Long] { override def write(value: Long, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val byteWriter: JsonWriter[Byte] = new JsonWriter[Byte] { + implicit val byteWriter: JsonWriter[Byte] = new JsonWriter[Byte] { override def write(value: Byte, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val shortWriter: JsonWriter[Short] = new JsonWriter[Short] { + implicit val shortWriter: JsonWriter[Short] = new JsonWriter[Short] { override def write(value: Short, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val doubleWriter: JsonWriter[Double] = new JsonWriter[Double] { + implicit val doubleWriter: JsonWriter[Double] = new JsonWriter[Double] { override def write(value: Double, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val floatWriter: JsonWriter[Float] = new JsonWriter[Float] { + implicit val floatWriter: JsonWriter[Float] = new JsonWriter[Float] { override def write(value: Float, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val bigDecimalWriter: JsonWriter[BigDecimal] = + implicit val bigDecimalWriter: JsonWriter[BigDecimal] = new JsonWriter[BigDecimal] { override def write(value: BigDecimal, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val bigIntWriter: JsonWriter[BigInt] = new JsonWriter[BigInt] { + implicit val bigIntWriter: JsonWriter[BigInt] = new JsonWriter[BigInt] { override def write(value: BigInt, tokenWriter: TokenWriter): Unit = tokenWriter.writeNumber(value) } - implicit lazy val booleanWriter: JsonWriter[Boolean] = + implicit val booleanWriter: JsonWriter[Boolean] = new JsonWriter[Boolean] { override def write(value: Boolean, tokenWriter: TokenWriter): Unit = tokenWriter.writeBoolean(value) } - implicit lazy val stringWriter: JsonWriter[String] = new JsonWriter[String] { + implicit val stringWriter: JsonWriter[String] = new JsonWriter[String] { override def write(value: String, tokenWriter: TokenWriter): Unit = tokenWriter.writeString(value) } - implicit lazy val charWriter: JsonWriter[Char] = new JsonWriter[Char] { + implicit val charWriter: JsonWriter[Char] = new JsonWriter[Char] { override def write(value: Char, tokenWriter: TokenWriter): Unit = tokenWriter.writeString(value.toString) } - implicit lazy val javaIntWriter: JsonWriter[java.lang.Integer] = + implicit val javaIntWriter: JsonWriter[java.lang.Integer] = new JsonWriter[java.lang.Integer] { override def write( value: java.lang.Integer, @@ -69,7 +69,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaLongWriter: JsonWriter[java.lang.Long] = + implicit val javaLongWriter: JsonWriter[java.lang.Long] = new JsonWriter[java.lang.Long] { override def write( value: java.lang.Long, @@ -77,7 +77,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaByteWriter: JsonWriter[java.lang.Byte] = + implicit val javaByteWriter: JsonWriter[java.lang.Byte] = new JsonWriter[java.lang.Byte] { override def write( value: java.lang.Byte, @@ -85,7 +85,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaShortWriter: JsonWriter[java.lang.Short] = + implicit val javaShortWriter: JsonWriter[java.lang.Short] = new JsonWriter[java.lang.Short] { override def write( value: java.lang.Short, @@ -93,7 +93,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaDoubleWriter: JsonWriter[java.lang.Double] = + implicit val javaDoubleWriter: JsonWriter[java.lang.Double] = new JsonWriter[java.lang.Double] { override def write( value: java.lang.Double, @@ -101,7 +101,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaFloatWriter: JsonWriter[java.lang.Float] = + implicit val javaFloatWriter: JsonWriter[java.lang.Float] = new JsonWriter[java.lang.Float] { override def write( value: java.lang.Float, @@ -109,7 +109,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBigDecimalWriter: JsonWriter[java.math.BigDecimal] = + implicit val javaBigDecimalWriter: JsonWriter[java.math.BigDecimal] = new JsonWriter[java.math.BigDecimal] { override def write( value: java.math.BigDecimal, @@ -117,7 +117,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBigIntegerWriter: JsonWriter[java.math.BigInteger] = + implicit val javaBigIntegerWriter: JsonWriter[java.math.BigInteger] = new JsonWriter[java.math.BigInteger] { override def write( value: java.math.BigInteger, @@ -125,7 +125,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeNumber(value) } - implicit lazy val javaBooleanWriter: JsonWriter[java.lang.Boolean] = + implicit val javaBooleanWriter: JsonWriter[java.lang.Boolean] = new JsonWriter[java.lang.Boolean] { override def write( value: java.lang.Boolean, @@ -133,7 +133,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeBoolean(value) } - implicit lazy val uuidWriter: JsonWriter[java.util.UUID] = + implicit val uuidWriter: JsonWriter[java.util.UUID] = new JsonWriter[java.util.UUID] { override def write( value: java.util.UUID, @@ -141,12 +141,12 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { ): Unit = tokenWriter.writeString(value.toString) } - implicit lazy val nullWriter: JsonWriter[Null] = new JsonWriter[Null] { + implicit val nullWriter: JsonWriter[Null] = new JsonWriter[Null] { override def write(value: Null, tokenWriter: TokenWriter): Unit = tokenWriter.writeNull() } - implicit lazy val instantWriter: JsonWriter[java.time.Instant] = + implicit val instantWriter: JsonWriter[java.time.Instant] = new JsonWriter[java.time.Instant] { override def write( value: java.time.Instant, @@ -155,7 +155,7 @@ trait AllJsonWriters extends OptionWriters with EitherWriters { tokenWriter.writeString(value.toString) } - implicit lazy val localDateWriter: JsonWriter[java.time.LocalDate] = + implicit val localDateWriter: JsonWriter[java.time.LocalDate] = new JsonWriter[java.time.LocalDate] { override def write( value: java.time.LocalDate, diff --git a/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala index 82f0491e..cfb81845 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/IterableWriters.scala @@ -8,22 +8,14 @@ import scala.language.higherKinds private[tethys] trait IterableWriters extends LowPriorityJsonWriters { final implicit def iterableWriter[A, C[X] <: Iterable[X]](implicit valueWriter: JsonWriter[A] - ): JsonWriter[C[A]] = new IterableWriter[A, C](valueWriter) { - override def iterator(c: C[A]): Iterator[A] = c.iterator - } - - abstract class IterableWriter[A, C[_]](valueWriter: JsonWriter[A]) - extends JsonWriter[C[A]] { - def iterator(c: C[A]): Iterator[A] - + ): JsonWriter[C[A]] = new JsonWriter[C[A]] { override def write(value: C[A], tokenWriter: TokenWriter): Unit = { tokenWriter.writeArrayStart() - val valueIterator = iterator(value) - while (valueIterator.hasNext) { - val v = valueIterator.next() - valueWriter.write(v, tokenWriter) - } + val valueIterator = value.iterator + + while (valueIterator.hasNext) + valueWriter.write(valueIterator.next(), tokenWriter) tokenWriter.writeArrayEnd() } diff --git a/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala b/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala index 3047316e..31911059 100644 --- a/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala +++ b/modules/core/src/main/scala/tethys/writers/instances/OptionWriters.scala @@ -4,7 +4,7 @@ import tethys.JsonWriter import tethys.writers.tokens.TokenWriter private[tethys] trait OptionWriters extends MapWriters { - implicit lazy val noneWriter: JsonWriter[None.type] = + implicit val noneWriter: JsonWriter[None.type] = new JsonWriter[None.type] { override def write( name: String, diff --git a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala index 354d12c5..bedf2eea 100644 --- a/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala +++ b/modules/core/src/test/scala/tethys/readers/DefaultReadersTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.readers.DefaultReadersTest.TestDefinition import tethys.readers.tokens._ -import tethys.TokenIteratorOps +import tethys._ import scala.reflect.ClassTag @@ -117,7 +117,8 @@ class DefaultReadersTest extends AnyFlatSpec { case (TestDefinition(result, jsonReader, name), nodes) => it should s"correctly read $name" in { val iterator = QueueIterator(nodes) - iterator.readJson(jsonReader).fold(throw _, identity) shouldBe result + implicit val reader = jsonReader + iterator.readJson.fold(throw _, identity) shouldBe result iterator.currentToken() shouldBe Token.Empty } diff --git a/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala b/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala index 587c0cd5..1866f9f1 100644 --- a/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala +++ b/modules/core/src/test/scala/tethys/readers/JsonReaderBuilderTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.readers.JsonReaderBuilderTest._ import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ class JsonReaderBuilderTest extends AnyFlatSpec with Matchers { behavior of "JsonReaderBuilder" diff --git a/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala b/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala index 1b7cc6f8..e9ab7802 100644 --- a/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala +++ b/modules/macro-derivation/src/test/scala-3/tethys/derivation/AutoReaderDerivationTest.scala @@ -7,7 +7,7 @@ import tethys.commons.{Token, TokenNode} import tethys.commons.TokenNode._ import tethys.derivation.auto._ import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ class AutoReaderDerivationTest extends AnyFlatSpec with Matchers { diff --git a/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala b/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala index c0518ea7..920bee59 100644 --- a/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala +++ b/modules/macro-derivation/src/test/scala-3/tethys/derivation/RedundantJsonReaderTest.scala @@ -9,7 +9,7 @@ import tethys.derivation.RedundantJsonReaderTest.* import tethys.derivation.semiauto.* import tethys.readers.tokens.QueueIterator -import tethys.TokenIteratorOps +import tethys._ object RedundantJsonReaderTest { case class RedundantClass(i: Int) From 2ce17b6db698923d7b267fd556e1e412778c45f9 Mon Sep 17 00:00:00 2001 From: Georgii Kovalev Date: Wed, 26 Mar 2025 15:45:02 +0530 Subject: [PATCH 2/4] add DefaultTokenWriter; main changes --- .sbtopts | 1 + build.sbt | 16 +- .../tethys/jackson/JacksonTokenWriter.scala | 3 + .../main/scala/tethys/jackson/package.scala | 66 +- .../scala/tethys/jackson/pretty/package.scala | 17 +- .../main/scala/tethys/commons/RawJson.scala | 20 +- .../core/src/main/scala/tethys/package.scala | 47 +- .../writers/tokens/ByteArrayAccess.java | 46 + .../writers/tokens/DefaultTokenWriter.scala | 1936 +++++++++++++++++ .../writers/tokens/SimpleTokenWriter.scala | 3 + .../tethys/writers/tokens/TokenWriter.scala | 80 +- .../writers/tokens/TokenWriterConfig.scala | 84 + .../writers/tokens/TokenWriterException.scala | 6 + .../writers/tokens/TokenWriterProducer.scala | 19 +- 14 files changed, 2278 insertions(+), 66 deletions(-) create mode 100644 .sbtopts create mode 100644 modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java create mode 100644 modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala create mode 100644 modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala create mode 100644 modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 00000000..29810bec --- /dev/null +++ b/.sbtopts @@ -0,0 +1 @@ +-Dsbt.io.implicit.relative.glob.conversion=allow \ No newline at end of file diff --git a/build.sbt b/build.sbt index 004c1922..e673038e 100644 --- a/build.sbt +++ b/build.sbt @@ -225,7 +225,7 @@ lazy val `jackson-212` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.12.7" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-213` = project .in(jackson / "jackson-213") @@ -239,7 +239,7 @@ lazy val `jackson-213` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.13.5" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-214` = project .in(jackson / "jackson-214") @@ -253,7 +253,7 @@ lazy val `jackson-214` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.14.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-215` = project .in(jackson / "jackson-215") @@ -267,7 +267,7 @@ lazy val `jackson-215` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.15.4" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-216` = project .in(jackson / "jackson-216") @@ -281,7 +281,7 @@ lazy val `jackson-216` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.16.2" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-217` = project .in(jackson / "jackson-217") @@ -295,7 +295,7 @@ lazy val `jackson-217` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.17.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val `jackson-218` = project .in(jackson / "jackson-218") @@ -309,7 +309,7 @@ lazy val `jackson-218` = project "com.fasterxml.jackson.core" % "jackson-core" % "2.18.3" ) ) - .dependsOn(core) + .dependsOn(core % "compile->compile;test->test") lazy val benchmarks = project .in(modules / "benchmarks") @@ -327,6 +327,8 @@ lazy val benchmarks = project "io.circe" %% "circe-jackson210" % "0.14.0", "dev.zio" %% "zio-json" % "0.7.1", "com.typesafe.play" %% "play-json" % "2.10.5", + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.33.1", + "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.33.1", "org.knowm.xchart" % "xchart" % "3.8.2" exclude ("de.erichseifert.vectorgraphics2d", "VectorGraphics2D") withSources () ), scalacOptions ++= { diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala index 53497f7f..681a966a 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/JacksonTokenWriter.scala @@ -92,4 +92,7 @@ class JacksonTokenWriter(jsonGenerator: JsonGenerator) extends TokenWriter { override def close(): Unit = jsonGenerator.close() override def flush(): Unit = jsonGenerator.flush() + + override def result(): String = + jsonGenerator.getOutputTarget.toString } diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala index 78720394..c1fb2c33 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/package.scala @@ -1,26 +1,66 @@ package tethys -import java.io.{Reader, Writer} - -import com.fasterxml.jackson.core.JsonFactory +import java.io.Reader +import com.fasterxml.jackson.core.{ + JsonFactory, + JsonFactoryBuilder, + JsonGenerator +} import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.{TokenIterator, TokenIteratorProducer} -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.writers.tokens.{ + TokenWriter, + TokenWriterConfig, + TokenWriterProducer +} package object jackson { - lazy val defaultJsonFactory: JsonFactory = { - val f = new JsonFactory() - f.configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false) - f + lazy val defaultJsonFactory: JsonFactory = + new JsonFactoryBuilder() + .configure(JsonFactory.Feature.INTERN_FIELD_NAMES, false) + .build() + + class JacksonTokenWriterProducer( + jsonFactory: JsonFactory, + // used for compatibility where tethys.jackson.pretty import is required + modifyConfig: TokenWriterConfig => TokenWriterConfig + ) extends TokenWriterProducer { + + private def updateGeneratorFromConfig( + generator: JsonGenerator, + config: TokenWriterConfig + ): JsonGenerator = { + if (config == TokenWriterConfig.default) + generator + else { + val first = + if (config.indentionStep == 2) + generator.useDefaultPrettyPrinter() + else + generator + + val second = + if (config.escapeUnicode) + first.setHighestNonEscapedChar(127) + else + first + second + } + } + + override def produce(config: TokenWriterConfig): TokenWriter = { + val generator = updateGeneratorFromConfig( + jsonFactory.createGenerator(new java.io.StringWriter()), + modifyConfig(config) + ) + new JacksonTokenWriter(generator) + } } implicit def jacksonTokenWriterProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory - ): TokenWriterProducer = new TokenWriterProducer { - override def forWriter(writer: Writer): TokenWriter = { - new JacksonTokenWriter(jsonFactory.createGenerator(writer)) - } - } + ): JacksonTokenWriterProducer = + new JacksonTokenWriterProducer(jsonFactory, identity) implicit def jacksonTokenIteratorProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory diff --git a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala index 6bb86bd3..7ce322ae 100644 --- a/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala +++ b/modules/backend/jackson/jackson-backend/src/main/scala/tethys/jackson/pretty/package.scala @@ -1,21 +1,18 @@ package tethys.jackson -import java.io.Writer - import com.fasterxml.jackson.core.JsonFactory import tethys.readers.tokens.TokenIteratorProducer -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} package object pretty { + + @deprecated("Provide implicit TokenWriterConfig to `asJson` instead") implicit def prettyJacksonTokenWriterProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory - ): TokenWriterProducer = new TokenWriterProducer { - override def forWriter(writer: Writer): TokenWriter = { - new JacksonTokenWriter( - jsonFactory.createGenerator(writer).useDefaultPrettyPrinter() - ) - } - } + ): JacksonTokenWriterProducer = + new tethys.jackson.JacksonTokenWriterProducer( + jsonFactory, + modifyConfig = _.withDefaultPrettyPrinter + ) implicit def jacksonTokenIteratorProducer(implicit jsonFactory: JsonFactory = defaultJsonFactory diff --git a/modules/core/src/main/scala/tethys/commons/RawJson.scala b/modules/core/src/main/scala/tethys/commons/RawJson.scala index 26b40093..61b45cf2 100644 --- a/modules/core/src/main/scala/tethys/commons/RawJson.scala +++ b/modules/core/src/main/scala/tethys/commons/RawJson.scala @@ -1,10 +1,12 @@ package tethys.commons -import java.io.StringWriter - import tethys.readers.FieldName import tethys.readers.tokens.TokenIterator -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.writers.tokens.{ + TokenWriter, + TokenWriterConfig, + TokenWriterProducer +} import tethys.{JsonReader, JsonStreaming, JsonWriter} final case class RawJson(json: String) @@ -16,16 +18,16 @@ object RawJson { } implicit def rawJsonReader(implicit - tokenWriterProducer: TokenWriterProducer + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig ): JsonReader[RawJson] = new JsonReader[RawJson] { override def read( it: TokenIterator )(implicit fieldName: FieldName): RawJson = { - val stringWriter = new StringWriter() - val tokenWriter: TokenWriter = tokenWriterProducer.forWriter(stringWriter) - JsonStreaming.streamValue(it, tokenWriter) - tokenWriter.flush() - RawJson(stringWriter.toString) + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try JsonStreaming.streamValue(it, tokenWriter) + finally tokenWriter.flush() + RawJson(tokenWriter.result()) } } } diff --git a/modules/core/src/main/scala/tethys/package.scala b/modules/core/src/main/scala/tethys/package.scala index ca0f45e1..94f9af37 100644 --- a/modules/core/src/main/scala/tethys/package.scala +++ b/modules/core/src/main/scala/tethys/package.scala @@ -1,9 +1,12 @@ -import java.io.{Reader, StringReader, StringWriter, Writer} - -import tethys.readers.{FieldName, ReaderError} import tethys.readers.tokens.{TokenIterator, TokenIteratorProducer} -import tethys.writers.tokens.{TokenWriter, TokenWriterProducer} +import tethys.readers.{FieldName, ReaderError} +import tethys.writers.tokens.{ + TokenWriter, + TokenWriterConfig, + TokenWriterProducer +} +import java.io.{Reader, StringReader} import scala.Specializable.Group package object tethys { @@ -12,40 +15,42 @@ package object tethys { (Byte, Short, Int, Long, Float, Double, Boolean) ) - // given - implicit class JsonWriterOps[A](val a: A) extends AnyVal { def asJson(implicit jsonWriter: JsonWriter[A], - tokenWriterProducer: TokenWriterProducer + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig ): String = { - val stringWriter = new StringWriter() - writeJson(tokenWriterProducer.forWriter(stringWriter)) - stringWriter.toString + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try jsonWriter.write(a, tokenWriter) + finally tokenWriter.flush() + tokenWriter.result() } def asJsonWith( jsonWriter: JsonWriter[A] - )(implicit tokenWriterProducer: TokenWriterProducer): String = { - asJson(jsonWriter, tokenWriterProducer) + )(implicit + tokenWriterProducer: TokenWriterProducer, + tokenWriterConfig: TokenWriterConfig + ): String = { + val tokenWriter = tokenWriterProducer.produce(tokenWriterConfig) + try + jsonWriter.write(a, tokenWriter) + finally + tokenWriter.flush() + tokenWriter.result() } def writeJson( tokenWriter: TokenWriter )(implicit jsonWriter: JsonWriter[A]): Unit = { - try jsonWriter.write(a, tokenWriter) - finally { + try + jsonWriter.write(a, tokenWriter) + finally tokenWriter.flush() - } } } - implicit class WriterOps(val w: Writer) extends AnyVal { - def toTokenWriter(implicit - tokenWriterProducer: TokenWriterProducer - ): TokenWriter = tokenWriterProducer.forWriter(w) - } - implicit class StringReaderOps(val json: String) extends AnyVal { def jsonAs[A](implicit jsonReader: JsonReader[A], diff --git a/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java b/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java new file mode 100644 index 00000000..ced6b066 --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/ByteArrayAccess.java @@ -0,0 +1,46 @@ +package tethys.writers.tokens; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +class ByteArrayAccess { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332 + private static final VarHandle VH_LONG = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_INT = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_SHORT = + MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN); + private static final VarHandle VH_LONG_REVERSED = + MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + private static final VarHandle VH_INT_REVERSED = + MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + static void setLong(byte[] buf, int pos, long value) { + VH_LONG.set(buf, pos, value); + } + + static long getLong(byte[] buf, int pos) { + return (long) VH_LONG.get(buf, pos); + } + + static void setInt(byte[] buf, int pos, int value) { + VH_INT.set(buf, pos, value); + } + + static int getInt(byte[] buf, int pos) { + return (int) VH_INT.get(buf, pos); + } + + static void setShort(byte[] buf, int pos, short value) { + VH_SHORT.set(buf, pos, value); + } + + static void setLongReversed(byte[] buf, int pos, long value) { + VH_LONG_REVERSED.set(buf, pos, value); + } + + static int getIntReversed(byte[] buf, int pos) { + return (int) VH_INT_REVERSED.get(buf, pos); + } +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala new file mode 100644 index 00000000..db5fc40e --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/DefaultTokenWriter.scala @@ -0,0 +1,1936 @@ +/* + This started as a copy of jsoniter-scala JsonWriter + https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/jvm/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala + MIT License + + Copyright (c) 2017 Andriy Plokhotnyuk, and respective contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +package tethys.writers.tokens + +import java.io.OutputStream +import java.math.BigInteger +import java.nio.ByteBuffer +import java.nio.charset.{Charset, StandardCharsets} +import java.time._ +import scala.annotation.tailrec + +/** A writer for iterative serialization of JSON keys and values. + * + * @param buf + * an internal buffer for writing JSON data + * @param count + * the current position in the internal buffer + * @param limit + * the last position in the internal buffer + * @param indention + * the current indention level + * @param comma + * a flag indicating if the next element should be preceded by comma + * @param disableBufGrowing + * a flag indicating if growing of the internal buffer is disabled + * @param bbuf + * a byte buffer for writing JSON data + * @param out + * the output stream for writing JSON data + * @param config + * a writer configuration + */ +final class DefaultTokenWriter( + private[this] var buf: Array[Byte] = new Array[Byte](32768), + private[this] var count: Int = 0, + private[this] var limit: Int = 32768, + private[this] var indention: Int = 0, + private[this] var comma: Boolean = false, + private[this] var disableBufGrowing: Boolean = false, + private[this] var bbuf: ByteBuffer = null, + private[this] var out: OutputStream = null, + private[this] var config: TokenWriterConfig = null +) extends TokenWriter { + import DefaultTokenWriter._ + + def withConfig(newConfig: TokenWriterConfig): DefaultTokenWriter = { + config = newConfig + if (newConfig.preferredBufSize == buf.size) () + else if (newConfig.preferredBufSize > buf.size) + growBuf(newConfig.preferredBufSize) + else reallocateBufToPreferredSize() + this + } + + override def flush(): Unit = () + + override def close(): Unit = () + + override def writeFieldName(x: String): DefaultTokenWriter.this.type = { + val indention = this.indention + var pos = ensureBufCapacity(indention + 10) + val buf = this.buf + if (comma) { + comma = false + buf(pos) = ',' + pos += 1 + if (indention != 0) pos = writeIndention(buf, pos, indention) + } + buf(pos) = '"' + pos += 1 + pos = writeString(x, 0, pos, buf, Math.min(x.length, limit - pos - 1) + pos) + if (pos + 4 >= limit) pos = flushAndGrowBuf(4, pos) + ByteArrayAccess.setInt(this.buf, pos, 0x203a22) + if (indention > 0) pos += 1 + count = pos + 2 + this + } + + /** Throws a [[TokenWriterException]] with the given error message. */ + private def encodeError(msg: String): Nothing = + throw new TokenWriterException(msg, null) + + override def writeNumber(x: BigDecimal): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeBigDecimal(x.bigDecimal) + this + } + + override def writeNumber(x: BigInt): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + if (x.isValidLong) writeLong(x.longValue) + else writeBigInteger(x.bigInteger, null) + this + } + + override def writeString(x: String): DefaultTokenWriter.this.type = { + val indention = this.indention + var pos = ensureBufCapacity(indention + 10) + val buf = this.buf + if (comma) { + buf(pos) = ',' + pos += 1 + if (indention != 0) pos = writeIndention(buf, pos, indention) + } else comma = true + buf(pos) = '"' + pos += 1 + pos = writeString(x, 0, pos, buf, Math.min(x.length, limit - pos - 1) + pos) + this.buf(pos) = '"' + count = pos + 1 + this + } + + override def writeBoolean(x: Boolean): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeBooleanInternal(x) + this + } + + override def writeNumber(x: Byte): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeByte(x) + this + } + + override def writeNumber(x: Short): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeShort(x) + this + } + + override def writeNumber(x: Int): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeInt(x) + this + } + + override def writeNumber(x: Long): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeLong(x) + this + } + + override def writeNumber(x: Float): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeFloat(x) + this + } + + override def writeNumber(x: Double): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeDouble(x) + this + } + + override def writeRawJson(json: String): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + writeRawBytes(json.getBytes(StandardCharsets.UTF_8)) + this + } + + override def writeNull(): DefaultTokenWriter.this.type = { + writeOptionalCommaAndIndentionBeforeValue() + val pos = ensureBufCapacity(4) + ByteArrayAccess.setInt(buf, pos, 0x6c6c756e) + count = pos + 4 + this + } + + /** Writes a JSON array start marker (`[`). + */ + override def writeArrayStart(): DefaultTokenWriter.this.type = { + writeNestedStart('[') + this + } + + /** Writes a JSON array end marker (`]`). + */ + override def writeArrayEnd(): DefaultTokenWriter.this.type = { + writeNestedEnd(']') + this + } + + /** Writes a JSON array start marker (`{`). + */ + override def writeObjectStart(): DefaultTokenWriter.this.type = { + writeNestedStart('{') + this + } + + override def writeObjectEnd(): DefaultTokenWriter.this.type = { + writeNestedEnd('}') + this + } + + def result(): String = + try { + val res = new String(buf, 0, count, StandardCharsets.UTF_8) + comma = false + count = 0 + indention = 0 + disableBufGrowing = false + res + } finally { + if (limit > config.preferredBufSize) reallocateBufToPreferredSize() + } + + private[this] def writeNestedStart(b: Byte): Unit = { + writeOptionalCommaAndIndentionBeforeKey() + writeBytes(b) + val indentionStep = config.indentionStep + if (indentionStep != 0) { + indention += indentionStep + writeIndention() + } + } + + private[this] def writeNestedEnd(b: Byte): Unit = { + comma = true + if (indention != 0) { + indention -= config.indentionStep + writeIndention() + } + writeBytes(b) + } + + private[this] def writeOptionalCommaAndIndentionBeforeValue(): Unit = + if (comma) { + writeBytes(',') + if (indention != 0) writeIndention() + } else comma = true + + private[this] def writeOptionalCommaAndIndentionBeforeKey(): Unit = + if (comma) { + comma = false + writeBytes(',') + if (indention != 0) writeIndention() + } + + private[this] def writeIndention(): Unit = { + val n = indention + val pos = ensureBufCapacity(n + 8) + count = writeIndention(buf, pos, n) + } + + private[this] def writeIndention(buf: Array[Byte], p: Int, n: Int): Int = { + var pos = p + buf(pos) = '\n' + pos += 1 + val posLim = pos + n + while (pos < posLim) { + ByteArrayAccess.setLong(buf, pos, 0x2020202020202020L) + pos += 8 + } + posLim + } + + private[this] def writeParenthesesWithColon(): Unit = { + var pos = ensureBufCapacity(4) // 4 == size of Int in bytes + ByteArrayAccess.setInt(buf, pos, 0x203a22) + if (indention > 0) pos += 1 + count = pos + 2 + } + + private[this] def writeColon(): Unit = { + var pos = ensureBufCapacity(2) + ByteArrayAccess.setShort(buf, pos, 0x203a) + if (indention > 0) pos += 1 + count = pos + 1 + } + + private[this] def writeBytes(b: Byte): Unit = { + var pos = count + if (pos >= limit) pos = flushAndGrowBuf(1, pos) + buf(pos) = b + count = pos + 1 + } + + private[this] def writeRawBytes(bs: Array[Byte]): Unit = { + var pos = count + var step = Math.max(config.preferredBufSize, limit - pos) + var remaining = bs.length + var offset = 0 + while (remaining > 0) { + step = Math.min(step, remaining) + if (pos + step > limit) pos = flushAndGrowBuf(step, pos) + System.arraycopy(bs, offset, buf, pos, step) + offset += step + pos += step + remaining -= step + } + count = pos + } + + @tailrec + private[this] def writeString( + s: String, + from: Int, + pos: Int, + buf: Array[Byte], + minLim: Int + ): Int = + if (pos < minLim) { + val ch = s.charAt(from).toInt + buf(pos) = ch.toByte + if (ch >= 0x20 && ch < 0x7f && ch != 0x22 && ch != 0x5c) + writeString(s, from + 1, pos + 1, buf, minLim) + else writeEscapedOrEncodedString(s, from, pos) + } else if (s.length == from) pos + else { + val newPos = flushAndGrowBuf(2, pos) + writeString( + s, + from, + newPos, + this.buf, + Math.min(s.length - from, limit - newPos - 1) + newPos + ) + } + + private[this] def writeEscapedOrEncodedString( + s: String, + from: Int, + pos: Int + ): Int = + if (config.escapeUnicode) + writeEscapedString( + s, + from, + s.length, + pos, + limit - 13, + escapedChars, + lowerCaseHexDigits + ) + else writeEncodedString(s, from, s.length, pos, limit - 7, escapedChars) + + @tailrec + private[this] def writeEncodedString( + s: String, + from: Int, + to: Int, + pos: Int, + posLim: Int, + escapedChars: Array[Byte] + ): Int = + if (from >= to) pos + else if (pos >= posLim) + writeEncodedString( + s, + from, + to, + flushAndGrowBuf(7, pos), + limit - 6, + escapedChars + ) + else { + val ch1 = s.charAt(from).toInt + if (ch1 < 0x80) { + val esc = escapedChars(ch1) + if (esc == 0) { // 000000000aaaaaaa (UTF-16 char) -> 0aaaaaaa (UTF-8 byte) + buf(pos) = ch1.toByte + writeEncodedString(s, from + 1, to, pos + 1, posLim, escapedChars) + } else if (esc > 0) { + ByteArrayAccess.setShort(buf, pos, (esc << 8 | 0x5c).toShort) + writeEncodedString(s, from + 1, to, pos + 2, posLim, escapedChars) + } else + writeEncodedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1.toByte, pos, buf, lowerCaseHexDigits), + posLim, + escapedChars + ) + } else if (ch1 < 0x800) { // 00000bbbbbaaaaaa (UTF-16 char) -> 110bbbbb 10aaaaaa (UTF-8 bytes) + ByteArrayAccess.setShort( + buf, + pos, + (ch1 >> 6 | (ch1 << 8 & 0x3f00) | 0x80c0).toShort + ) + writeEncodedString(s, from + 1, to, pos + 2, posLim, escapedChars) + } else if ((ch1 & 0xf800) != 0xd800) { // ccccbbbbbbaaaaaa (UTF-16 char) -> 1110cccc 10bbbbbb 10aaaaaa (UTF-8 bytes) + ByteArrayAccess.setInt( + buf, + pos, + ch1 >> 12 | (ch1 << 2 & 0x3f00) | (ch1 << 16 & 0x3f0000) | 0x8080e0 + ) + writeEncodedString(s, from + 1, to, pos + 3, posLim, escapedChars) + } else { // 110110uuuuccccbb 110111bbbbaaaaaa (UTF-16 chars) -> 11110ddd 10ddcccc 10bbbbbb 10aaaaaa (UTF-8 bytes), where ddddd = uuuu + 1 + var ch2 = 0 + if ( + ch1 >= 0xdc00 || from + 1 >= to || { + ch2 = s.charAt(from + 1).toInt + (ch2 & 0xfc00) != 0xdc00 + } + ) illegalSurrogateError() + val cp = + (ch1 << 10) + (ch2 - 56613888) // -56613888 == 0x10000 - (0xD800 << 10) - 0xDC00 + ByteArrayAccess.setInt( + buf, + pos, + cp >> 18 | (cp >> 4 & 0x3f00) | (cp << 10 & 0x3f0000) | (cp << 24 & 0x3f000000) | 0x808080f0 + ) + writeEncodedString(s, from + 2, to, pos + 4, posLim, escapedChars) + } + } + + @tailrec + private[this] def writeEscapedString( + s: String, + from: Int, + to: Int, + pos: Int, + posLim: Int, + escapedChars: Array[Byte], + ds: Array[Short] + ): Int = + if (from >= to) pos + else if (pos >= posLim) + writeEscapedString( + s, + from, + to, + flushAndGrowBuf(13, pos), + limit - 12, + escapedChars, + ds + ) + else { + val ch1 = s.charAt(from).toInt + if (ch1 < 0x80) { + val esc = escapedChars(ch1) + if (esc == 0) { + buf(pos) = ch1.toByte + writeEscapedString(s, from + 1, to, pos + 1, posLim, escapedChars, ds) + } else if (esc > 0) { + ByteArrayAccess.setShort(buf, pos, (esc << 8 | 0x5c).toShort) + writeEscapedString(s, from + 1, to, pos + 2, posLim, escapedChars, ds) + } else + writeEscapedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1.toByte, pos, buf, ds), + posLim, + escapedChars, + ds + ) + } else if ((ch1 & 0xf800) != 0xd800) { + writeEscapedString( + s, + from + 1, + to, + writeEscapedUnicode(ch1, pos, buf, ds), + posLim, + escapedChars, + ds + ) + } else { + var ch2 = 0 + if ( + ch1 >= 0xdc00 || from + 1 >= to || { + ch2 = s.charAt(from + 1).toInt + (ch2 & 0xfc00) != 0xdc00 + } + ) illegalSurrogateError() + writeEscapedString( + s, + from + 2, + to, + writeEscapedUnicode( + ch2, + writeEscapedUnicode(ch1, pos, buf, ds), + buf, + ds + ), + posLim, + escapedChars, + ds + ) + } + } + + private[this] def writeEscapedUnicode( + ch: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + ByteArrayAccess.setShort(buf, pos, 0x755c) + val d1 = ds(ch >> 8) + val d2 = ds(ch & 0xff) << 16 + ByteArrayAccess.setInt(buf, pos + 2, d1 | d2) + pos + 6 + } + + private[this] def writeEscapedUnicode( + b: Byte, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + ByteArrayAccess.setInt(buf, pos, 0x3030755c) + ByteArrayAccess.setShort(buf, pos + 4, ds(b & 0xff)) + pos + 6 + } + + private[this] def illegalSurrogateError(): Nothing = encodeError( + "illegal char sequence of surrogate pair" + ) + + private[this] def writeBigInteger( + x: BigInteger, + ss: Array[BigInteger] + ): Unit = { + val bitLen = x.bitLength + if (bitLen < 64) writeLong(x.longValue) + else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + writeBigInteger(qr(0), ss1) + writeBigIntegerRemainder(qr(1), n - 1, ss1) + } + } + + private[this] def writeBigIntegerRemainder( + x: BigInteger, + n: Int, + ss: Array[BigInteger] + ): Unit = + if (n < 0) + count = + write18Digits(Math.abs(x.longValue), ensureBufCapacity(18), buf, digits) + else { + val qr = x.divideAndRemainder(ss(n)) + writeBigIntegerRemainder(qr(0), n - 1, ss) + writeBigIntegerRemainder(qr(1), n - 1, ss) + } + + private[this] def writeBigDecimal(x: java.math.BigDecimal): Unit = { + var exp = writeBigDecimal(x.unscaledValue, x.scale, 0, null) + if (exp != 0) { + var pos = ensureBufCapacity(12) + val buf = this.buf + val ds = digits + var m: Short = 0x2b45 + if (exp < 0) { + m = 0x2d45 + exp = -exp + } + ByteArrayAccess.setShort(buf, pos, m) + pos += 2 + var q = exp + if (exp < 100000000L) { + pos += digitCount(exp) + count = pos + } else { + q = Math.multiplyHigh( + exp, + 6189700196426901375L + ) >>> 25 // divide a positive long by 100000000 + pos += digitCount(q) + count = write8Digits(exp - q * 100000000L, pos, buf, ds) + } + writePositiveIntDigits(q.toInt, pos, buf, ds) + } + } + + private[this] def writeBigDecimal( + x: BigInteger, + scale: Int, + blockScale: Int, + ss: Array[BigInteger] + ): Long = { + val bitLen = x.bitLength + if (bitLen < 64) { + val v = x.longValue + val pos = ensureBufCapacity( + 28 + ) // Long.MinValue.toString.length + 8 (for a leading zero, dot, and padding zeroes) + val buf = this.buf + var lastPos = writeLong(v, pos, buf) + val digits = (v >> 63).toInt + lastPos - pos + val dotOff = scale.toLong - blockScale + val exp = (digits - 1) - dotOff + if (scale >= 0 && exp >= -6) { + if (exp < 0) + lastPos = insertDotWithZeroes(digits, -1 - exp.toInt, lastPos, buf) + else if (dotOff > 0) + lastPos = insertDot(lastPos - dotOff.toInt, lastPos, buf) + count = lastPos + 0 + } else { + if (digits > 1 || blockScale > 0) + lastPos = insertDot(lastPos - digits + 1, lastPos, buf) + count = lastPos + exp + } + } else { + val n = calculateTenPow18SquareNumber(bitLen) + val ss1 = + if (ss eq null) getTenPow18Squares(n) + else ss + val qr = x.divideAndRemainder(ss1(n)) + val exp = writeBigDecimal(qr(0), scale, (18 << n) + blockScale, ss1) + writeBigDecimalRemainder(qr(1), scale, blockScale, n - 1, ss1) + exp + } + } + + private[this] def writeBigDecimalRemainder( + x: BigInteger, + scale: Int, + blockScale: Int, + n: Int, + ss: Array[BigInteger] + ): Unit = + if (n < 0) { + val pos = ensureBufCapacity(19) // 18 digits and a place for optional dot + val buf = this.buf + var lastPos = write18Digits(Math.abs(x.longValue), pos, buf, digits) + val dotOff = scale - blockScale + if (dotOff > 0 && dotOff <= 18) + lastPos = insertDot(lastPos - dotOff, lastPos, buf) + count = lastPos + } else { + val qr = x.divideAndRemainder(ss(n)) + writeBigDecimalRemainder(qr(0), scale, (18 << n) + blockScale, n - 1, ss) + writeBigDecimalRemainder(qr(1), scale, blockScale, n - 1, ss) + } + + private[this] def calculateTenPow18SquareNumber(bitLen: Int): Int = { + val m = Math.max( + (bitLen * 71828554L >> 32).toInt - 1, + 1 + ) // Math.max((x.bitLength * Math.log(2) / Math.log(1e18)).toInt - 1, 1) + 31 - java.lang.Integer.numberOfLeadingZeros(m) + } + + private[this] def insertDotWithZeroes( + digits: Int, + pad: Int, + lastPos: Int, + buf: Array[Byte] + ): Int = { + var pos = lastPos + pad + 1 + val numPos = pos - digits + val off = pad + 2 + while (pos > numPos) { + buf(pos) = buf(pos - off) + pos -= 1 + } + val dotPos = pos - pad + while (pos > dotPos) { + buf(pos) = '0' + pos -= 1 + } + ByteArrayAccess.setShort(buf, dotPos - 1, 0x2e30) + lastPos + off + } + + private[this] def insertDot( + dotPos: Int, + lastPos: Int, + buf: Array[Byte] + ): Int = { + var pos = lastPos + while (pos > dotPos) { + buf(pos) = buf(pos - 1) + pos -= 1 + } + buf(dotPos) = '.' + lastPos + 1 + } + + private[this] def writeBooleanInternal(x: Boolean): Unit = { + var pos = ensureBufCapacity(8) // bytes in Long + if (x) { + ByteArrayAccess.setInt(buf, pos, 0x65757274) + pos += 4 + } else { + ByteArrayAccess.setLong(buf, pos, 0x65736c6166L) + pos += 5 + } + count = pos + } + + private[this] def writeByte(x: Byte): Unit = { + var pos = ensureBufCapacity( + 5 + ) // size of Int in bytes + one byte for the sign + val buf = this.buf + val ds = digits + var q0 = x.toInt + if (q0 < 0) { + q0 = -q0 + buf(pos) = '-' + pos += 1 + } + if (q0 < 10) { + buf(pos) = (q0 | '0').toByte + pos += 1 + } else if (q0 < 100) { + ByteArrayAccess.setShort(buf, pos, ds(q0)) + pos += 2 + } else { + ByteArrayAccess.setInt(buf, pos, ds(q0 - 100) << 8 | '1') + pos += 3 + } + count = pos + } + + private[this] def write3Digits( + x: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val q1 = x * 1311 >> 17 // divide a small positive int by 100 + ByteArrayAccess.setInt(buf, pos, ds(x - q1 * 100) << 8 | q1 | '0') + pos + 3 + } + + private[this] def write4Digits( + x: Int, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val q1 = x * 5243 >> 19 // divide a small positive int by 100 + val d1 = ds(x - q1 * 100) << 16 + val d2 = ds(q1) + ByteArrayAccess.setInt(buf, pos, d1 | d2) + pos + 4 + } + + private[this] def write8Digits( + x: Long, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val y1 = + x * 140737489 // Based on James Anhalt's algorithm for 8 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ + val m1 = 0x7fffffffffffL + val m2 = 100L + val y2 = (y1 & m1) * m2 + val y3 = (y2 & m1) * m2 + val y4 = (y3 & m1) * m2 + val d1 = ds((y1 >> 47).toInt) + val d2 = ds((y2 >> 47).toInt) << 16 + val d3 = ds((y3 >> 47).toInt).toLong << 32 + val d4 = ds((y4 >> 47).toInt).toLong << 48 + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3 | d4) + pos + 8 + } + + private[this] def write18Digits( + x: Long, + pos: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + val m1 = 6189700196426901375L + val q1 = + Math.multiplyHigh(x, m1) >>> 25 // divide a positive long by 100000000 + val q2 = + Math.multiplyHigh(q1, m1) >>> 25 // divide a positive long by 100000000 + ByteArrayAccess.setShort(buf, pos, ds(q2.toInt)) + write8Digits( + x - q1 * 100000000L, + write8Digits(q1 - q2 * 100000000L, pos + 2, buf, ds), + buf, + ds + ) + } + + private[this] def writeShort(x: Short): Unit = { + var pos = ensureBufCapacity(9) // 8 bytes in long + a byte for the sign + val buf = this.buf + val ds = digits + var q0 = x.toInt + if (q0 < 0) { + q0 = -q0 + buf(pos) = '-' + pos += 1 + } + if (q0 < 100) { + if (q0 < 10) { + buf(pos) = (q0 | '0').toByte + pos += 1 + } else { + ByteArrayAccess.setShort(buf, pos, ds(q0)) + pos += 2 + } + } else if (q0 < 10000) { + val q1 = q0 * 5243 >> 19 // divide a small positive int by 100 + val d2 = ds(q0 - q1 * 100) + if (q0 < 1000) { + ByteArrayAccess.setInt(buf, pos, q1 | '0' | d2 << 8) + pos += 3 + } else { + ByteArrayAccess.setInt(buf, pos, ds(q1) | d2 << 16) + pos += 4 + } + } else { + val y1 = + q0 * 429497L // Based on James Anhalt's algorithm for 5 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/ + val y2 = (y1 & 0xffffffffL) * 100 + val y3 = (y2 & 0xffffffffL) * 100 + val d1 = (y1 >> 32).toInt | '0' + val d2 = ds((y2 >> 32).toInt) << 8 + val d3 = ds((y3 >> 32).toInt).toLong << 24 + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos += 5 + } + count = pos + } + + private[this] def writeInt(x: Int): Unit = { + var pos = ensureBufCapacity(11) // Int.MinValue.toString.length + val buf = this.buf + val ds = digits + var q0 = x + if (x < 0) { + q0 = -q0 + ByteArrayAccess.setShort(buf, pos, 0x322d) + pos += 1 + if (q0 == x) { + q0 = 147483648 + pos += 1 + } + } + pos += digitCount(q0.toLong) + writePositiveIntDigits(q0, pos, buf, ds) + count = pos + } + + private[this] def writeLong(x: Long): Unit = + count = + writeLong(x, ensureBufCapacity(20), buf) // Long.MinValue.toString.length + + private[this] def writeLong(x: Long, p: Int, buf: Array[Byte]): Int = { + var pos = p + val ds = digits + var q0 = x + if (x < 0) { + q0 = -q0 + ByteArrayAccess.setInt(buf, pos, 0x3232392d) + pos += 1 + if (q0 == x) { + q0 = 3372036854775808L + pos += 3 + } + } + val m1 = 100000000L + var q2 = q0 + var lastPos = pos + if (q0 < m1) { + lastPos += digitCount(q0) + pos = lastPos + } else { + val m2 = 6189700196426901375L + val q1 = + Math.multiplyHigh(q0, m2) >>> 25 // divide a positive long by 100000000 + if (q1 < m1) { + q2 = q1 + lastPos += digitCount(q1) + pos = lastPos + } else { + q2 = Math.multiplyHigh( + q1, + m2 + ) >>> 25 // divide a small positive long by 100000000 + lastPos += digitCount(q2) + pos = write8Digits(q1 - q2 * m1, lastPos, buf, ds) + } + pos = write8Digits(q0 - q1 * m1, pos, buf, ds) + } + writePositiveIntDigits(q2.toInt, lastPos, buf, ds) + pos + } + + // Based on the amazing work of Raffaello Giulietti + // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/FloatToDecimal.java + private[this] def writeFloat(x: Float): Unit = { + var pos = ensureBufCapacity(15) + val buf = this.buf + if (x < 0.0f) { + buf(pos) = '-' + pos += 1 + } + if (x == 0.0f) { + ByteArrayAccess.setInt(buf, pos, 0x302e30) + pos += 3 + } else { + val bits = java.lang.Float.floatToRawIntBits(x) + var e2 = (bits >> 23 & 0xff) - 150 + var m2 = bits & 0x7fffff | 0x800000 + var m10, e10 = 0 + if (e2 == 0) m10 = m2 + else if ((e2 >= -23 && e2 < 0) && m2 << e2 == 0) m10 = m2 >> -e2 + else { + var e10Corr, e2Corr = 0 + var cblCorr = 2 + if (e2 == -150) { + m2 &= 0x7fffff + e2 = -149 + if (m2 < 8) { + m2 *= 10 + e10Corr = 1 + } + } else if (e2 == 105) illegalNumberError(x) + else if (m2 == 0x800000 && e2 > -149) { + e2Corr = 131007 + cblCorr = 1 + } + e10 = e2 * 315653 - e2Corr >> 20 + val g = gs(e10 + 324 << 1) + 1 + val h = (e10 * -108853 >> 15) + e2 + 1 + val cb = m2 << 2 + val vbCorr = (m2 & 0x1) - 1 + val vb = rop(g, cb << h) + val vbl = rop(g, cb - cblCorr << h) + vbCorr + val vbr = rop(g, cb + 2 << h) - vbCorr + var diff = 0 + if ( + vb < 400 || { + m10 = (vb * 107374183L >> 32).toInt // divide a positive int by 40 + val vb40 = m10 * 40 + diff = vbl - vb40 + (vb40 - vbr + 40 ^ diff) >= 0 + } + ) { + m10 = vb >> 2 + val vb4 = m10 << 2 + diff = vbl - vb4 + if ((vb4 - vbr + 4 ^ diff) >= 0) diff = (vb & 0x3) + (m10 & 0x1) - 3 + } else e10Corr = -1 + m10 += ~diff >>> 31 + e10 -= e10Corr + } + val len = digitCount(m10.toLong) + e10 += len - 1 + val ds = digits + if (e10 < -3 || e10 >= 7) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, (buf(pos + 1) | 0x2e00).toShort) + if (lastPos - 3 < pos) { + buf(lastPos) = '0' + pos = lastPos + 1 + } else pos = lastPos + ByteArrayAccess.setShort(buf, pos, 0x2d45) + pos += 1 + if (e10 < 0) { + e10 = -e10 + pos += 1 + } + if (e10 < 10) { + buf(pos) = (e10 | '0').toByte + pos += 1 + } else { + ByteArrayAccess.setShort(buf, pos, ds(e10)) + pos += 2 + } + } else if (e10 < 0) { + val dotPos = pos + 1 + ByteArrayAccess.setInt(buf, pos, 0x30303030) + pos -= e10 + pos = writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + buf(dotPos) = '.' + } else if (e10 < len - 1) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + val bs = ByteArrayAccess.getLong(buf, pos) + val s = e10 << 3 + val m = 0xffffffffffff0000L << s + val d1 = (~m & bs) >> 8 + val d2 = 0x2e00L << s + val d3 = m & bs + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos = lastPos + } else { + pos += len + writePositiveIntDigits(m10, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, 0x302e) + pos += 2 + } + } + count = pos + } + + private[this] def rop(g: Long, cp: Int): Int = { + val x = Math.multiplyHigh(g, cp.toLong << 32) + (x >>> 31).toInt | -x.toInt >>> 31 + } + + // Based on the amazing work of Raffaello Giulietti + // "The Schubfach way to render doubles": https://drive.google.com/file/d/1luHhyQF9zKlM8yJ1nebU0OgVYhfC6CBN/view + // Sources with the license are here: https://github.com/c4f7fcce9cb06515/Schubfach/blob/3c92d3c9b1fead540616c918cdfef432bca53dfa/todec/src/math/DoubleToDecimal.java + private[this] def writeDouble(x: Double): Unit = { + var pos = ensureBufCapacity( + 25 + ) // -1.2898455142673966E-135.toString.length + 1 + val buf = this.buf + if (x < 0.0) { + buf(pos) = '-' + pos += 1 + } + if (x == 0.0) { + ByteArrayAccess.setInt(buf, pos, 0x302e30) + pos += 3 + } else { + val bits = java.lang.Double.doubleToRawLongBits(x) + var e2 = ((bits >> 52).toInt & 0x7ff) - 1075 + var m2 = bits & 0xfffffffffffffL | 0x10000000000000L + var m10 = 0L + var e10 = 0 + if (e2 == 0) m10 = m2 + else if ((e2 >= -52 && e2 < 0) && m2 << e2 == 0) m10 = m2 >> -e2 + else { + var e10Corr, e2Corr = 0 + var cblCorr = 2 + if (e2 == -1075) { + m2 &= 0xfffffffffffffL + e2 = -1074 + if (m2 < 3) { + m2 *= 10 + e10Corr = 1 + } + } else if (e2 == 972) illegalNumberError(x) + else if (m2 == 0x10000000000000L && e2 > -1074) { + e2Corr = 131007 + cblCorr = 1 + } + e10 = e2 * 315653 - e2Corr >> 20 + val gs = DefaultTokenWriter.gs + val i = e10 + 324 << 1 + val g1 = gs(i) + val g0 = gs(i + 1) + val h = (e10 * -108853 >> 15) + e2 + 2 + val cb = m2 << 2 + val vbCorr = (m2.toInt & 0x1) - 1 + val vb = rop(g1, g0, cb << h) + val vbl = rop(g1, g0, cb - cblCorr << h) + vbCorr + val vbr = rop(g1, g0, cb + 2 << h) - vbCorr + var diff = 0 + if ( + vb < 400 || { + m10 = Math.multiplyHigh( + vb, + 461168601842738792L + ) // divide a positive long by 40 + val vb40 = m10 * 40 + diff = (vbl - vb40).toInt + ((vb40 - vbr).toInt + 40 ^ diff) >= 0 + } + ) { + m10 = vb >> 2 + val vb4 = m10 << 2 + diff = (vbl - vb4).toInt + if (((vb4 - vbr).toInt + 4 ^ diff) >= 0) + diff = (vb.toInt & 0x3) + (m10.toInt & 0x1) - 3 + } else e10Corr = -1 + m10 += ~diff >>> 31 + e10 -= e10Corr + } + val len = digitCount(m10) + e10 += len - 1 + val ds = digits + if (e10 < -3 || e10 >= 7) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, (buf(pos + 1) | 0x2e00).toShort) + if (lastPos - 3 < pos) { + buf(lastPos) = '0' + pos = lastPos + 1 + } else pos = lastPos + ByteArrayAccess.setShort(buf, pos, 0x2d45) + pos += 1 + if (e10 < 0) { + e10 = -e10 + pos += 1 + } + if (e10 < 10) { + buf(pos) = (e10 | '0').toByte + pos += 1 + } else if (e10 < 100) { + ByteArrayAccess.setShort(buf, pos, ds(e10)) + pos += 2 + } else pos = write3Digits(e10, pos, buf, ds) + } else if (e10 < 0) { + val dotPos = pos + 1 + ByteArrayAccess.setInt(buf, pos, 0x30303030) + pos -= e10 + pos = writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + buf(dotPos) = '.' + } else if (e10 < len - 1) { + val lastPos = + writeSignificantFractionDigits(m10, pos + len, pos, buf, ds) + val bs = ByteArrayAccess.getLong(buf, pos) + val s = e10 << 3 + val m = 0xffffffffffff0000L << s + val d1 = (~m & bs) >> 8 + val d2 = 0x2e00L << s + val d3 = m & bs + ByteArrayAccess.setLong(buf, pos, d1 | d2 | d3) + pos = lastPos + } else { + pos += len + writePositiveIntDigits(m10.toInt, pos, buf, ds) + ByteArrayAccess.setShort(buf, pos, 0x302e) + pos += 2 + } + } + count = pos + } + + private[this] def rop(g1: Long, g0: Long, cp: Long): Long = { + val x = Math.multiplyHigh(g0, cp) + (g1 * cp >>> 1) + Math.multiplyHigh(g1, cp) + (x >>> 63) | (-x ^ x) >>> 63 + } + + // Adoption of a nice trick from Daniel Lemire's blog that works for numbers up to 10^18: + // https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/ + private[this] def digitCount(x: Long): Int = + (offsets(java.lang.Long.numberOfLeadingZeros(x)) + x >> 58).toInt + + private[this] def writeSignificantFractionDigits( + x: Long, + p: Int, + pl: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + var q0 = x.toInt + var pos = p + var posLim = pl + if (q0 != x) { + val q1 = + (Math.multiplyHigh( + x, + 6189700196426901375L + ) >>> 25).toInt // divide a positive long by 100000000 + val r1 = (x - q1 * 100000000L).toInt + val posm8 = pos - 8 + if (r1 == 0) { + q0 = q1 + pos = posm8 + } else { + writeFractionDigits(q1, posm8, posLim, buf, ds) + q0 = r1 + posLim = posm8 + } + } + writeSignificantFractionDigits(q0, pos, posLim, buf, ds) + } + + private[this] def writeSignificantFractionDigits( + x: Int, + p: Int, + posLim: Int, + buf: Array[Byte], + ds: Array[Short] + ): Int = { + var q0 = x + var q1 = 0 + var pos = p + while ({ + val qp = q0 * 1374389535L + q1 = (qp >> 37).toInt // divide a positive int by 100 + (qp & 0x1fc0000000L) == 0 // check if q is divisible by 100 + }) { + q0 = q1 + pos -= 2 + } + val d = ds(q0 - q1 * 100) + ByteArrayAccess.setShort(buf, pos - 1, d) + writeFractionDigits(q1, pos - 2, posLim, buf, ds) + pos + ((0x3039 - d) >>> 31) + } + + private[this] def writeFractionDigits( + x: Int, + p: Int, + posLim: Int, + buf: Array[Byte], + ds: Array[Short] + ): Unit = { + var q0 = x + var pos = p + while (pos > posLim) { + val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100 + ByteArrayAccess.setShort(buf, pos - 1, ds(q0 - q1 * 100)) + q0 = q1 + pos -= 2 + } + } + + private[this] def writePositiveIntDigits( + x: Int, + p: Int, + buf: Array[Byte], + ds: Array[Short] + ): Unit = { + var q0 = x + var pos = p + while ({ + pos -= 2 + q0 >= 100 + }) { + val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100 + ByteArrayAccess.setShort(buf, pos, ds(q0 - q1 * 100)) + q0 = q1 + } + if (q0 < 10) buf(pos + 1) = (q0 | '0').toByte + else ByteArrayAccess.setShort(buf, pos, ds(q0)) + } + + private[this] def illegalNumberError(x: Float): Nothing = encodeError( + "illegal number: " + x + ) + + private[this] def illegalNumberError(x: Double): Nothing = encodeError( + "illegal number: " + x + ) + + private[this] def ensureBufCapacity(required: Int): Int = { + val pos = count + if (pos + required <= limit) pos + else flushAndGrowBuf(required, pos) + } + + private[this] def flushAndGrowBuf(required: Int, pos: Int): Int = + if (bbuf ne null) { + bbuf.put(buf, 0, pos) + if (required > limit) growBuf(required) + 0 + } else if (out ne null) { + out.write(buf, 0, pos) + if (required > limit) growBuf(required) + 0 + } else if (disableBufGrowing) + throw new ArrayIndexOutOfBoundsException("`buf` length exceeded") + else { + growBuf(pos + required) + pos + } + + private[this] def growBuf(required: Int): Unit = + setBuf( + java.util.Arrays.copyOf( + buf, + (-1 >>> Integer.numberOfLeadingZeros(limit | required)) + 1 + ) + ) + + private[this] def reallocateBufToPreferredSize(): Unit = setBuf( + new Array[Byte](config.preferredBufSize) + ) + + private[this] def setBuf(buf: Array[Byte]): Unit = { + this.buf = buf + limit = buf.length + } +} + +object DefaultTokenWriter { + /* Use the following code to generate `escapedChars` in Scala REPL: + val es = new Array[Byte](128) + java.util.Arrays.fill(es, 0, 32, -1: Byte) + es('\n') = 'n' + es('\r') = 'r' + es('\t') = 't' + es('\b') = 'b' + es('\f') = 'f' + es('\\') = '\\' + es('\"') = '"' + es(127) = -1 + es.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val escapedChars: Array[Byte] = Array( + -1, -1, -1, -1, -1, -1, -1, -1, 98, 116, 110, -1, 102, 114, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 34, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 + ) + private final val offsets = Array(5088146770730811392L, 5088146770730811392L, + 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, + 5088146770730811392L, 5088146770730811392L, 5088146770730811392L, + 4889916394579099648L, 4889916394579099648L, 4889916394579099648L, + 4610686018427387904L, 4610686018427387904L, 4610686018427387904L, + 4610686018427387904L, 4323355642275676160L, 4323355642275676160L, + 4323355642275676160L, 4035215266123964416L, 4035215266123964416L, + 4035215266123964416L, 3746993889972252672L, 3746993889972252672L, + 3746993889972252672L, 3746993889972252672L, 3458764413820540928L, + 3458764413820540928L, 3458764413820540928L, 3170534127668829184L, + 3170534127668829184L, 3170534127668829184L, 2882303760517117440L, + 2882303760517117440L, 2882303760517117440L, 2882303760517117440L, + 2594073385265405696L, 2594073385265405696L, 2594073385265405696L, + 2305843009203693952L, 2305843009203693952L, 2305843009203693952L, + 2017612633060982208L, 2017612633060982208L, 2017612633060982208L, + 2017612633060982208L, 1729382256910170464L, 1729382256910170464L, + 1729382256910170464L, 1441151880758548720L, 1441151880758548720L, + 1441151880758548720L, 1152921504606845976L, 1152921504606845976L, + 1152921504606845976L, 1152921504606845976L, 864691128455135132L, + 864691128455135132L, 864691128455135132L, 576460752303423478L, + 576460752303423478L, 576460752303423478L, 576460752303423478L, + 576460752303423478L, 576460752303423478L, 576460752303423478L) + /* Use the following code to generate `digits` in Scala REPL: + val ds = new Array[Short](100) + var i, j = 0 + while (j < 10) { + var k = 0 + while (k < 10) { + ds(i) = (((k + '0') << 8) + (j + '0')).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(10).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val digits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 12337, + 12593, 12849, 13105, 13361, 13617, 13873, 14129, 14385, 14641, 12338, 12594, + 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 12339, 12595, 12851, + 13107, 13363, 13619, 13875, 14131, 14387, 14643, 12340, 12596, 12852, 13108, + 13364, 13620, 13876, 14132, 14388, 14644, 12341, 12597, 12853, 13109, 13365, + 13621, 13877, 14133, 14389, 14645, 12342, 12598, 12854, 13110, 13366, 13622, + 13878, 14134, 14390, 14646, 12343, 12599, 12855, 13111, 13367, 13623, 13879, + 14135, 14391, 14647, 12344, 12600, 12856, 13112, 13368, 13624, 13880, 14136, + 14392, 14648, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, + 14649 + ) + /* Use the following code to generate `lowerCaseHexDigits` in Scala REPL: + val ds = new Array[Short](256) + var i, j = 0 + while (j < 16) { + val d1 = + if (j <= 9) j + '0' + else j + 'a' - 10 + var k = 0 + while (k < 16) { + val d2 = + if (k <= 9) k + '0' + else k + 'a' - 10 + ds(i) = ((d2 << 8) + d1).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val lowerCaseHexDigits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 24880, + 25136, 25392, 25648, 25904, 26160, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 24881, 25137, 25393, 25649, 25905, 26161, 12338, + 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 24882, 25138, + 25394, 25650, 25906, 26162, 12339, 12595, 12851, 13107, 13363, 13619, 13875, + 14131, 14387, 14643, 24883, 25139, 25395, 25651, 25907, 26163, 12340, 12596, + 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, 24884, 25140, 25396, + 25652, 25908, 26164, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, + 14389, 14645, 24885, 25141, 25397, 25653, 25909, 26165, 12342, 12598, 12854, + 13110, 13366, 13622, 13878, 14134, 14390, 14646, 24886, 25142, 25398, 25654, + 25910, 26166, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, + 14647, 24887, 25143, 25399, 25655, 25911, 26167, 12344, 12600, 12856, 13112, + 13368, 13624, 13880, 14136, 14392, 14648, 24888, 25144, 25400, 25656, 25912, + 26168, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649, + 24889, 25145, 25401, 25657, 25913, 26169, 12385, 12641, 12897, 13153, 13409, + 13665, 13921, 14177, 14433, 14689, 24929, 25185, 25441, 25697, 25953, 26209, + 12386, 12642, 12898, 13154, 13410, 13666, 13922, 14178, 14434, 14690, 24930, + 25186, 25442, 25698, 25954, 26210, 12387, 12643, 12899, 13155, 13411, 13667, + 13923, 14179, 14435, 14691, 24931, 25187, 25443, 25699, 25955, 26211, 12388, + 12644, 12900, 13156, 13412, 13668, 13924, 14180, 14436, 14692, 24932, 25188, + 25444, 25700, 25956, 26212, 12389, 12645, 12901, 13157, 13413, 13669, 13925, + 14181, 14437, 14693, 24933, 25189, 25445, 25701, 25957, 26213, 12390, 12646, + 12902, 13158, 13414, 13670, 13926, 14182, 14438, 14694, 24934, 25190, 25446, + 25702, 25958, 26214 + ) + /* Use the following code to generate `upperCaseHexDigits` in Scala REPL: + val ds = new Array[Short](256) + var i, j = 0 + while (j < 16) { + val d1 = + if (j <= 9) j + '0' + else j + 'A' - 10 + var k = 0 + while (k < 16) { + val d2 = + if (k <= 9) k + '0' + else k + 'A' - 10 + ds(i) = ((d2 << 8) + d1).toShort + i += 1 + k += 1 + } + j += 1 + } + ds.grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val upperCaseHexDigits: Array[Short] = Array( + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 16688, + 16944, 17200, 17456, 17712, 17968, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 16689, 16945, 17201, 17457, 17713, 17969, 12338, + 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 16690, 16946, + 17202, 17458, 17714, 17970, 12339, 12595, 12851, 13107, 13363, 13619, 13875, + 14131, 14387, 14643, 16691, 16947, 17203, 17459, 17715, 17971, 12340, 12596, + 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, 16692, 16948, 17204, + 17460, 17716, 17972, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, + 14389, 14645, 16693, 16949, 17205, 17461, 17717, 17973, 12342, 12598, 12854, + 13110, 13366, 13622, 13878, 14134, 14390, 14646, 16694, 16950, 17206, 17462, + 17718, 17974, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, + 14647, 16695, 16951, 17207, 17463, 17719, 17975, 12344, 12600, 12856, 13112, + 13368, 13624, 13880, 14136, 14392, 14648, 16696, 16952, 17208, 17464, 17720, + 17976, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649, + 16697, 16953, 17209, 17465, 17721, 17977, 12353, 12609, 12865, 13121, 13377, + 13633, 13889, 14145, 14401, 14657, 16705, 16961, 17217, 17473, 17729, 17985, + 12354, 12610, 12866, 13122, 13378, 13634, 13890, 14146, 14402, 14658, 16706, + 16962, 17218, 17474, 17730, 17986, 12355, 12611, 12867, 13123, 13379, 13635, + 13891, 14147, 14403, 14659, 16707, 16963, 17219, 17475, 17731, 17987, 12356, + 12612, 12868, 13124, 13380, 13636, 13892, 14148, 14404, 14660, 16708, 16964, + 17220, 17476, 17732, 17988, 12357, 12613, 12869, 13125, 13381, 13637, 13893, + 14149, 14405, 14661, 16709, 16965, 17221, 17477, 17733, 17989, 12358, 12614, + 12870, 13126, 13382, 13638, 13894, 14150, 14406, 14662, 16710, 16966, 17222, + 17478, 17734, 17990 + ) + /* Use the following code to generate `base64Digits` in Scala REPL: + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes + .grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64Digits: Array[Byte] = Array( + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43, 47 + ) + /* Use the following code to generate `base64UrlDigits` in Scala REPL: + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".getBytes + .grouped(16).map(_.mkString(", ")).mkString("Array(\n", ",\n", "\n)") + */ + private final val base64UrlDigits: Array[Byte] = Array( + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 45, 95 + ) + /* Use the following code to generate `gs` in Scala REPL: + val gs = new Array[Long](1234) + var i = 0 + var pow5 = BigInt(1) + while (i < 650) { + val av = (pow5 >> (pow5.bitLength - 126)) + 1 + gs(648 - i) = (av >> 63).longValue & 0x7FFFFFFFFFFFFFFFL + gs(649 - i) = av.longValue & 0x7FFFFFFFFFFFFFFFL + pow5 *= 5 + i += 2 + } + pow5 = BigInt(5) + while (i < 1234) { + val inv = ((BigInt(1) << (pow5.bitLength + 125)) / pow5) + 1 + gs(i) = (inv >> 63).longValue & 0x7FFFFFFFFFFFFFFFL + gs(i + 1) = inv.longValue & 0x7FFFFFFFFFFFFFFFL + pow5 *= 5 + i += 2 + } + gs.grouped(4).map(_.mkString("L, ")).mkString("Array(\n", "L,\n", "L\n)") + */ + private final val gs: Array[Long] = Array( + 5696189077778435540L, 6557778377634271669L, 9113902524445496865L, + 1269073367360058862L, 7291122019556397492L, 1015258693888047090L, + 5832897615645117993L, 6346230177223303157L, 4666318092516094394L, + 8766332956520552849L, 7466108948025751031L, 8492109508320019073L, + 5972887158420600825L, 4949013199285060097L, 4778309726736480660L, + 3959210559428048077L, 7645295562778369056L, 6334736895084876923L, + 6116236450222695245L, 3223115108696946377L, 4892989160178156196L, + 2578492086957557102L, 7828782656285049914L, 436238524390181040L, + 6263026125028039931L, 2193665226883099993L, 5010420900022431944L, + 9133629810990300641L, 8016673440035891111L, 9079784475471615541L, + 6413338752028712889L, 5419153173006337271L, 5130671001622970311L, + 6179996945776024979L, 8209073602596752498L, 6198646298499729642L, + 6567258882077401998L, 8648265853541694037L, 5253807105661921599L, + 1384589460720489745L, 8406091369059074558L, 5904691951894693915L, + 6724873095247259646L, 8413102376257665455L, 5379898476197807717L, + 4885807493635177203L, 8607837561916492348L, 438594360332462878L, + 6886270049533193878L, 4040224303007880625L, 5509016039626555102L, + 6921528257148214824L, 8814425663402488164L, 3695747581953323071L, + 7051540530721990531L, 4801272472933613619L, 5641232424577592425L, + 1996343570975935733L, 9025971879324147880L, 3194149713561497173L, + 7220777503459318304L, 2555319770849197738L, 5776622002767454643L, + 3888930224050313352L, 4621297602213963714L, 6800492993982161005L, + 7394076163542341943L, 5346765568258592123L, 5915260930833873554L, + 7966761269348784022L, 4732208744667098843L, 8218083422849982379L, + 7571533991467358150L, 2080887032334240837L, 6057227193173886520L, + 1664709625867392670L, 4845781754539109216L, 1331767700693914136L, + 7753250807262574745L, 7664851543223128102L, 6202600645810059796L, + 6131881234578502482L, 4962080516648047837L, 3060830580291846824L, + 7939328826636876539L, 6742003335837910079L, 6351463061309501231L, + 7238277076041283225L, 5081170449047600985L, 3945947253462071419L, + 8129872718476161576L, 6313515605539314269L, 6503898174780929261L, + 3206138077060496254L, 5203118539824743409L, 720236054277441842L, + 8324989663719589454L, 4841726501585817270L, 6659991730975671563L, + 5718055608639608977L, 5327993384780537250L, 8263793301653597505L, + 8524789415648859601L, 3998697245790980200L, 6819831532519087681L, + 1354283389261828999L, 5455865226015270144L, 8462124340893283845L, + 8729384361624432231L, 8005375723316388668L, 6983507489299545785L, + 4559626171282155773L, 5586805991439636628L, 3647700937025724618L, + 8938889586303418605L, 3991647091870204227L, 7151111669042734884L, + 3193317673496163382L, 5720889335234187907L, 4399328546167885867L, + 9153422936374700651L, 8883600081239572549L, 7322738349099760521L, + 5262205657620702877L, 5858190679279808417L, 2365090118725607140L, + 4686552543423846733L, 7426095317093351197L, 7498484069478154774L, + 813706063123630946L, 5998787255582523819L, 2495639257869859918L, + 4799029804466019055L, 3841185813666843096L, 7678447687145630488L, + 6145897301866948954L, 6142758149716504390L, 8606066656235469486L, + 4914206519773203512L, 6884853324988375589L, 7862730431637125620L, + 3637067690497580296L, 6290184345309700496L, 2909654152398064237L, + 5032147476247760397L, 483048914547496228L, 8051435961996416635L, + 2617552670646949126L, 6441148769597133308L, 2094042136517559301L, + 5152919015677706646L, 5364582523955957764L, 8244670425084330634L, + 4893983223587622099L, 6595736340067464507L, 5759860986241052841L, + 5276589072053971606L, 918539974250931950L, 8442542515286354569L, + 7003687180914356604L, 6754034012229083655L, 7447624152102440445L, + 5403227209783266924L, 5958099321681952356L, 8645163535653227079L, + 3998935692578258285L, 6916130828522581663L, 5043822961433561789L, + 5532904662818065330L, 7724407183888759755L, 8852647460508904529L, + 3135679457367239799L, 7082117968407123623L, 4353217973264747001L, + 5665694374725698898L, 7171923193353707924L, 9065110999561118238L, + 407030665140201709L, 7252088799648894590L, 4014973346854071690L, + 5801671039719115672L, 3211978677483257352L, 4641336831775292537L, + 8103606164099471367L, 7426138930840468060L, 5587072233075333540L, + 5940911144672374448L, 4469657786460266832L, 4752728915737899558L, + 7265075043910123789L, 7604366265180639294L, 556073626030467093L, + 6083493012144511435L, 2289533308195328836L, 4866794409715609148L, + 1831626646556263069L, 7786871055544974637L, 1085928227119065748L, + 6229496844435979709L, 6402765803808118083L, 4983597475548783767L, + 6966887050417449628L, 7973755960878054028L, 3768321651184098759L, + 6379004768702443222L, 6704006135689189330L, 5103203814961954578L, + 1673856093809441141L, 8165126103939127325L, 833495342724150664L, + 6532100883151301860L, 666796274179320531L, 5225680706521041488L, + 533437019343456425L, 8361089130433666380L, 8232196860433350926L, + 6688871304346933104L, 6585757488346680741L, 5351097043477546483L, + 7113280398048299755L, 8561755269564074374L, 313202192651548637L, + 6849404215651259499L, 2095236161492194072L, 5479523372521007599L, + 3520863336564710419L, 8767237396033612159L, 99358116390671185L, + 7013789916826889727L, 1924160900483492110L, 5611031933461511781L, + 7073351942499659173L, 8977651093538418850L, 7628014293257544353L, + 7182120874830735080L, 6102411434606035483L, 5745696699864588064L, + 4881929147684828386L, 9193114719783340903L, 2277063414182859933L, + 7354491775826672722L, 5510999546088198270L, 5883593420661338178L, + 719450822128648293L, 4706874736529070542L, 4264909472444828957L, + 7530999578446512867L, 8668529563282681493L, 6024799662757210294L, + 3245474835884234871L, 4819839730205768235L, 4441054276078343059L, + 7711743568329229176L, 7105686841725348894L, 6169394854663383341L, + 3839875066009323953L, 4935515883730706673L, 1227225645436504001L, + 7896825413969130677L, 118886625327451240L, 6317460331175304541L, + 5629132522374826477L, 5053968264940243633L, 2658631610528906020L, + 8086349223904389813L, 2409136169475294470L, 6469079379123511850L, + 5616657750322145900L, 5175263503298809480L, 4493326200257716720L, + 8280421605278095168L, 7189321920412346751L, 6624337284222476135L, + 217434314217011916L, 5299469827377980908L, 173947451373609533L, + 8479151723804769452L, 7657013551681595899L, 6783321379043815562L, + 2436262026603366396L, 5426657103235052449L, 7483032843395558602L, + 8682651365176083919L, 6438829327320028278L, 6946121092140867135L, + 6995737869226977784L, 5556896873712693708L, 5596590295381582227L, + 8891034997940309933L, 7109870065239576402L, 7112827998352247947L, + 153872830078795637L, 5690262398681798357L, 5657121486175901994L, + 9104419837890877372L, 1672696748397622544L, 7283535870312701897L, + 6872180620830963520L, 5826828696250161518L, 1808395681922860493L, + 4661462957000129214L, 5136065360280198718L, 7458340731200206743L, + 2683681354335452463L, 5966672584960165394L, 5836293898210272294L, + 4773338067968132315L, 6513709525939172997L, 7637340908749011705L, + 1198563204647900987L, 6109872726999209364L, 958850563718320789L, + 4887898181599367491L, 2611754858345611793L, 7820637090558987986L, + 489458958611068546L, 6256509672447190388L, 7770264796372675483L, + 5005207737957752311L, 682188614985274902L, 8008332380732403697L, + 6625525006089305327L, 6406665904585922958L, 1611071190129533939L, + 5125332723668738366L, 4978205766845537474L, 8200532357869981386L, + 4275780412210949635L, 6560425886295985109L, 1575949922397804547L, + 5248340709036788087L, 3105434345289198799L, 8397345134458860939L, + 6813369359833673240L, 6717876107567088751L, 7295369895237893754L, + 5374300886053671001L, 3991621508819359841L, 8598881417685873602L, + 2697245599369065423L, 6879105134148698881L, 7691819701608117823L, + 5503284107318959105L, 4308781353915539097L, 8805254571710334568L, + 6894050166264862555L, 7044203657368267654L, 9204588947753800367L, + 5635362925894614123L, 9208345565573995455L, 9016580681431382598L, + 3665306460692661759L, 7213264545145106078L, 6621593983296039730L, + 5770611636116084862L, 8986624001378742108L, 4616489308892867890L, + 3499950386361083363L, 7386382894228588624L, 5599920618177733380L, + 5909106315382870899L, 6324610901913141866L, 4727285052306296719L, + 6904363128901468655L, 7563656083690074751L, 5512957784129484362L, + 6050924866952059801L, 2565691819932632328L, 4840739893561647841L, + 207879048575150701L, 7745183829698636545L, 5866629699833106606L, + 6196147063758909236L, 4693303759866485285L, 4956917651007127389L, + 1909968600522233067L, 7931068241611403822L, 6745298575577483229L, + 6344854593289123058L, 1706890045720076260L, 5075883674631298446L, + 5054860851317971332L, 8121413879410077514L, 4398428547366843807L, + 6497131103528062011L, 5363417245264430207L, 5197704882822449609L, + 2446059388840589004L, 8316327812515919374L, 7603043836886852730L, + 6653062250012735499L, 7927109476880437346L, 5322449800010188399L, + 8186361988875305038L, 8515919680016301439L, 7564155960087622576L, + 6812735744013041151L, 7895999175441053223L, 5450188595210432921L, + 4472124932981887417L, 8720301752336692674L, 3466051078029109543L, + 6976241401869354139L, 4617515269794242796L, 5580993121495483311L, + 5538686623206349399L, 8929588994392773298L, 5172549782388248714L, + 7143671195514218638L, 7827388640652509295L, 5714936956411374911L, + 727887690409141951L, 9143899130258199857L, 6698643526767492606L, + 7315119304206559886L, 1669566006672083762L, 5852095443365247908L, + 8714350434821487656L, 4681676354692198327L, 1437457125744324640L, + 7490682167507517323L, 4144605808561874585L, 5992545734006013858L, + 7005033461591409992L, 4794036587204811087L, 70003547160262509L, + 7670458539527697739L, 1956680082827375175L, 6136366831622158191L, + 3410018473632855302L, 4909093465297726553L, 883340371535329080L, + 7854549544476362484L, 8792042223940347174L, 6283639635581089987L, + 8878308186523232901L, 5026911708464871990L, 3413297734476675998L, + 8043058733543795184L, 5461276375162681596L, 6434446986835036147L, + 6213695507501100438L, 5147557589468028918L, 1281607591258970028L, + 8236092143148846269L, 205897738643396882L, 6588873714519077015L, + 2009392598285672668L, 5271098971615261612L, 1607514078628538134L, + 8433758354584418579L, 4416696933176616176L, 6747006683667534863L, + 5378031953912248102L, 5397605346934027890L, 7991774377871708805L, + 8636168555094444625L, 3563466967739958280L, 6908934844075555700L, + 2850773574191966624L, 5527147875260444560L, 2280618859353573299L, + 8843436600416711296L, 3648990174965717279L, 7074749280333369037L, + 1074517732601618662L, 5659799424266695229L, 6393637408194160414L, + 9055679078826712367L, 4695796630997791177L, 7244543263061369894L, + 67288490056322619L, 5795634610449095915L, 1898505199416013257L, + 4636507688359276732L, 1518804159532810606L, 7418412301374842771L, + 4274761062623452130L, 5934729841099874217L, 1575134442727806543L, + 4747783872879899373L, 6794130776295110719L, 7596454196607838997L, + 9025934834701221989L, 6077163357286271198L, 3531399053019067268L, + 4861730685829016958L, 6514468057157164137L, 7778769097326427133L, + 8578474484080507458L, 6223015277861141707L, 1328756365151540482L, + 4978412222288913365L, 6597028314234097870L, 7965459555662261385L, + 1331873265919780784L, 6372367644529809108L, 1065498612735824627L, + 5097894115623847286L, 4541747704930570025L, 8156630584998155658L, + 3577447513147001717L, 6525304467998524526L, 6551306825259511697L, + 5220243574398819621L, 3396371052836654196L, 8352389719038111394L, + 1744844869796736390L, 6681911775230489115L, 3240550303208344274L, + 5345529420184391292L, 2592440242566675419L, 8552847072295026067L, + 5992578795477635832L, 6842277657836020854L, 1104714221640198342L, + 5473822126268816683L, 2728445784683113836L, 8758115402030106693L, + 2520838848122026975L, 7006492321624085354L, 5706019893239531903L, + 5605193857299268283L, 6409490321962580684L, 8968310171678829253L, + 8410510107769173933L, 7174648137343063403L, 1194384864102473662L, + 5739718509874450722L, 4644856706023889253L, 9183549615799121156L, + 53073100154402158L, 7346839692639296924L, 7421156109607342373L, + 5877471754111437539L, 7781599295056829060L, 4701977403289150031L, + 8069953843416418410L, 7523163845262640050L, 9222577334724359132L, + 6018531076210112040L, 7378061867779487306L, 4814824860968089632L, + 5902449494223589845L, 7703719777548943412L, 2065221561273923105L, + 6162975822039154729L, 7186200471132003969L, 4930380657631323783L, + 7593634784276558337L, 7888609052210118054L, 1081769210616762369L, + 6310887241768094443L, 2710089775864365057L, 5048709793414475554L, + 5857420635433402369L, 8077935669463160887L, 3837849794580578305L, + 6462348535570528709L, 8604303057777328129L, 5169878828456422967L, + 8728116853592817665L, 8271806125530276748L, 6586289336264687617L, + 6617444900424221398L, 8958380283753660417L, 5293955920339377119L, + 1632681004890062849L, 8470329472543003390L, 6301638422566010881L, + 6776263578034402712L, 5041310738052808705L, 5421010862427522170L, + 343699775700336641L, 8673617379884035472L, 549919641120538625L, + 6938893903907228377L, 5973958935009296385L, 5551115123125782702L, + 1089818333265526785L, 8881784197001252323L, 3588383740595798017L, + 7105427357601001858L, 6560055807218548737L, 5684341886080801486L, + 8937393460516749313L, 9094947017729282379L, 1387108685230112769L, + 7275957614183425903L, 2954361355555045377L, 5820766091346740722L, + 6052837899185946625L, 4656612873077392578L, 1152921504606846977L, + 7450580596923828125L, 1L, 5960464477539062500L, 1L, 4768371582031250000L, + 1L, 7629394531250000000L, 1L, 6103515625000000000L, 1L, + 4882812500000000000L, 1L, 7812500000000000000L, 1L, 6250000000000000000L, + 1L, 5000000000000000000L, 1L, 8000000000000000000L, 1L, + 6400000000000000000L, 1L, 5120000000000000000L, 1L, 8192000000000000000L, + 1L, 6553600000000000000L, 1L, 5242880000000000000L, 1L, + 8388608000000000000L, 1L, 6710886400000000000L, 1L, 5368709120000000000L, + 1L, 8589934592000000000L, 1L, 6871947673600000000L, 1L, + 5497558138880000000L, 1L, 8796093022208000000L, 1L, 7036874417766400000L, + 1L, 5629499534213120000L, 1L, 9007199254740992000L, 1L, + 7205759403792793600L, 1L, 5764607523034234880L, 1L, 4611686018427387904L, + 1L, 7378697629483820646L, 3689348814741910324L, 5902958103587056517L, + 1106804644422573097L, 4722366482869645213L, 6419466937650923963L, + 7555786372591432341L, 8426472692870523179L, 6044629098073145873L, + 4896503746925463381L, 4835703278458516698L, 7606551812282281028L, + 7737125245533626718L, 1102436455425918676L, 6189700196426901374L, + 4571297979082645264L, 4951760157141521099L, 5501712790637071373L, + 7922816251426433759L, 3268717242906448711L, 6338253001141147007L, + 4459648201696114131L, 5070602400912917605L, 9101741783469756789L, + 8112963841460668169L, 5339414816696835055L, 6490371073168534535L, + 6116206260728423206L, 5192296858534827628L, 4892965008582738565L, + 8307674973655724205L, 5984069606361426541L, 6646139978924579364L, + 4787255685089141233L, 5316911983139663491L, 5674478955442268148L, + 8507059173023461586L, 5389817513965718714L, 6805647338418769269L, + 2467179603801619810L, 5444517870735015415L, 3818418090412251009L, + 8711228593176024664L, 6109468944659601615L, 6968982874540819731L, + 6732249563098636453L, 5575186299632655785L, 3541125243107954001L, + 8920298079412249256L, 5665800388972726402L, 7136238463529799405L, + 2687965903807225960L, 5708990770823839524L, 2150372723045780768L, + 9134385233318143238L, 7129945171615159552L, 7307508186654514591L, + 169932915179262157L, 5846006549323611672L, 7514643961627230372L, + 4676805239458889338L, 2322366354559873974L, 7482888383134222941L, + 1871111759924843197L, 5986310706507378352L, 8875587037423695204L, + 4789048565205902682L, 3411120815197045840L, 7662477704329444291L, + 7302467711686228506L, 6129982163463555433L, 3997299761978027643L, + 4903985730770844346L, 6887188624324332438L, 7846377169233350954L, + 7330152984177021577L, 6277101735386680763L, 7708796794712572423L, + 5021681388309344611L, 633014213657192454L, 8034690221294951377L, + 6546845963964373411L, 6427752177035961102L, 1548127956429588405L, + 5142201741628768881L, 6772525587256536209L, 8227522786606030210L, + 7146692124868547611L, 6582018229284824168L, 5717353699894838089L, + 5265614583427859334L, 8263231774657780795L, 8424983333484574935L, + 7687147617339583786L, 6739986666787659948L, 6149718093871667029L, + 5391989333430127958L, 8609123289839243947L, 8627182933488204734L, + 2706550819517059345L, 6901746346790563787L, 4009915062984602637L, + 5521397077432451029L, 8741955272500547595L, 8834235323891921647L, + 8453105213888010667L, 7067388259113537318L, 3073135356368498210L, + 5653910607290829854L, 6147857099836708891L, 9046256971665327767L, + 4302548137625868741L, 7237005577332262213L, 8976061732213560478L, + 5789604461865809771L, 1646826163657982898L, 4631683569492647816L, + 8696158560410206965L, 7410693711188236507L, 1001132845059645012L, + 5928554968950589205L, 6334929498160581494L, 4742843975160471364L, + 5067943598528465196L, 7588550360256754183L, 2574686535532678828L, + 6070840288205403346L, 5749098043168053386L, 4856672230564322677L, + 2754604027163487547L, 7770675568902916283L, 6252040850832535236L, + 6216540455122333026L, 8690981495407938512L, 4973232364097866421L, + 5108110788955395648L, 7957171782556586274L, 4483628447586722714L, + 6365737426045269019L, 5431577165440333333L, 5092589940836215215L, + 6189936139723221828L, 8148143905337944345L, 680525786702379117L, + 6518515124270355476L, 544420629361903293L, 5214812099416284380L, + 7814234132973343281L, 8343699359066055009L, 3279402575902573442L, + 6674959487252844007L, 4468196468093013915L, 5339967589802275205L, + 9108580396587276617L, 8543948143683640329L, 5350356597684866779L, + 6835158514946912263L, 6124959685518848585L, 5468126811957529810L, + 8589316563156989191L, 8749002899132047697L, 4519534464196406897L, + 6999202319305638157L, 9149650793469991003L, 5599361855444510526L, + 3630371820034082479L, 8958978968711216842L, 2119246097312621643L, + 7167183174968973473L, 7229420099962962799L, 5733746539975178779L, + 249512857857504755L, 9173994463960286046L, 4088569387313917931L, + 7339195571168228837L, 1426181102480179183L, 5871356456934583069L, + 6674968104097008831L, 4697085165547666455L, 7184648890648562227L, + 7515336264876266329L, 2272066188182923754L, 6012269011901013063L, + 3662327357917294165L, 4809815209520810450L, 6619210701075745655L, + 7695704335233296721L, 1367365084866417240L, 6156563468186637376L, + 8472589697376954439L, 4925250774549309901L, 4933397350530608390L, + 7880401239278895842L, 4204086946107063100L, 6304320991423116673L, + 8897292778998515965L, 5043456793138493339L, 1583811001085947287L, + 8069530869021589342L, 6223446416479425982L, 6455624695217271474L, + 1289408318441630463L, 5164499756173817179L, 2876201062124259532L, + 8263199609878107486L, 8291270514140725574L, 6610559687902485989L, + 4788342003941625298L, 5288447750321988791L, 5675348010524255400L, + 8461516400515182066L, 5391208002096898316L, 6769213120412145653L, + 2468291994306563491L, 5415370496329716522L, 5663982410187161116L, + 8664592794127546436L, 1683674226815637140L, 6931674235302037148L, + 8725637010936330358L, 5545339388241629719L, 1446486386636198802L, + 8872543021186607550L, 6003727033359828406L, 7098034416949286040L, + 4802981626687862725L, 5678427533559428832L, 3842385301350290180L, + 9085484053695086131L, 7992490889531419449L, 7268387242956068905L, + 4549318304254180398L, 5814709794364855124L, 3639454643403344318L, + 4651767835491884099L, 4756238122093630616L, 7442828536787014559L, + 2075957773236943501L, 5954262829429611647L, 3505440625960509963L, + 4763410263543689317L, 8338375722881273455L, 7621456421669902908L, + 5962703527126216881L, 6097165137335922326L, 8459511636442883828L, + 4877732109868737861L, 4922934901783351901L, 7804371375789980578L, + 4187347028111452718L, 6243497100631984462L, 7039226437231072498L, + 4994797680505587570L, 1942032335042947675L, 7991676288808940112L, + 3107251736068716280L, 6393341031047152089L, 8019824610967838509L, + 5114672824837721671L, 8260534096145225969L, 8183476519740354675L, + 304133702235675419L, 6546781215792283740L, 243306961788540335L, + 5237424972633826992L, 194645569430832268L, 8379879956214123187L, + 2156107318460286790L, 6703903964971298549L, 7258909076881094917L, + 5363123171977038839L, 7651801668875831096L, 8580997075163262143L, + 6708859448088464268L, 6864797660130609714L, 9056436373212681737L, + 5491838128104487771L, 9089823505941100552L, 8786941004967180435L, + 1630996757909074751L, 7029552803973744348L, 1304797406327259801L, + 5623642243178995478L, 4733186739803718164L, 8997827589086392765L, + 5728424376314993901L, 7198262071269114212L, 4582739501051995121L, + 5758609657015291369L, 9200214822954461581L, 9213775451224466191L, + 9186320494614273045L, 7371020360979572953L, 5504381988320463275L, + 5896816288783658362L, 8092854405398280943L, 4717453031026926690L, + 2784934709576714431L, 7547924849643082704L, 4455895535322743090L, + 6038339879714466163L, 5409390835629149634L, 4830671903771572930L, + 8016861483245230030L, 7729075046034516689L, 3603606336337592240L, + 6183260036827613351L, 4727559476441028954L, 4946608029462090681L, + 1937373173781868001L, 7914572847139345089L, 8633820300163854287L, + 6331658277711476071L, 8751730647502038591L, 5065326622169180857L, + 5156710110630675711L, 8104522595470689372L, 872038547525260492L, + 6483618076376551497L, 6231654060133073878L, 5186894461101241198L, + 1295974433364548779L, 8299031137761985917L, 228884686012322885L, + 6639224910209588733L, 5717130970922723793L, 5311379928167670986L, + 8263053591480089358L, 8498207885068273579L, 308164894771456841L, + 6798566308054618863L, 2091206323188120634L, 5438853046443695090L, + 5362313873292406831L, 8702164874309912144L, 8579702197267850929L, + 6961731899447929715L, 8708436165185235905L, 5569385519558343772L, + 6966748932148188724L, 8911016831293350036L, 3768100661953281312L, + 7128813465034680029L, 1169806122191669888L, 5703050772027744023L, + 2780519305124291072L, 9124881235244390437L, 2604156480827910553L, + 7299904988195512349L, 7617348406775193928L, 5839923990556409879L, + 7938553132791110304L, 4671939192445127903L, 8195516913603843405L, + 7475102707912204646L, 2044780617540418478L, 5980082166329763716L, + 9014522123516155429L, 4784065733063810973L, 5366943291441969181L, + 7654505172902097557L, 6742434858936195528L, 6123604138321678046L, + 1704599072407046100L, 4898883310657342436L, 8742376887409457526L, + 7838213297051747899L, 1075082168258445910L, 6270570637641398319L, + 2704740141977711890L, 5016456510113118655L, 4008466520953124674L, + 8026330416180989848L, 6413546433524999478L, 6421064332944791878L, + 8820185961561909905L, 5136851466355833503L, 1522125547136662440L, + 8218962346169333605L, 590726468047704741L, 6575169876935466884L, + 472581174438163793L, 5260135901548373507L, 2222739346921486196L, + 8416217442477397611L, 5401057362445333075L, 6732973953981918089L, + 2476171482585311299L, 5386379163185534471L, 3825611593439204201L, + 8618206661096855154L, 2431629734760816398L, 6894565328877484123L, + 3789978195179608280L, 5515652263101987298L, 6721331370885596947L, + 8825043620963179677L, 8909455786045999954L, 7060034896770543742L, + 3438215814094889640L, 5648027917416434993L, 8284595873388777197L, + 9036844667866295990L, 2187306953196312545L, 7229475734293036792L, + 1749845562557050036L, 5783580587434429433L, 6933899672158505514L, + 4626864469947543547L, 13096515613938926L, 7402983151916069675L, + 1865628832353257443L, 5922386521532855740L, 1492503065882605955L, + 4737909217226284592L, 1194002452706084764L, 7580654747562055347L, + 3755078331700690783L, 6064523798049644277L, 8538085887473418112L, + 4851619038439715422L, 3141119895236824166L, 7762590461503544675L, + 6870466239749873827L, 6210072369202835740L, 5496372991799899062L, + 4968057895362268592L, 4397098393439919250L, 7948892632579629747L, + 8880031836874825961L, 6359114106063703798L, 3414676654757950445L, + 5087291284850963038L, 6421090138548270680L, 8139666055761540861L, + 8429069814306277926L, 6511732844609232689L, 4898581444074067179L, + 5209386275687386151L, 5763539562630208905L, 8335018041099817842L, + 5532314485466423924L, 6668014432879854274L, 736502773631228816L, + 5334411546303883419L, 2433876626275938215L, 8535058474086213470L, + 7583551416783411467L, 6828046779268970776L, 6066841133426729173L, + 5462437423415176621L, 3008798499370428177L, 8739899877464282594L, + 1124728784250774760L, 6991919901971426075L, 2744457434771574970L, + 5593535921577140860L, 2195565947817259976L, 8949657474523425376L, + 3512905516507615961L, 7159725979618740301L, 965650005835137607L, + 5727780783694992240L, 8151217634151930732L, 9164449253911987585L, + 3818576177788313364L, 7331559403129590068L, 3054860942230650691L, + 5865247522503672054L, 6133237568526430876L, 4692198018002937643L, + 6751264462192099863L, 7507516828804700229L, 8957348732136404618L, + 6006013463043760183L, 9010553393080078856L, 4804810770435008147L, + 1674419492351197600L, 7687697232696013035L, 4523745595132871322L, + 6150157786156810428L, 3618996476106297057L, 4920126228925448342L, + 6584545995626947969L, 7872201966280717348L, 3156575963519296104L, + 6297761573024573878L, 6214609585557347207L, 5038209258419659102L, + 8661036483187788089L, 8061134813471454564L, 6478960743616640295L, + 6448907850777163651L, 7027843002264267398L, 5159126280621730921L, + 3777599994440458757L, 8254602048994769474L, 2354811176362823687L, + 6603681639195815579L, 3728523348461214111L, 5282945311356652463L, + 4827493086139926451L, 8452712498170643941L, 5879314530452927160L, + 6762169998536515153L, 2858777216991386566L, 5409735998829212122L, + 5976370588335019576L, 8655577598126739396L, 2183495311852210675L, + 6924462078501391516L, 9125493878965589187L, 5539569662801113213L, + 5455720695801516188L, 8863311460481781141L, 6884478705911470739L, + 7090649168385424913L, 3662908557358221429L, 5672519334708339930L, + 6619675660628487467L, 9076030935533343889L, 1368109020150804139L, + 7260824748426675111L, 2939161623491598473L, 5808659798741340089L, + 506654891422323617L, 4646927838993072071L, 2249998320508814055L, + 7435084542388915313L, 9134020534926967972L, 5948067633911132251L, + 1773193205828708893L, 4758454107128905800L, 8797252194146787761L, + 7613526571406249281L, 4852231473780084609L, 6090821257124999425L, + 2037110771653112526L, 4872657005699999540L, 1629688617322490021L, + 7796251209119999264L, 2607501787715984033L, 6237000967295999411L, + 3930675837543742388L, 4989600773836799529L, 1299866262664038749L, + 7983361238138879246L, 5769134835004372321L, 6386688990511103397L, + 2770633460632542696L, 5109351192408882717L, 7750529990618899641L, + 8174961907854212348L, 5022150355506418780L, 6539969526283369878L, + 7707069099147045347L, 5231975621026695903L, 631632057204770793L, + 8371160993642713444L, 8389308921011453915L, 6696928794914170755L, + 8556121544180118293L, 5357543035931336604L, 6844897235344094635L, + 8572068857490138567L, 5417812354437685931L, 6857655085992110854L, + 644901068808238421L, 5486124068793688683L, 2360595262417545899L, + 8777798510069901893L, 1932278012497118276L, 7022238808055921514L, + 5235171224739604944L, 5617791046444737211L, 6032811387162639117L, + 8988465674311579538L, 5963149404718312264L, 7190772539449263630L, + 8459868338516560134L, 5752618031559410904L, 6767894670813248108L, + 9204188850495057447L, 5294608251188331487L + ) + @volatile private[this] var tenPow18Squares: Array[BigInteger] = Array( + BigInteger.valueOf(1000000000000000000L) + ) + + final private def getTenPow18Squares(n: Int): Array[BigInteger] = { + var ss = tenPow18Squares + var i = ss.length + if (n >= i) { + var s = ss(i - 1) + ss = java.util.Arrays.copyOf(ss, n + 1) + while (i <= n) { + s = s.multiply(s) + ss(i) = s + i += 1 + } + tenPow18Squares = ss + } + ss + } + + /** Checks if a character does not require JSON escaping or encoding. + * + * @param ch + * the character to check + * @return + * `true` if the character is a basic ASCII character (code point less than + * `0x80`) that does not need JSON escaping + */ + final def isNonEscapedAscii(ch: Char): Boolean = + ch < 0x80 && escapedChars(ch.toInt) == 0 +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala index e72be059..9497ee1d 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/SimpleTokenWriter.scala @@ -93,6 +93,9 @@ class SimpleTokenWriter extends TokenWriter { this } } + + override def result(): String = + throw new UnsupportedOperationException("SimpleTokenWriter.outString") } object SimpleTokenWriter { diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala index d1172d0a..d71c6a98 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala @@ -1,32 +1,98 @@ package tethys.writers.tokens + trait TokenWriter { + + /** Writes a JSON array start marker [ . */ def writeArrayStart(): this.type + /** Writes a JSON array end marker ] . */ def writeArrayEnd(): this.type + /** Writes a JSON object start marker { . */ def writeObjectStart(): this.type + /** Writes a JSON object end marker } . */ def writeObjectEnd(): this.type + /** Writes a `String` value as a JSON key. + * + * @param name + * the `String` value to write + * @throws TokenWriterException + * if the provided string has an illegal surrogate pair + */ def writeFieldName(name: String): this.type + /** Writes a `String` value as a JSON value. + * + * @param v + * the `String` value to write + * @throws TokenWriterException + * if the provided string has an illegal surrogate pair + */ def writeString(v: String): this.type + /** Writes a `Byte` value as a JSON value. + * + * @param v + * the `Byte` value to write + */ def writeNumber(v: Byte): this.type + /** Writes a `Short` value as a JSON value. + * + * @param v + * the `Short` value to write + */ def writeNumber(v: Short): this.type + /** Writes a `Int` value as a JSON value. + * + * @param v + * the `Int` value to write + */ def writeNumber(v: Int): this.type + /** Writes a `Long` value as a JSON value. + * + * @param v + * the `Long` value to write + */ def writeNumber(v: Long): this.type - def writeNumber(v: BigInt): this.type - + /** Writes a `Double` value as a JSON value. + * + * @param v + * the `Double` value to write + * + * @throws TokenWriterException + * if the value is non-finite + */ def writeNumber(v: Double): this.type + /** Writes a `Float` value as a JSON value. + * + * @param v + * the `Float` value to write + * + * @throws TokenWriterException + * if the value is non-finite + */ def writeNumber(v: Float): this.type + /** Writes a `BigInt` value as a JSON value. + * + * @param v + * the `BigInt` value to write + */ + def writeNumber(v: BigInt): this.type + + /** Writes a `BigDecimal` value as a JSON value. + * + * @param v + * the `BigDecimal` value to write + */ def writeNumber(v: BigDecimal): this.type def writeRawNumber(n: Number): this.type = n match { @@ -43,14 +109,22 @@ trait TokenWriter { case num => writeNumber(num.doubleValue()) } + /** Writes a `Boolean` value as a JSON value. + * + * @param v + * the `Boolean` value to write + */ def writeBoolean(v: Boolean): this.type + /** Writes a JSON `null` value. */ def writeNull(): this.type @throws[UnsupportedOperationException] def writeRawJson(json: String): this.type + def flush(): Unit + def close(): Unit - def flush(): Unit + def result(): String } diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala new file mode 100644 index 00000000..81774a8d --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterConfig.scala @@ -0,0 +1,84 @@ +/* + This is copy of jsoniter-scala WriterConfig + https://github.com/plokhotnyuk/jsoniter-scala/blob/master/jsoniter-scala-core/shared/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/WriterConfig.scala + MIT License + + Copyright (c) 2017 Andriy Plokhotnyuk, and respective contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ +package tethys.writers.tokens + +/** Configuration for [[tethys.writers.tokens.DefaultTokenWriter]] that contains + * params for formatting of output JSON and for tuning of preferred size for + * internal byte buffer that is created on the writer instantiation and reused + * in runtime for serialization of messages using any type of output except + * pre-allocated byte arrays or heap byte buffers supplied as arguments.
+ * All configuration params already initialized to default values, but in some + * cases they should be altered: + * @param indentionStep + * a size of indention for pretty-printed formatting or 0 for compact output + * @param escapeUnicode + * a flag to turn on hexadecimal escaping of all non-ASCII chars + * @param preferredBufSize + * a preferred size (in bytes) of an internal byte buffer when writing to any + * type of output except pre-allocated byte arrays or heap byte buffers + * supplied as arguments + */ +case class TokenWriterConfig private[tethys] ( + private[tethys] val indentionStep: Int, + private[tethys] val preferredBufSize: Int, + private[tethys] val escapeUnicode: Boolean +) extends Serializable { + + /** Pretty prints output with 2 spaces indentation. Implementation is backend + * specific and result may vary + */ + def withDefaultPrettyPrinter: TokenWriterConfig = + copy(indentionStep = 2) + + /** Escapes all non-ASCII characters. Implementation is backend specific and + * result may vary. + */ + def withEscapeUnicode(escapeUnicode: Boolean): TokenWriterConfig = + copy(escapeUnicode = escapeUnicode) + + /** Sets preferred buffer size. Not all backends support this setting + */ + def withBufferSize(size: Int): TokenWriterConfig = { + if (size <= 0) + throw new IllegalArgumentException( + "'preferredBufSize' should be not less than 1" + ) + copy(preferredBufSize = size) + } +} + +object TokenWriterConfig { + implicit val default: TokenWriterConfig = new TokenWriterConfig( + indentionStep = 0, + preferredBufSize = 32768, + escapeUnicode = false + ) +} diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala new file mode 100644 index 00000000..8bfba5ac --- /dev/null +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterException.scala @@ -0,0 +1,6 @@ +package tethys.writers.tokens + +class TokenWriterException( + msg: String, + cause: Throwable +) extends RuntimeException(msg, cause, true, true) diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala index a6fbff20..4bfd2110 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriterProducer.scala @@ -1,7 +1,20 @@ package tethys.writers.tokens -import java.io.Writer - trait TokenWriterProducer { - def forWriter(writer: Writer): TokenWriter + def produce(config: TokenWriterConfig): TokenWriter +} + +object TokenWriterProducer { + implicit val tethysTokenWriterProducer: TokenWriterProducer = + new TokenWriterProducer { + private val writerPool: ThreadLocal[DefaultTokenWriter] = + new ThreadLocal[DefaultTokenWriter] { + override def initialValue(): DefaultTokenWriter = + new DefaultTokenWriter(config = TokenWriterConfig.default) + } + + override def produce(config: TokenWriterConfig): TokenWriter = + writerPool.get().withConfig(config) + + } } From 7eb346efd3ee99b18ac83002153da35d7c75a328 Mon Sep 17 00:00:00 2001 From: Georgii Kovalev Date: Wed, 26 Mar 2025 15:45:23 +0530 Subject: [PATCH 3/4] add DefaultTokenWriter; tests --- .../jackson/JacksonJsonWriterSpec.scala | 169 ++++++++++ .../jackson/JacksonTokenWriterSpec.scala | 6 + .../jackson/JacksonTokenWriterTest.scala | 105 ------- .../tokens/DefaultJsonWriterSpec.scala | 79 +++++ .../tokens/DefaultTokenWriterSpec.scala | 3 + .../writers/tokens/JsonWriterSpec.scala | 293 ++++++++++++++++++ .../tethys/writers/tokens/TestModels.scala | 120 +++++++ .../writers/tokens/TokenWriterSpec.scala | 246 +++++++++++++++ 8 files changed, 916 insertions(+), 105 deletions(-) create mode 100644 modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala create mode 100644 modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala delete mode 100644 modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala create mode 100644 modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala create mode 100644 modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala create mode 100644 modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala create mode 100644 modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala create mode 100644 modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala new file mode 100644 index 00000000..7b8365ca --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonJsonWriterSpec.scala @@ -0,0 +1,169 @@ +package tethys.jackson + +import tethys.writers.tokens.{ + TokenWriterProducer, + TokenWriterConfig, + JsonWriterSpec +} +import tethys._ + +class JacksonJsonWriterSpec extends JsonWriterSpec { + { + import tethys.jackson.pretty._ + import tethys.writers.tokens.TestModels._ + + configurableTestCase( + description = "pretty print complex structures", + config = TokenWriterConfig.default.withDefaultPrettyPrinter, + value = Department( + "Global Engineering", + List( + Employee( + 1L, + Person("John Doe", 30, Some("john@example.com")), + Address("123 Main St", "New York", "USA", Some("10001")), + "Backend", + BigDecimal("100000.00") + ), + Employee( + 2L, + Person("Jane Smith", 28, None), + Address("456 Park Ave", "Boston", "USA", None), + "Frontend", + BigDecimal("95000.50") + ) + ), + Set("global", "engineering", "tech"), + Map( + "headquarters" -> "New York", + "founded" -> "2020", + "status" -> "active" + ) + ), + json = """{ + "name" : "Global Engineering", + "employees" : [ { + "id" : 1, + "person" : { + "name" : "John Doe", + "age" : 30, + "email" : "john@example.com" + }, + "address" : { + "street" : "123 Main St", + "city" : "New York", + "country" : "USA", + "postalCode" : "10001" + }, + "department" : "Backend", + "salary" : 100000.00 + }, { + "id" : 2, + "person" : { + "name" : "Jane Smith", + "age" : 28 + }, + "address" : { + "street" : "456 Park Ave", + "city" : "Boston", + "country" : "USA" + }, + "department" : "Frontend", + "salary" : 95000.50 + } ], + "tags" : [ "global", "engineering", "tech" ], + "metadata" : { + "headquarters" : "New York", + "founded" : "2020", + "status" : "active" + } +}""" + + ) + + configurableTestCase( + description = "escape unicode", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: \\u00A9 \\u20AC \\u2603\"" + ) + + configurableTestCase( + description = "escape unicode emoji", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = "Emoji: 😀 🚀 🌍", + json = "\"Emoji: \\uD83D\\uDE00 \\uD83D\\uDE80 \\uD83C\\uDF0D\"" + ) + + configurableTestCase( + description = "unicode characters in object keys", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Map("café" -> "coffee", "résumé" -> "CV", "über" -> "super"), + json = "{\"caf\\u00E9\":\"coffee\",\"r\\u00E9sum\\u00E9\":\"CV\",\"\\u00FCber\":\"super\"}" + ) + + case class MenuItem(name: String, price: Double) + case class Menu(items: List[MenuItem]) + case class Restaurant(menu: Menu) + + implicit val menuItemWriter: JsonWriter[MenuItem] = JsonWriter.obj[MenuItem] + .addField("name")(_.name) + .addField("price")(_.price) + + implicit val menuWriter: JsonWriter[Menu] = JsonWriter.obj[Menu] + .addField("items")(_.items) + + implicit val restaurantWriter: JsonWriter[Restaurant] = JsonWriter.obj[Restaurant] + .addField("menu")(_.menu) + + configurableTestCase( + description = "unicode characters in nested objects", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Restaurant(Menu(List( + MenuItem("Café au lait", 3.50), + MenuItem("Crème brûlée", 5.75) + ))), + json = "{\"menu\":{\"items\":[{\"name\":\"Caf\\u00E9 au lait\",\"price\":3.5},{\"name\":\"Cr\\u00E8me br\\u00FBl\\u00E9e\",\"price\":5.75}]}}" + ) + + configurableTestCase( + description = "mixed ASCII and unicode in arrays", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = List("hello", "世界", "こんにちは", "안녕하세요"), + json = "[\"hello\",\"\\u4E16\\u754C\",\"\\u3053\\u3093\\u306B\\u3061\\u306F\",\"\\uC548\\uB155\\uD558\\uC138\\uC694\"]" + ) + + configurableTestCase( + description = "control characters that should always be escaped", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Control chars: \u0000\u0001\u0008\u0009\u000A\u000C\u000D\u001F", + json = "\"Control chars: \\u0000\\u0001\\b\\t\\n\\f\\r\\u001F\"" + ) + + configurableTestCase( + description = "unicode characters with escapeUnicode set to false", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + case class SurrogatePairsTest(text: String, list: List[String], keyValue: (String, String)) + + implicit val surrogatePairsTestWriter: JsonWriter[SurrogatePairsTest] = JsonWriter.obj[SurrogatePairsTest] + .addField("text")(_.text) + .addField("list")(_.list) + .addField("nested")(test => Map(test.keyValue)) + + configurableTestCase( + description = "surrogate pairs in different contexts", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = SurrogatePairsTest( + "Surrogate pairs: 𝄞 𝌆 𝓐", + List("𝄞", "𝌆", "𝓐"), + ("key𝄞", "value𝌆") + ), + json = "{\"text\":\"Surrogate pairs: \\uD834\\uDD1E \\uD834\\uDF06 \\uD835\\uDCD0\",\"list\":[\"\\uD834\\uDD1E\",\"\\uD834\\uDF06\",\"\\uD835\\uDCD0\"],\"nested\":{\"key\\uD834\\uDD1E\":\"value\\uD834\\uDF06\"}}" + ) + } + +} diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala new file mode 100644 index 00000000..9fbe9a0b --- /dev/null +++ b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterSpec.scala @@ -0,0 +1,6 @@ +package tethys.jackson + +import tethys._ +import tethys.writers.tokens.{TokenWriterSpec, TokenWriterProducer} + +class JacksonTokenWriterSpec extends TokenWriterSpec diff --git a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala b/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala deleted file mode 100644 index 290a05d6..00000000 --- a/modules/backend/jackson/jackson-backend/src/test/scala/tethys/jackson/JacksonTokenWriterTest.scala +++ /dev/null @@ -1,105 +0,0 @@ -package tethys.jackson - -import java.io.StringWriter - -import org.scalatest.matchers.should.Matchers -import org.scalatest.flatspec.AnyFlatSpec -import tethys._ -import tethys.writers.tokens.TokenWriter - -class JacksonTokenWriterTest extends AnyFlatSpec with Matchers { - - def iterate(fun: (TokenWriter) => Unit): String = { - val sw = new StringWriter() - val tokenWriter = sw.toTokenWriter - fun(tokenWriter) - tokenWriter.close() - sw.toString - } - - behavior of "JacksonTokenWriter" - - it should "write String value" in { - iterate(_.writeString("string")) shouldBe """"string"""" - } - - it should "write Byte value" in { - iterate(_.writeNumber(1: Byte)) shouldBe """1""" - } - - it should "write Short value" in { - iterate(_.writeNumber(1: Short)) shouldBe """1""" - } - - it should "write Int value" in { - iterate(_.writeNumber(1: Int)) shouldBe """1""" - } - - it should "write Long value" in { - iterate(_.writeNumber(1: Long)) shouldBe """1""" - } - - it should "write Float value" in { - iterate(_.writeNumber(1: Float)) shouldBe """1.0""" - } - - it should "write Double value" in { - iterate(_.writeNumber(1: Double)) shouldBe """1.0""" - } - - it should "write BitInt value" in { - iterate(_.writeNumber(1: BigInt)) shouldBe """1""" - } - - it should "write BigDecimal value" in { - iterate(_.writeNumber(1: BigDecimal)) shouldBe """1""" - } - - it should "write true value" in { - iterate(_.writeBoolean(true)) shouldBe """true""" - } - - it should "write false value" in { - iterate(_.writeBoolean(false)) shouldBe """false""" - } - - it should "write null value" in { - iterate(_.writeNull()) shouldBe """null""" - } - - it should "write object structure" in { - iterate(_.writeObjectStart().writeObjectEnd()) shouldBe """{}""" - } - - it should "write array structure" in { - iterate(_.writeArrayStart().writeArrayEnd()) shouldBe """[]""" - } - - it should "write raw json" in { - iterate( - _.writeRawJson("""{"some" : "raw json"}""") - ) shouldBe """{"some" : "raw json"}""" - } - - it should "write complex object structure" in { - iterate { - _.writeObjectStart() - .writeFieldName("a") - .writeNumber(1) - .writeFieldName("b") - .writeArrayStart() - .writeString("s") - .writeRawJson("false") - .writeBoolean(true) - .writeObjectStart() - .writeFieldName("a") - .writeNull() - .writeObjectEnd() - .writeArrayEnd() - .writeFieldName("c") - .writeRawJson("""{"some" : "raw json"}""") - .writeObjectEnd() - } shouldBe """{"a":1,"b":["s",false,true,{"a":null}],"c":{"some" : "raw json"}}""" - } - -} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala new file mode 100644 index 00000000..f0e22dcd --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/DefaultJsonWriterSpec.scala @@ -0,0 +1,79 @@ +package tethys.writers.tokens + +import tethys._ + +class DefaultJsonWriterSpec extends JsonWriterSpec { + { + import TestModels._ + + configurableTestCase( + description = "unicode characters in object keys", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Map("café" -> "coffee", "résumé" -> "CV", "über" -> "super"), + json = "{\"caf\\u00e9\":\"coffee\",\"r\\u00e9sum\\u00e9\":\"CV\",\"\\u00fcber\":\"super\"}" + ) + + case class MenuItem(name: String, price: Double) + case class Menu(items: List[MenuItem]) + case class Restaurant(menu: Menu) + + implicit val menuItemWriter: JsonWriter[MenuItem] = JsonWriter.obj[MenuItem] + .addField("name")(_.name) + .addField("price")(_.price) + + implicit val menuWriter: JsonWriter[Menu] = JsonWriter.obj[Menu] + .addField("items")(_.items) + + implicit val restaurantWriter: JsonWriter[Restaurant] = JsonWriter.obj[Restaurant] + .addField("menu")(_.menu) + + configurableTestCase( + description = "unicode characters in nested objects", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = Restaurant(Menu(List( + MenuItem("Café au lait", 3.50), + MenuItem("Crème brûlée", 5.75) + ))), + json = "{\"menu\":{\"items\":[{\"name\":\"Caf\\u00e9 au lait\",\"price\":3.5},{\"name\":\"Cr\\u00e8me br\\u00fbl\\u00e9e\",\"price\":5.75}]}}" + ) + + configurableTestCase( + description = "mixed ASCII and unicode in arrays", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = List("hello", "世界", "こんにちは", "안녕하세요"), + json = "[\"hello\",\"\\u4e16\\u754c\",\"\\u3053\\u3093\\u306b\\u3061\\u306f\",\"\\uc548\\ub155\\ud558\\uc138\\uc694\"]" + ) + + configurableTestCase( + description = "control characters that should always be escaped", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Control chars: \u0000\u0001\u0008\u0009\u000A\u000C\u000D\u001F", + json = "\"Control chars: \\u0000\\u0001\\b\\t\\n\\f\\r\\u001f\"" + ) + + configurableTestCase( + description = "unicode characters with escapeUnicode set to false", + config = TokenWriterConfig.default.withEscapeUnicode(false), + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + case class SurrogatePairsTest(text: String, list: List[String], keyValue: (String, String)) + + implicit val surrogatePairsTestWriter: JsonWriter[SurrogatePairsTest] = JsonWriter.obj[SurrogatePairsTest] + .addField("text")(_.text) + .addField("list")(_.list) + .addField("nested")(test => Map(test.keyValue)) + + configurableTestCase( + description = "surrogate pairs in different contexts", + config = TokenWriterConfig.default.withEscapeUnicode(true), + value = SurrogatePairsTest( + "Surrogate pairs: 𝄞 𝌆 𝓐", + List("𝄞", "𝌆", "𝓐"), + ("key𝄞", "value𝌆") + ), + json = "{\"text\":\"Surrogate pairs: \\ud834\\udd1e \\ud834\\udf06 \\ud835\\udcd0\",\"list\":[\"\\ud834\\udd1e\",\"\\ud834\\udf06\",\"\\ud835\\udcd0\"],\"nested\":{\"key\\ud834\\udd1e\":\"value\\ud834\\udf06\"}}" + ) + } +} \ No newline at end of file diff --git a/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala new file mode 100644 index 00000000..e8a50bc2 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/DefaultTokenWriterSpec.scala @@ -0,0 +1,3 @@ +package tethys.writers.tokens + +class DefaultTokenWriterSpec extends TokenWriterSpec diff --git a/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala new file mode 100644 index 00000000..23116868 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/JsonWriterSpec.scala @@ -0,0 +1,293 @@ +package tethys.writers.tokens + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import tethys._ + +abstract class JsonWriterSpec(implicit producer: TokenWriterProducer) + extends AnyFlatSpec + with Matchers { + + import TestModels._ + import java.time.{LocalDate, LocalDateTime, Duration} + import java.util.UUID + + def testCase[A: JsonWriter]( + value: A, + json: String + )(implicit + config: TokenWriterConfig, + pos: org.scalactic.source.Position + ): Unit = + it should s"write $value as $json" in { + value.asJson.filterNot(Set('\n', '\t', ' ')) shouldBe json.filterNot( + Set('\n', '\t', ' ') + ) + } + + def configurableTestCase[A: JsonWriter]( + description: String, + config: TokenWriterConfig, + value: A, + json: String + )(implicit + pos: org.scalactic.source.Position + ): Unit = + it should description in { + implicit val cfg: TokenWriterConfig = config + value.asJson shouldBe json + } + + testCase( + value = 2, + json = "2" + ) + + testCase( + value = 1L, + json = "1" + ) + + testCase( + value = 1.0, + json = "1.0" + ) + + testCase( + value = "мама", + json = """"мама"""" + ) + + testCase( + value = true, + json = "true" + ) + + testCase( + value = false, + json = "false" + ) + + testCase( + value = null, + json = "null" + ) + + testCase( + value = List(1, 2, 3), + json = "[1,2,3]" + ) + + testCase( + value = Map("a" -> 1, "b" -> 2), + json = """{"a":1,"b":2}""" + ) + + testCase( + value = Option(1), + json = "1" + ) + + testCase( + value = Option.empty[Int], + json = "null" + ) + + testCase( + value = Person("John Doe", 30, Some("john@example.com")), + json = """ + { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }""" + ) + + testCase( + value = Person("Jane Doe", 25, None), + json = """ + { + "name": "Jane Doe", + "age": 25 + }""" + ) + + val address = Address("123 Main St", "New York", "USA", Some("10001")) + val person = Person("John Doe", 30, Some("john@example.com")) + + testCase( + value = + Employee(1L, person, address, "Engineering", BigDecimal("100000.00")), + json = """ + { + "id": 1, + "person": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "address": { + "street": "123 Main St", + "city": "New York", + "country": "USA", + "postalCode": "10001" + }, + "department": "Engineering", + "salary": 100000.00 + }""" + ) + + testCase( + value = Department( + "Engineering", + List( + Employee(1L, person, address, "Engineering", BigDecimal("100000.00")) + ), + Set("tech", "development"), + Map("location" -> "Floor 3", "manager" -> "Jane Smith") + ), + json = """ + { + "name": "Engineering", + "employees": [{ + "id": 1, + "person": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "address": { + "street": "123 Main St", + "city": "New York", + "country": "USA", + "postalCode": "10001" + }, + "department": "Engineering", + "salary": 100000.00 + }], + "tags": ["tech", "development"], + "metadata": { + "location": "Floor 3", + "manager": "Jane Smith" + } + }""" + ) + + testCase( + value = Container("123", person, 1234567890L), + json = """ + { + "id": "123", + "data": { + "name": "John Doe", + "age": 30, + "email": "john@example.com" + }, + "timestamp": 1234567890 + }""" + ) + + testCase( + value = CustomTypes( + UUID.fromString("550e8400-e29b-41d4-a716-446655440000"), + LocalDate.of(2023, 1, 1), + LocalDateTime.of(2023, 1, 1, 12, 0), + BigInt("123456789"), + BigDecimal("123456.789"), + Duration.ofHours(24) + ), + json = + """{"uuid":"550e8400-e29b-41d4-a716-446655440000","date":"2023-01-01","datetime":"2023-01-01T12:00","bigInt":123456789,"bigDecimal":123456.789,"duration":"PT24H"}""" + ) + + testCase( + value = Department("Empty", List(), Set(), Map()), + json = """{"name":"Empty","employees":[],"tags":[],"metadata":{}}""" + ) + + testCase( + value = Container("empty", List[String](), 0L), + json = """{"id":"empty","data":[],"timestamp":0}""" + ) + + testCase( + value = Department( + "Global Engineering", + List( + Employee( + 1L, + Person("John Doe", 30, Some("john@example.com")), + Address("123 Main St", "New York", "USA", Some("10001")), + "Backend", + BigDecimal("100000.00") + ), + Employee( + 2L, + Person("Jane Smith", 28, None), + Address("456 Park Ave", "Boston", "USA", None), + "Frontend", + BigDecimal("95000.50") + ) + ), + Set("global", "engineering", "tech"), + Map( + "headquarters" -> "New York", + "founded" -> "2020", + "status" -> "active" + ) + ), + json = """ + |{ + | "name": "Global Engineering", + | "employees": [ + | { + | "id": 1, + | "person": { + | "name": "John Doe", + | "age": 30, + | "email": "john@example.com" + | }, + | "address": { + | "street": "123 Main St", + | "city": "New York", + | "country": "USA", + | "postalCode": "10001" + | }, + | "department": "Backend", + | "salary": 100000.00 + | }, + | { + | "id": 2, + | "person": { + | "name": "Jane Smith", + | "age": 28 + | }, + | "address": { + | "street": "456 Park Ave", + | "city": "Boston", + | "country": "USA" + | }, + | "department": "Frontend", + | "salary": 95000.50 + | } + | ], + | "tags": ["global", "engineering", "tech"], + | "metadata": { + | "headquarters": "New York", + | "founded": "2020", + | "status": "active" + | } + |} + |""".stripMargin + ) + + testCase( + value = "Unicode: \u00a9 \u20ac \u2603", + json = "\"Unicode: © € ☃\"" + ) + + testCase( + value = "Emoji: 😀 🚀 🌍", + json = "\"Emoji: 😀 🚀 🌍\"" + ) +} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala b/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala new file mode 100644 index 00000000..29d31114 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/TestModels.scala @@ -0,0 +1,120 @@ +package tethys.writers.tokens + +import tethys.JsonWriter + +import java.time.{Duration, LocalDate, LocalDateTime} +import java.util.UUID + +object TestModels { + + implicit val uuidWriter: JsonWriter[UUID] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val localDateWriter: JsonWriter[LocalDate] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val localDateTimeWriter: JsonWriter[LocalDateTime] = + JsonWriter.stringWriter.contramap(_.toString) + + implicit val durationWriter: JsonWriter[Duration] = + JsonWriter.stringWriter.contramap(_.toString) + + case class Person(name: String, age: Int, email: Option[String]) + + object Person { + implicit val personWriter: JsonWriter[Person] = JsonWriter + .obj[Person] + .addField("name")(_.name) + .addField("age")(_.age) + .addField("email")(_.email) + } + case class Address( + street: String, + city: String, + country: String, + postalCode: Option[String] + ) + + object Address { + implicit val addressWriter: JsonWriter[Address] = JsonWriter + .obj[Address] + .addField("street")(_.street) + .addField("city")(_.city) + .addField("country")(_.country) + .addField("postalCode")(_.postalCode) + } + + case class Employee( + id: Long, + person: Person, + address: Address, + department: String, + salary: BigDecimal + ) + + object Employee { + implicit val employeeWriter: JsonWriter[Employee] = JsonWriter + .obj[Employee] + .addField("id")(_.id) + .addField("person")(_.person) + .addField("address")(_.address) + .addField("department")(_.department) + .addField("salary")(_.salary) + } + + case class Department( + name: String, + employees: List[Employee], + tags: Set[String], + metadata: Map[String, String] + ) + + object Department { + implicit val departmentWriter: JsonWriter[Department] = JsonWriter + .obj[Department] + .addField("name")(_.name) + .addField("employees")(_.employees) + .addField("tags")(_.tags) + .addField("metadata")(_.metadata) + } + + case class Container[T]( + id: String, + data: T, + timestamp: Long + ) + + object Container { + implicit def containerWriter[T: JsonWriter]: JsonWriter[Container[T]] = + JsonWriter + .obj[Container[T]] + .addField("id")(_.id) + .addField("data")(_.data) + .addField("timestamp")(_.timestamp) + } + + case class TreeNode( + value: String, + children: List[TreeNode] + ) + + case class CustomTypes( + uuid: java.util.UUID, + date: java.time.LocalDate, + datetime: java.time.LocalDateTime, + bigInt: BigInt, + bigDecimal: BigDecimal, + duration: java.time.Duration + ) + + object CustomTypes { + implicit val customTypesWriter: JsonWriter[CustomTypes] = JsonWriter + .obj[CustomTypes] + .addField("uuid")(_.uuid) + .addField("date")(_.date) + .addField("datetime")(_.datetime) + .addField("bigInt")(_.bigInt) + .addField("bigDecimal")(_.bigDecimal) + .addField("duration")(_.duration) + } +} diff --git a/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala b/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala new file mode 100644 index 00000000..cdad18b6 --- /dev/null +++ b/modules/core/src/test/scala/tethys/writers/tokens/TokenWriterSpec.scala @@ -0,0 +1,246 @@ +package tethys.writers.tokens + +import org.scalatest.flatspec.AnyFlatSpecLike +import org.scalatest.matchers.should.Matchers + +abstract class TokenWriterSpec(implicit producer: TokenWriterProducer) + extends AnyFlatSpecLike + with Matchers { + + behavior of "TokenWriter" + + def testCase( + json: String, + iterate: TokenWriter => Unit + ): Unit = { + it should s"write $json" in { + val writer = producer.produce(TokenWriterConfig.default) + iterate(writer) + writer.flush() + writer.result() shouldBe json + } + } + + testCase( + json = """true""", + iterate = _.writeBoolean(true) + ) + + testCase( + json = """false""", + iterate = _.writeBoolean(false) + ) + + testCase( + json = """null""", + iterate = _.writeNull() + ) + + testCase( + json = """"hello"""", + iterate = _.writeString("hello") + ) + + testCase( + json = """"special chars: \"\\\n\r\t\b\f"""", + iterate = _.writeString("special chars: \"\\\n\r\t\b\f") + ) + + testCase( + json = """42""", + iterate = _.writeNumber(42) + ) + + testCase( + json = """42.5""", + iterate = _.writeNumber(42.5) + ) + + testCase( + json = """9223372036854775807""", + iterate = _.writeNumber(Long.MaxValue) + ) + + testCase( + json = """3.141592653589793""", + iterate = _.writeNumber(Math.PI) + ) + + testCase( + json = """1.23456789E10""", + iterate = _.writeNumber(1.23456789e+10) + ) + + testCase( + json = """{}""", + iterate = _.writeObjectStart().writeObjectEnd() + ) + + testCase( + json = """[]""", + iterate = _.writeArrayStart().writeArrayEnd() + ) + + testCase( + json = """{"name":"John","age":30}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("age") + writer.writeNumber(30) + writer.writeObjectEnd() + } + ) + + testCase( + json = """[1,2,3,4,5]""", + iterate = writer => { + writer.writeArrayStart() + for (i <- 1 to 5) writer.writeNumber(i) + writer.writeArrayEnd() + } + ) + + testCase( + json = """[1,"two",true,null,3.14]""", + iterate = writer => { + writer.writeArrayStart() + writer.writeNumber(1) + writer.writeString("two") + writer.writeBoolean(true) + writer.writeNull() + writer.writeNumber(3.14) + writer.writeArrayEnd() + } + ) + + testCase( + json = + """{"person":{"name":"John","age":30,"address":{"city":"New York","zip":"10001"}}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("person") + writer.writeObjectStart() + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("age") + writer.writeNumber(30) + writer.writeFieldName("address") + writer.writeObjectStart() + writer.writeFieldName("city") + writer.writeString("New York") + writer.writeFieldName("zip") + writer.writeString("10001") + writer.writeObjectEnd() + writer.writeObjectEnd() + writer.writeObjectEnd() + } + ) + + testCase( + json = """[{"id":1,"value":"first"},{"id":2,"value":"second"}]""", + iterate = writer => { + writer.writeArrayStart() + for ((id, value) <- List((1, "first"), (2, "second"))) { + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(id) + writer.writeFieldName("value") + writer.writeString(value) + writer.writeObjectEnd() + } + writer.writeArrayEnd() + } + ) + + testCase( + json = + """{"data":{"users":[{"id":1,"name":"John","active":true,"scores":[10,20,30]},{"id":2,"name":"Jane","active":false,"scores":[15,25,35]}],"metadata":{"count":2,"version":"1.0"}}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("data") + writer.writeObjectStart() + + writer.writeFieldName("users") + writer.writeArrayStart() + + // First user + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(1) + writer.writeFieldName("name") + writer.writeString("John") + writer.writeFieldName("active") + writer.writeBoolean(true) + writer.writeFieldName("scores") + writer.writeArrayStart() + writer.writeNumber(10) + writer.writeNumber(20) + writer.writeNumber(30) + writer.writeArrayEnd() + writer.writeObjectEnd() + + writer.writeObjectStart() + writer.writeFieldName("id") + writer.writeNumber(2) + writer.writeFieldName("name") + writer.writeString("Jane") + writer.writeFieldName("active") + writer.writeBoolean(false) + writer.writeFieldName("scores") + writer.writeArrayStart() + writer.writeNumber(15) + writer.writeNumber(25) + writer.writeNumber(35) + writer.writeArrayEnd() + writer.writeObjectEnd() + + writer.writeArrayEnd() + + writer.writeFieldName("metadata") + writer.writeObjectStart() + writer.writeFieldName("count") + writer.writeNumber(2) + writer.writeFieldName("version") + writer.writeString("1.0") + writer.writeObjectEnd() + + writer.writeObjectEnd() + writer.writeObjectEnd() + } + ) + + testCase( + json = """{"raw":{"some":"json"}}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("raw") + writer.writeRawJson("""{"some":"json"}""") + writer.writeObjectEnd() + } + ) + + testCase( + json = """""""", + iterate = _.writeString("") + ) + + testCase( + json = + """{"empty_string":"","empty_array":[],"empty_object":{},"null_value":null}""", + iterate = writer => { + writer.writeObjectStart() + writer.writeFieldName("empty_string") + writer.writeString("") + writer.writeFieldName("empty_array") + writer.writeArrayStart().writeArrayEnd() + writer.writeFieldName("empty_object") + writer.writeObjectStart().writeObjectEnd() + writer.writeFieldName("null_value") + writer.writeNull() + writer.writeObjectEnd() + } + ) + +} From 480e424e5ab5b4264700b5ab250fabf3c20653f7 Mon Sep 17 00:00:00 2001 From: Georgii Kovalev Date: Wed, 26 Mar 2025 15:48:08 +0530 Subject: [PATCH 4/4] add DefaultTokenWriter; benchmarks --- modules/benchmarks/README.md | 14 +- .../benchmarks/images/WritingPerformance.png | Bin 26041 -> 27914 bytes modules/benchmarks/jmh-writer.json | 1820 +++++++++++------ .../json/bench/tethysjson/TethysBench.scala | 14 +- .../json/bench/tethysjson/TethysBench.scala | 21 +- .../main/scala/json/bench/BenchMarkdown.scala | 98 +- .../main/scala/json/bench/DataReader.scala | 3 + .../main/scala/json/bench/DataWriter.scala | 3 + .../scala/json/bench/JmhReaderBench.scala | 46 +- .../scala/json/bench/JmhWriterBench.scala | 48 +- .../scala/json/bench/circe/CirceBench.scala | 6 +- .../scala/json/bench/json4s/Json4sBench.scala | 12 +- .../json/bench/jsoniter/JsoniterBench.scala | 17 + .../main/scala/json/bench/model/Data.scala | 22 +- .../scala/json/bench/play/PlayBench.scala | 3 +- .../scala/json/bench/spray/SprayBench.scala | 3 +- .../tethys/writers/tokens/TokenWriter.scala | 1 - 17 files changed, 1394 insertions(+), 737 deletions(-) create mode 100644 modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala diff --git a/modules/benchmarks/README.md b/modules/benchmarks/README.md index 5bb18392..d4042824 100644 --- a/modules/benchmarks/README.md +++ b/modules/benchmarks/README.md @@ -20,12 +20,14 @@ zio-json|2001448.661|255068.591|2001.826|237.499|3.439 name \ size|128b|1kb|128kb|1mb|32mb ---|---|---|---|---|--- -tethys-jackson|1819887.866|403267.403|3265.018|300.502|11.786 -pure-jackson|1109155.693|427421.925|3096.09|392.062|10.441 -circe|1348593.267|177497.011|1255.093|157.518|2.706 -play-json|327191.603|47736.655|360.734|49.417|1.064 -spray-json|1172370.195|148580.554|1053.782|142.164|3.425 -zio-json|2191564.981|266636.44|2092.48|239.598|7.347 +tethys|5623657.356|549741.974|4728.468|582.912|16.223 +tethys-jackson|1778419.803|434317.737|3577.799|499.213|11.343 +pure-jackson|1153651.397|420722.96|3177.921|394.1|9.972 +circe|1393264.287|176604.569|1279.973|156.184|2.192 +jsoniter|6036735.563|892874.569|5576.712|727.844|19.491 +play-json|329380.553|48283.895|375.691|49.423|1.065 +spray-json|1167053.231|149003.266|1109.504|138.309|2.95 +zio-json|2034331.785|277147.012|1934.274|238.862|7.332 ![WritingPerformance](./images/WritingPerformance.png) diff --git a/modules/benchmarks/images/WritingPerformance.png b/modules/benchmarks/images/WritingPerformance.png index a9da918da9dab298c89374a2d0d2db8ccc64a1ef..d4dfbe885d632c84ce1c9c0eafa390fcea4dc677 100644 GIT binary patch literal 27914 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

QZpy+Wq0SOL{XE?r~Uuzy@;j!+8M1CMGfp+Ajdf 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 z z@k_11*2Bm5dj$n29un|z*=11qDW$x;{I`~%;7d_w1HqSvfE7u1SJ#`>E-p*Lc@DZf z$pQ}Q0+-_+0}cQmJ$7u`lqnvCz{QuqxVZ#eSMPa6Ls!@JV$w$7;STA*2_)dcMbp`5 zb*+K<=K2w!`RmlItZrStY^<-J4_w3oOc6m>>SU6DW3th?z@zhc*_?qjbr-OR{PyXS z(M%s;hyByXk1JQN4z^YS8gLkFfQrhI!-p3yTC`~A&PdVvzdi+tiHbr6IXODJfTikU z;8{#rSAcDShHblAeOz4hIu&Ph#GD0YWp*Bk1_fymk&J^)t(1*Cok z-kh^+*|G~40!mJtJ`Eh!k8*Woef90Ps*;j8DCpOL7AONVl&J&{aGbqkb5KuzKR*|j z(_`Sc^}n^LDJdSddV1$BUl#r;;SU;xPMhumj0}mUPGBwOQBE!TAM@H~{YAE>t4@Gg N{hqFVF6*2UngHsGKsf*a literal 26041 zcmch=2RzmL{|By|=p@vUk*$m(sqAE=VN*uPOas~3J4dCGQ3zQnkqBAI>{L{;va>U@ z_g??k=N#Ji-tX`Ke?0#8aUb`(Ip;Ip@7MeFdcK~o=j(NEol=$|-$K8Ih=_<>PWHGe z5fO1R5z&Ug%^TrQI6Pd2h=?v;l{^q2 zT2@-5^X9f)ejoN+;koju`#uFZ$*cA&{7|mU`D+swYRPquX-BSw_9qD zx-{sAYA@;ywzgMJx*r)`c3W@V_L;q0RcxHJ59^T4CbO@tpWkYcvtf7(SM zdjS8#O%YZc{s-31t0-dRr>i%K|9|@syYbr_j8{?<3Puj%}xD7z9kDd;`R1!qR1x6@ASbQkLPHB;@q9&vjl-4>s`ljutLje!9;~ zyI=Q11YCj@gDip@Q8?i*s@+?tx%YH+ydY=KI$v_YVXxz@+6zvQPJ0=onNOYBw z^)ZKS!F0Wjsp1X!u6rB`)kR5Xo3bk(cJdCL&2UzO!$9pV8Xrm)Wor68EDODQXS607n8jVT(hKa-I?bp# z8)T@5_P=$@dGPtmT*pynd+oxE^mJM0kj16~SH~C$cayDX5+b6QjquMo@80r=AUg`C z!&9&Nq8FRnSAExX#i5MK5ALJ+ zlNGRaf!QPpFG5|?xNxP9jB;1f9v&h`%(jpOu^!SGj|EfpmDtzUw@^Pg zba9IYx5l&M%#2i2HL=HhIs}3+M?99C+ECFb9vb}@Mkg*d!9KHr@pKpb5=MAo*=tzLaF}7ae@~$4E2(l78;(4fmmcSPsSK)lUKSyo zYFrt;yg1ul;3_P3v>+@hO6hzLyXf`W+S=~!?kq5zvza&9b6^p18erURD0Vs`qOxi+ zmV1VWM!_!bp&W;sG{oT0U1dS+{;Kw2yKfJdjCUTtyXyp)k~5_B*N??M;-?L%D{gN@6QZrWYYwsu<)qxf*aBINeJHUsx~XGdK)LY ztt|dluT4~o5Huq}Q!qF?O-#`J<*GcHmaX&{Mximm`#;_u-jUNMuzAuS|hoZ_GWefsbUEvZGouEu_Eg1!mWf*}i{W zCMoPu+y=D&kx1FG?tZbX#V>AGTufQESo_0r(#6OhI6ycgr>t}MaPK|H?nGGe+ulEh zC1d{MCEvQER|a&R%Z67by=B8~qGZ?NX2tCXyM*|y=$i(o)ro60xFw3zcdTbX?S3_KYZ?Eb%;Zp1q~ zKiY3W{NIPcWn%5P!rA=!VbiW{UZh_;0{>_sgtG}IT_po&=#B~p9o}jF`lkPGu75xD z=D#Q$Ml$RVv3TH8E=liF;9oOHr&i{c%Hf^76tF1|He`R#lu+)PV-@gEjK*vK*1 zSa-yOg7J85_-7B)cyp3=Ba_dU%5d8za(RkBO(1Q1Uxk@LHgC_A_Am(t=`TL)03Qx8 ztt+Y1jn9u|%;-^Qp*L_c|JBm{g6Osh>20`V$Uut!bmYIUNV3BC=7UMrChpi@75;BFSPoG-0=I-@TkK*1ZVhk)?pQVV+Xm>y%sSvY7L&a5#zR%hFWioo5T5~TY@$_=${%ClMk#a zxvI{Nk1-o}*UDj)Mm)16JUTB0EgB?5*C@=6qeKFz?D?lxFZk6UUpF2MxGKmzMY=Y(%ymg_d0xej4Jtfw5hw%mWX=;Q=np0tNekiv>@Y8G+i zaQYC)cA=oab@mL6nEk+U-wOo;qGAq1Pc@Rxs7p{D8I!tn>5_Gw7`OV%&Fxmy5@0A2 z5YEQr($qVG3~66;;U;joI|9=Bw;UiwE+?{g@2%QwnE>WeqdbRUlY<-_C&c^4-?OBi zij_Wl_H2ZZ#iPfMlQdJKgMuDM2PVwQrOj~Jl0v-g^)sLQ=SX!<@3u^%-KH=vi^x1o z{*@m2^fnsavwM7}2kLg0mw))MSy`*dLtN=w%d3p^xrtAFHJy;JVX1cfH;pc^b5 zNS4ZD6XN%b0+7aT-qjM)k-ucyYIOd*BqTKUgY|ljt@dEw37tAt1saeM0Mxf=%YSA4 z^^KreeM`0lL!C~xxptiF_UY~0w*(q0bl>`mFNKAoJg=vnF1MI$B2lAMa0$y}$k3OI z@?7XoaBOoneqmIcQZUm5HntZV5f(;AP5neyD2!R{h1-V^Us}G3mCA}z#_~sN3-+)6 zN-MPUTry8!$APy(753;eTIm-)LK2=?_i3o{#d$a;WVHKhb;Wr%ubqQmy!LDSV|t-{ zc4ph0yJzSlg^{e&$4(i-8}Bt;sRx%U2jCY!#jDvU9p=WAJBhQc4QC z15!FYr>@SLVX|%3Ul0#ty-=>?vS|$n7RSFtu{c`7eZj6FMQ=23v@igYXINgZr5h9s zLgwS0#Y1OLoTOwr91MR&M=Mhm&i6)K{B(cQ%adKJE3-ucT7c67*^bfd*QL{a@--pG z<7}auD7kXgBD2sHAi2@S@u-m! zrFlz^4IDQOZc+-yhwl?TUUQyX3jq6m!=?lbz6NdakI!L_iM)H)rc!*iELwcAKOu&h z|I$_-T;|Lzu4qNf)AH#9wG%69+glcX*#6nf z%Ki-J$zFQF%T=s`g7)1X?%D;w4r!D{FdtJ?+=?E#vy=Z!itf%+a$~UCjYXcG^l6TF z!^5>>rLTVs;wqYLa}{_<`ncuP^lfi-H>Q~)j4}BqMDzc z-hDASXY}h^pV0k!@*_#wNg_Sr{Kh7V#o8H{K0kzG`__jt0T*Fan7H+W!C*tG=hB=_ z&lwbY>Krw&NSo!Y1K+)S2ZT@ra1{a%U$}(q!ewgbrYeN)-!wJy2+sIu(Q4r#Wm(x% z*$)@5mcJJI*_?+2S#kQ1mayP0DI`tKI_q-X2X~fF2`7AM9%K!iw*_Z+*tS=ZKG~qC zP(Fm4{Q+$L@>d_0jq@@hnO5!k^8%XTN1VnCFGg&UTwSs~g`FMxQWGafmEH^qKLt=d zfva=^+48eG3CdAPsf8}>h3@j#3&?Wo;3x-=gQfL<-79>7B@H4poTIBK9YP*dK*HW= z1}T%b{a)sh+|F#2uzBODox&znhHtK+^Rsf5o%^ewG+Pykv$4g(uHkgH1JSf!ExrtL z?%HD3qiCQGvabk?4W<^Q$Q}^C;jz4s$i*`o5gDlqk-lTR(2HmEk$!sV^Pv}}bP2hvH7$GH4KmT3*=iJf(3&dPF>65QO} zyu3=?tiGi8B&sD;*VLRhGAapR zQPG*3>k1&|4rMxQX9D5cV|CeaX8(t{B=tn;kon2Jnt;Gi&qB`84L;W_7}*;)!wE>E zfV7itCOcEKJBfTi4l<*H&a34vov=_zzP9^9Jx8`Xkjxk%yuAhQG+!-NhAtYx$>w7HL zU8iZFp?r(~ZGhxTj_2yi>BlGczGrl$`Y#Xm3F8LlFw&YA)O1-F{(8XA)leJ49>sDvRYZzjoHH@bBmRzUAx;OLKHQrCT%$(JO`>IGWeQwDzsv z7ytWnLh++MMNda`n-LUQ$Y}QEjN}etX??A!Wz!@HsOx9j+pIVK9K|AftwVfRws<2f`bJtTXG)D z1aMRLczv#as_fmN*bJM%inHrtu*`{tkHaY#Xv#ACl74YqvSg=BFzP=&Et_4j5}I>5 ze=sHQfsloK3Q~Sij#(+1heCjSvejGVRu|dUK!5} zeNh3HJnZ0k^@t_k@(AIEr>DJ15)%`_(Z+p5;8I+;B{LP(F~`GDv+9_kMth7G(}GQ_ zcmsaqigq%B6Rwt^tRWPJM^2m4#{f44kQjI_XI(kw!Y&ZRo_G8f1`_qq(RNPru6?p& z5Xe^mXp*J|IFt)ERIOAiUm=xE=#hu976F@_`M z)6aieOFb*@Ha7;f7eKla&ln*^OuG$P1d2mM*2t(6VxW>Ypr|UX0{Od!)t&XvbqsRt zZgC5w7!<9HcfI$vr57?s%1S(4*RG*1lHEqdh}zyL@uL?6sB^};P#ihShFAf&>Nq*5 zA`JrCb)vX-3S6FaV}sT&ybhksUE`q<|5!yG$|2&!ZVB0C2)D*YH}n~n44vm^1+TQf zpD_LWXp;sBC3EsV?xm&4%A@GE3}clbPNff)X7$OUCRMCGz4SUyh8q&q`Ua9x+g@75 zsERsITmb6|p}Z5arS&pEtx_k*nUo@gG%V@d4AsL$ZQ~DRlOyLT-);Vi%LQCkEex%?DN zr8RZ$cL1ut>w+LZc<0-r`}K0C{KZCCBg`Aq&Vfg@;}GHJ4{EW4(*{1j)7khGmc`{8 z+m(7q0a`p)m!U#-#C|~C#l=NYv21Rg7c^vd=Y=>H$eBPkv-Gm4@R6 zDbewy4{>s(iHAH_+2sFHtjn2BsAoX?Sh|IdLt@} z+cR})h%pFUz%vfpay!#eFHJsUSB9&fd>W`r3h8{J5Gt`W_BLAFb!D-AHTvNB5?TYd zqs38Tj+ghN?nXp3=Q)lx=)3v_tLstTgS=<9)v=}nP;PuUKv@ZwDH9{3M^|1Sb8v77 zU=|1Ji2MOf&Xq-hpg6bp{!CVd^PFEYuFP-VbZ->UQqA+3k=7*jT4CKZL$401E?I2< z^S7_Ph*N>jf|W;(O`u^Huwf|7kjnQA84nOP>)B{yTXPPN{)E(g$QH@6ZD{tLxob$z z&c#K`b0*p3^CP&z&3lHw5WLszQ9ozOcmxAW5sykpNKknoAg5zr7HZ%Di~LE*qKQ%T z%JIC4NpO3HFOH%)@9)#T@`?AQ6xMIIJa+O|(m#3kFNchgaI%Cs=+f}t*4EYz4-dy7 zzRa{HKCwOz5W|nsPj}x-Vo8V0@QlSQWRC!m5%_r7v`s5oJLf9fEqWKTo1VWSf%NTr zIAq0b1myAkcwfoPbC8Uv4A&j5pF${s!OcG?Uqu;@82Y)1OA&086tsjBMXN$uv0}te zyHX245o-~-7hSkK(+u8H((YT;p{-4cBip<%ZrvHL8wM?UhtR|IZ-9p&AV@sO@@1=L zr@Ka*ZSys-17bLoYP~5Xm&QKa-JLZ!A*ZYiUa$?|yNs<#x`X|3`LWynUaz30H_n%( zLq>&1nh4-ivFUf~SUMf?YAnOSjFA;b1EpEva}37v*r+jZC?{oPW+Cl_^!)Vc(^du3 z_b4<0L$hpALHbL9zA-L>oELmA0YmFG<}sZERhE+ngpJ)v$MN_ z0fQrEko0h~w6wIg*medR;??`X{p`NR1;_T)IFIW$M?Hi_o>1+e4@>l%Y(3aD%urj0 zIkJ1j{7MVZpS9_qlKLzcgt}*1pZ-jMrqX26<)+*-Y@0DXXunK5$RZK@^Ga+lgplka@6MXK&o}BFJt&{oU@WTERr@E zAD2_rD*K--I&F-6pO~1W86Iw6Xdi-N81>Bd82W~*YSxsq{;*tGPpcg9thF*9e&SO| zOI+^oTUg0g`LbNryxO#I5iQhMsQSOsr)p}e3xlclgZcBD)K=XGiiuzJ&4$KRNe$<) z4fYj2TW)Z0YUX=*7%ZI?7Y&A)hdFZL!?r6eZ3V78zQA`sIx*4OaSb>NwI_D{)qoe= zyYI@FC_ZA;UG%?gcskL{^N{0MKNn6rX=Rp$Z3|Hh%Ju`*l^3x@66oGt$-C#^RD}5h z^(tX>7^{NyiogSNWjevjJDe>lqs2ptLBUYd8N62wO9fhzqaky1P8VM|3^#rL{CVfj zoj5eYPI{pRNigQTfri2e<65&__5jO4CeTA5rfYz89Bpe&*3JNgY@+#8E89GL;jP|1 zy(=w%XGplpqQpWu?FMS)wAKAXwX!a2SYv@qSEaCAJdyWm3<|Cf=RJ4{VwKwBbadWc$(0o=zH9n|Q56aVyG2K!{O~j^EKGKz5<}eh zqZ7XJfH)r=M?X*l))e@p%fe6|q5J(v`E3sB;L{|%pPdmdJ_*%)Hdwo(*ye1DML3T& z9q|#CyFxjfpPznw!{Ht?P~p3>AP!lM?QTm>PR`8C1YFPU3ojEXXhu{1l}Q(F@yDq} zZsD$w$=BYS;hIYzruWHOOG`^b2%ebWn_YxtJ%C9}h@1OzO}tW*GZcgWya*(K{Zd#9 zsE=S#Dp7~C+~y}-lNkR&HBKLAPA*|QHEu8To<+Y~B`Y(NFxMA^f9(_q6zkqr+EK=xjV6k|@DZ6m}X zQigmOlguESmL-L4C4wNPD8q_#duNBjm>&)_WWaCPmoHx+JE_#a_AjlXbrd|NfZYzs1He2jG{A zQ_*SSBiCU4bFqu1JD$Z6g6>YmND-T{4jd3rRZ(&w+jpNlGVx(INRpO8m6O_%7J#=6 zNIBdg{9YTrAR_qNA#{a$I%Mk+%Ccx?VM|@8F98*ZkgGr~-REY{0ai>YbU`~Jt;h5%Mc`I=PdHuF*i{|qI;xr5lzUQ$@aKzd0M;yuN2vH!d|5|WI z(ekhrUKLl1QCy)(&frh1hVl=P*CtR3a%{2jaS2JqKd^!8u;B1^oCs&*fk*6gTqXU; zUWgX6|7|bOdvPG7g5>5#?xXvL7azW^h?9rBKxTd)WXA=i4fu7|%e8~3R|Zd+$pcyn z^SwF8AHW7#M+f?nLuFj|8XnyIWEX54IktTjt8?$EQ+1D^FBz-$AS{<=_f_O=m6eou z0j8FQ@MICL_HzXN*-qfu0F0go%nbY141@vvO)`fmt2p8^^-X-Xr3N4~5W)4&%U7Pc zvH%QYM<72n&zbzMHy8$(A9yHiT^7ls#6M?)IIMi&*R+oh-)3ZmM zC%Qp%R!iKb*ts-62`NqGhYxehv+Z^K?)U`a2mz|djUeZii>t(4(z-*l^2m8U;|zoP zLglc4TFkz!c*9t?5C5HL5_&xC0lKmNngp|?bAS%^{`}%91S+b=3)PHYC15LU&CQdu z>1p7UZ$#IVvp1e)#aR-eXLjIWj1ZG-8u|JqBP?@G0pd4Nj}ZTdD*+6j=``L6RCn<~ z94Fw(s7N|ebbSCnn#T6F931ZxT5!A>k>gz^gY0NLToGspGfZ;5fvoihz_nbVvfik{ z_u;5M^sB~Ge|%+^BDOMyB66YlQpIv2P)7Lpv2UAf+7l_PJzNZAWDGy~n&V+8Z=~9l zj_T{~j(hUt;)M$)nmLd_s9K9$L%cv39}P(3_P;^=2P@(~AcBH~(ejN@#RF8$FrcSL z6N!+iK=zr*--1iPxQ)CnTA6)g$is6u^89f9g}-@|tu5COQ`848*v`(*+}s=@{D-TM z7bD5sFuNllS%bMDmZk=DjrQl29cC{%iT~UJ{4-z?f!{P_$n9BXq3Jn*{-GRx@zN!I z(NDl<{cY#gzV_0py%3@Ciy^QirQ}{7Zp!j3w}vDC%R2oU8s+5V01xn<&7>1D*IHd! z23_mfXX=SS!wnyyg`|5utp0r~Un}9W^@&y)7I$?~9{)Y6aUUS(BhYC7ai-T63}MiM zf`Sa3dqSXq2HRz-nFldkl@*r@QJsvvk^C3q!QU~+@H7%b{v{@C=fov>8nli`4i8;i zNuA7!AdWReqB3Y>>A|fQx;Zbs{D>H<%^!hb_ZxPnk5&qqsgL9NmqYuT$`Q&FKm>>$ zZr;Alefklk4;KL76EHQxtgg9i3id`4gls|LG9=3IUVrgMzaPrB9Gl&8V?$pu{)f*p zLsbK?lJAba*tcwoCxcO4??mv9C?O2>H%CN>rJT6S%hyppclMx5II5AzK6y>oe9zxJ z_qwB?ft2o_)<6Ed3N}G8y!x32k42q;j6Y+zh|Qzy0haX|Mc2RkMrro%EaEb#1n^Y5 zVI*AwgV*m44g#Qrb%&IT%VF^w5Jmxpp4>~qG1vVu2#Rt{LgtNJF-xPxvry90hI;#0 zw!q4$=PHBlR%t80`+No|cYb048}9Am;@|+#I`ZxFWBC)#TYV^)9vvpf@}I!1)$!nM zqX~>cG)M=5ScnJR@JGkd+`$y6f82Z4<}&!aCSlde;}jP740vl%aPUxj+pnKL2P{ni zsDX=?XLQa^Pd6B7K2z_9O?p6%i4e9*IsKS~Tvb&y+Ntvv5G0CGhc&cAek*tSp(aL0 zqFqN~!!I;LWdHE-<7q42rFAt9V8K2Dd%qsI!|rwfr?~h0l^OsqT6-^3_{b_Mx+D;zX=B>g#nGbFJ5(5qNro2`eR|7w(hFa% zH34^zo0rCmhM<-Lp`?-!K-Nw6;BMT}b|nRcB-JOkb8|q%%&GJsa1Op@TlMg$x;CgT z%Gu6A&>MpEStFNalARqgERsw)OEmq&)U2)5-m z3fRZ~nPw{$Sy_{|{CNYNSN9&Gco-$zaumZ40r4Gc1BFYd$|cvjP7h3%@}#2JgE~O( z^Eo9J$6tAbikey@-^r$DGv%5;+t2CI6@!A1Eo!9`2(%i0QP_chF^-? zecPrXo48)KI>%MfUEl%#6%QU83KD|IergHVz#8 zL(e?B{&bh=%THu`vmszht$eWpv=zHj1Vu}zglcf(@^;hg)9M(d>ROoUhpVVTWf~G&p zdNwjzA`k)^A&^7f>sw6pm*n969h6Q1`xw-=pG9KcPk^Xlq`k7o(E0$R zO~0rBB<#nl{d${tIe{7D)7Qn5n(G+4GvH=iPvw z2P-wNi7$tah0MVv@Iz#nWHufiGpIQ;eI>?#)ta~i3)c8AgT^1_CPAT2<7S3L4(!m6t$x@a9Fc1exezM@KFpv zrt3Ji(s(JTwX)atYw`>!dW~B`u-W!=7`KJ$euIitBR)_4CAB=!ZCkQ!;@L$G9((~r zKOl9PzKSR?uJDM6q$peh-~b3fyYhysVu{ItywV%K3)aRO?~K)?xVb3j0@(_pqsa=!pS+ z_WmCkTnCgX%H-1?Kt2p2gS$=K*hP@^n;PqKii(Ek&pZj$SEY$@9T7bXBL7$yWd{eL zk2a7>BkwkeN!}`8rD6KQaIoEdHE4l)SK;E9oKWRcCWi4ImybQBGV9y}oKlhjugSp+ zdb~LP&PvYrQ4g!6o}bA-ESEN3dGVB{!dT(lT&oQtySo^wxyqZdye`vQXQ{m3hv%-4 zIY>^<6vZpaR+_iXqlAkL2Z~e*oLFa;R_?(& zGYMZKW%2bzTp%##0>I^H+4Iz2TiSQ`XwdtmOoWwYBo7GzjHs5=)p8`P0%>`?|ZzT(2R zqf9t1eBK{}C!G3ZvBwTw>6V>n0%~4KdkBn|7bH-kBInA&4yv2N*#`|qwA+Ln{Y%rD zOOw6j1PTjx%O-dG9-PQQW#Ns8+Q6_N5aqi8AH@-{_=*PyiaE|yi`Lw%^u~ury}>y> zHz<-2@nSv|{%HrFek3Q5JP!&mz!?1kdR&0=00woN>;XcJ8UoX!quwN+J8D!UpM&Vd z+aWyQ4b&#`TegTymq93ukmS@_48l_pthoI3 zw8h$s7_SyUtRHYAyxs$0D`juvXy+T|A$PLjXb(MM7TBkkn+XgRHMja6wRpw(iJlM@ zXK=KpWI$`FRbE3SK_amUsCH+Lxw88n13^jqdLnq6Vs-)dHVqjL{0fqP#9`2|=Roxe zs0pa#9Q*p_I)kufJ|xw5?JwF@a+1*N;bCpW$Ol`16uO?&uZO9mHt4946k){DLeRoiMYiC^!Nf)td%Lj3vu4pmu52|o2 zhYAbw0H?kzB6^SlQ|8By&Tic<1H9jRHH@O9DFByDjZqxJJ+2W34SJ4x;ciIu0D2E;Q~+fI2)<7t;>#Zt z5}IjsY%l-h*$fGF20T3cU6-V31riW|2YH}5bsTAtOOgN~Gm<&jQ+mE2Xf;tHjtkI> z3ZU3&cBB>PF7{=O+fYO0)QevPGN2leO0AA9tnz)1Fo#o6UO##AhQsv;BnKy82r*>O zlNo&QNu2(gAJ_`SEP$}Rd-ryS0utVGMphgN;TQ7_aah;U0?oZbmuvTk_=rHY7z(ZM z30T16z7SnNX890D#+N_#S%ziPgyj2W+2R<_m7T$cGs+($Kr8s<4k%ooy0s}f(zCFn z?uZaDJp>GChnpsFl}gzDnS%!pmfB;@>YkmLNQwIT&*y7vhH)jQu%EB5n!g+CG<%lD z8Sv}5Ai93A>maNYDYKK|(%d-g>M3?7P+W2-++RXOhXvDTCc3W1qDaHU;PVTRhprAT zLS_jKg)})6%0S5vP@>~!$$?h^b&dT%EgLZigRFs#S>r{1Ch5MLq*efMMS(+bGT8!C zqi=g-KuL1Bb-}afEj>C#^+_q{C`_v$BZ3kocQI_=++7)F=jl34?;3%p93s{|QPm|N z-b|@Q>JIffR+~U}1;zh@)s;nHlb-B@rc&rEmi{_l8N~K0hg46-9!85VvZr2neRV_J zbJzjSRG`ldh(iO=oQp#>2`Wg?dFvpES*rA0337W0b_EjnRJDW(zy(wvBtNi0;h~aw zWM|t^^rcZdppjm-I_MY~89|m~Qo&cWG#bfaAa4z_1W1XQb<0XikzNl_Gc7i31p;NA zWJ2N!SiO<*0vb63R9(NEfet9mTQ+6x6%?F?a``Fl+b$mP`Vdsa9fnp7LOejF(jjO5 z4v~3QS5q(_ar*jZ6aAiwT#sd!w!Jo6>|`MC+yY0{295(1kYJ&|4t-f&m6CN&=&pyO zM80-Lp>JXD;lsJWXF@tK2w4aK4}}o!5NK0~>H`O-fFsZ-QHVoAY(CO>c0whzB1ZCs zN5L?}A8&=Y@m;5|7SLKoI05?}P`Z~-0&MmO_LOHN+dBmH*0T`oL4h)SnBWtGMv->w zQf(Wgi5HZtslUYq@%<~~q~OQ6#w^YcdkC?}rZEQ^g0D*9ZGsaMkB9N;Ge zfN1Cm7P;T`J|H13uIz!}ATYm8_HEus8ud?n8-W`=K;csbMV#zq4L-06AB=e((4&@- za%scmjYB1E(8RL0bm{j^FO6EIG`srnV5^3V&CP1_`A{%rpue?xsh_c{KJkRT` z=3T6~lOWn7M5nd5ob6ss`Ii?%xJZIdb1m%LQHVoo9DQF3mZ<*a^Z3XKA!V(I3gxqw z&CqNETu@)M_{^1+!P9561X&0OlE8Hl@C6uq6@9SQdjog+U)Ba+h6AXf zB^+pI2Jf%|-AjTQqDNE}KevPZ!`AJ-ma_EX@Ypf2h&^ec+HL@&7q#qhKN{|BNPt^kOE$FTkb|Q1t0!?|MCv8 z(DX94u0p0GaK&rvWV6AvG}hS$39x__z+92_*HYT72c0Y+1o__<`~UhBKpR7T@mHJP zmW$CxuXG2g+}QeJ?ET4rB9KZoJ&VD&ZxE{kP#FW#(&PDRc=tGKwH zPR}IdCAedQ6FAX0ER=o|R~asr|7nVbI9^)th51$j{S&kyhswrbOb$SAc^*$e;$ z6p!|!Z3U&=t-PmRKn*+Y6VkA@j#wr){Or}=X;0(e;QbuayE-QN4u4`YNo5JE5ryWCKw&EGjeqMZzMS3 zteY<;>4W#9;AnJ!#Hd9i$gmkW!z6Sp&whn5plB_j83@WP7Z2{i0a#n@4qEjrXA*a@ zgXW%%H!zCOj}uX1V`Bp<<6aB49QQ}CHGv%)L0h%=pWH!cYP{k{7d}HigVctV9leB` zS;~(A-32Amgq1m=J&9AJV_)Bf7y(*n2X4EAQSdUF6Ok`NeHmIXQ6f4pQ{d-}7_mF> z;LMNMolayhjkYqTg{m+v`Qgtp7*GzdHa(w0DWmCRlo^9-k?|G|%@$C>hbq{v^5q`d zWvc*h%n^`D!)T2x(3Jp3I*&k%NM6tY5Nw`P*&L!S^IzXJV*xU3or3rJ#(BjoY2N z{Gi42YPuHCw;(NrNhDAEx_f)My+N6OZ$F4rr3%g<1!fUOY2+yh?zDO38SVFF_-^8t zrN5OvsqrE!YAh?31U(E?-ry#nI0?zm2D+it2&fxV8z^LHRxEe|FKS`|Ffo&7id}1` z$%_2{oyS`)#U%xy7*=&j?6Xj#z5(q_w1|2v#QXmRk*9ozf9}=RfjkCbp;xP4a(As0 zdgoi;KX#5}|4D6DSucmh%f=ooyb(%Jm%P7HSqzVnu^9dmzIYbv2f8##hbdMNq?L7T zm0#DMT}EiWhbW(xRde^RJTk{tbn{(KVata+mE5foPU1yy7R%`W>|hUPDF>=igAX8B>Ex~xxd6JGdLgrs>H)LFnJ(bRD% zJ(vQ7t{i^o`ny@sJ0sl{ppw0JUj@l^c>)c6f8`l%8)KXyny;P9^_Z5-`Mm?36X}rY z@)JIjsvo7E1N5sDm-zw?{DqIKQ z>x-G9aNzFUy9cjye*jfna`L_>>EN>}m&>v-gSVuq*phE;k zh$Q8W(0)eD5R7j5mGt^GP_4gO68bwBL<0KHWNRagSo>V03g7mutP;pf;OSy8jGW1Ym82pDW5Ou@09QAqTY{^`zN1Br*U zX1^>mPjF8c29b~@qx}DQ4WTum_(M!Wud<$}QGwqD&%&Fnm7st@*y*j`9|~VFkpDFY z<-#m|U2N`}EjDw!|0+MD|3v&H#OxqlJ&2#UflnL$W|a;q;S_|_4b~ZN0eHUjCqu(q z-4L7&7#)NRzChMmGIHV8GRrfiUh6#CMm>u|_@otLCN#!{!5pSJ;N#YktLmvwmztt3 z9wyZ{DXuLe!6e6wyl-DxQF7h6py3Guc9@?5Kpo5m_(+m^FG7Apcua(6Z}Un zyv2G33BDrI2iOPvK&3`_RPX>lvQq-plt-8yh0l!fa;Wdi-6KX=E+plLV-0`4Hj?O% z)YEJ>7&v9*#59Rt1j*)_6Tn*vLJa(T?h$s03)5nE(&~B4gNnvB!SDxPU`A0GEXdlD zf9C}N_Wg9U|MThb%AN0dJw41FTKARkJl5vQ$!AWNZ^CSa0cvh3wn9@B5p%5&UJ^Fxi!( zzrG&ElrWp?4=42NBO#Q{=X zQ@#FWcQ*T=K&`(B`e3W(>|sz2Wep3d$lzcYPS^^Z z;==SG$PJxJgZDP=Z(lZVQQ+Dma6nBmEA}(Yxe3l-=&D0%?CB}@J9ds62X#T+{-H-t zM;!zxzh)+x&u(vJec1*tquXd%VhsEr|1+*Hj7wR?BAp$niKHBjWiU<0Q~OIGq1A&L zEtIkPWx>CMToH!oq{u?!M`bPL$b4Aqs=0yePm^_Gp07c8!RhY(26!+pXn)Uet<0~G zHV#vE-up9OnDxBcuW$-`46asa-K36G`bjxNy5YBP+x5>AFn+|B)dvf0%6^UO%_a5A zd8Zn>a-%g|Er!%+6-r_f*6{i5#>r3eYxw+$Uq-FI823!)hkJc{1vYDIQuyqdcb2=| zn{zX8IU;B`UP>i_e$mfxL zHS_1&i@>+*9(f@HM0H;^OH)DWdn+zv(4D`VEA~s}CEJz0WAKIW(ISx~aDExHmEwx! z(wSmkRQE?UjJS*YGdGMFPl_&dRU$<(HA%P1k;_5zBa&R1;0p?u>O5pB7_G;G?JuP| zG}t!Yqt#0Pc&Sh_XZKhu##JTIHFVK-WQC)M_eE%wtL6d04ACt2iyiMU5uza} zto6{aTYRk%G5||`qP*pH36#&Ue>=)wZ!S6qxp>~v`3*P z^>7~yhz+9w6(I5zXrcYfh)fvS){%mUkAwLPkZ~W-Wq~xSK>f|E_GxU?g^ft(?bu(& zXTps*<5gljpukuUb1k5&3z=I5eJGjyil7Wf6_!l=Z2%?}jL02semM%_259w*(=((p>ku^UC=av0E}A<=nDa@rN9y|k(tjIW3|DEQ z62y-NeRI8D;i}~L6BP0JvDto@L8{{Aur_m)RT_NY=IA11IO*Oh3c}-|j&Fu3g}BL3 znA>?|Q`1-Y2K?gc=CxNR`uh~+#Q?|%jW1n##si!fq`Y5%70icWtoBi|Dk?Fst{}Mc z#y508`4oowoGRbA=_4E~Y!?h1D3~bac^1Mp@LCAkBSAhfPztiq-pZJm`}gDbb62&0 ziWIfYwrXci&V)=2cI(+*_$|(GYe{H^Jr4a8y916~X|-?oOm-}74#tt_!7vA8HZNqCAMbHM z{WrwL(sIPMQd|?Xg3zu~^9=eCxTm3tScCKfV|9osVjBWLpK%15b8Byck^wac^LP#I z>@qJK-8`ID3N_!RmsXxorT@GV*Dz6zw=jzx15Nc7MBg(^3#>S;Je1mY|78C4cM<859!_iH|(_|dPq&0;gf)0YA z0G#aX?~02*M{6n^us=F~A<3Y~Bl8)s3i45H!-v9NrL$f!?lU`=kr_z3iIw5}GV^;v>s6z6gs<$c6&4`E zY-NAlq=wm60W*&TFv&10?9hqZJ3f||m*?%nm6jo6c{8FrQMXahxs?rk0Pq;D(C2S^ z<%&S_h8a5G^;NK@T*8nogCJpG0ccQYw2p=8Qj1XKCvQVFb^vB*khKnkBL3%RAR0QU z&5njT?>A^MN!$%3PLG){#vv}J9OP)3oxAUL2y@FX!^pl@!`aOy(9_AX$KH^7AxcvL z=_Cc3cA;Ahi1mbMm)pUiB~#gPFgB*ti|QzP1f;v5X<&g-dik!7A3)LTF#5k(u8EgL%YOQ2U=Fo%iXt*Kd1|!XAUstTp?`u!r$URF14ysF zhwk{T#OU}?i?ekA7%tw`?}FYfxo1^s3_B0Bo=W7w=z?K{>boQYlPLgo+%W^44HO3g z4V#*$7Gm>XqXuCDn$$H3y?%W}+M( zwxw?e9W2rb41GE(=kKL;FkaqJZrYM_(69eFOrm*>LDU1G1%1)3AdyH1)pB{$CNi3; zj8o7o8*<`=HOwq(hPfj5E-jEfJM)}=8C?}bUErgB_F=Rw3{I&=1k{@5JBTVl0=DxA z8VV<-FtHrJQFs?JbyhiDf$!_oP<`w%(w=aIyM{etz%5_;9Hy&Xu-A|w8l@5`a(Ag8 zq_Xw3AvIK_Tj?G)s~Iv!qoC0^V8#{%3*I51iwfPVc4I13zoDO6Z}JwOeJphSfp1PM(ctm_MwVX#(vtK*x z4D)K!6oA4I)LOv6RvL;{p6F*?DyLuIC<430j-sh?8u81MF{@h(FYK{85v(>CEQ*w& z$AM%ER^IdyeUCNDDNqs@;vGJDX$&)8R?O#xoptF}t{AmDMSwZ3P zIOwK#~o zG$H@I6%>qWLomsiAdO%Wi08`fpdlLD6lfTOlOKcl{VfD(%<)tzO*rXCKjo5oC!_(> zBY2V*2TGn;8($jUT47Z7SGfZP3a>yIG4iU9xreyeP;Vzs3MMUERhWfutaYPQp#3KzR>eHSntnDk=#^ z$jHlyc5K5(AAQr{kGE;6++6NGGCrufzvqkm-HxKw<q9R>*wxNZ>oP*D*L zM+tD!wvbht>m{WL6Gy*j=pQh_9x~Xzao`gOCchPwg7odNZYQzU^%;g`-l|c4P#+ug z4En=Nlwb!}&_4uAZjpY)JqZrd5{B+WKQ34&49$S~ARzbBcQ(cZ>-Ah*@e^)4m91jm zRErFfgVrZl<}=A!rmQveWyWWUPH|(XH6MZ)SEj zsp3%kC$&SV3MH4#%rYbk&l(u8(9t!vwvzV_jgB^Vb$zn5vKrPtetPm+oW7ui@uBeO zXlBzS=Mw`31qGNUZXO&sW?~X(v1fE?WW>_-x-Vd${M{#_ za%!dNxaUd>1G95anm9XaHw_3~u9k^sa5lHH3T2*bv|v)8vx zT3=5u1ZDmp-YzdcKX_(rEUh-c-#^a6^V(J-YYup?m!c99`}z1nSGT zH8n7yVPRpx?l7<9$ndapTSR#H3o&A%7FdLPJC2 z(G#bdsK}|Nqr=10IX*W(?|djc*kvV$h=>;U!a5=)B_%!mP{4aN4Gj*PGt_Utrk$g! z&dJML9B*rE414Ay&6e80Gq2@gL> Ko+53C{eJ+G;EcZj diff --git a/modules/benchmarks/jmh-writer.json b/modules/benchmarks/jmh-writer.json index 5227eab3..3905b378 100644 --- a/modules/benchmarks/jmh-writer.json +++ b/modules/benchmarks/jmh-writer.json @@ -1,6 +1,6 @@ [ { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -14,41 +14,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128b", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 5623657.356338973, + "scoreError" : 621518.5137114808, + "scoreConfidence" : [ + 5002138.842627493, + 6245175.870050454 + ], + "scorePercentiles" : { + "0.0" : 5542828.219103702, + "50.0" : 5594806.800902309, + "90.0" : 5762187.604447574, + "95.0" : 5762187.604447574, + "99.0" : 5762187.604447574, + "99.9" : 5762187.604447574, + "99.99" : 5762187.604447574, + "99.999" : 5762187.604447574, + "99.9999" : 5762187.604447574, + "100.0" : 5762187.604447574 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 5762187.604447574, + 5608287.904018748, + 5542828.219103702, + 5581325.69778587 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 1819887.866280839, - "scoreError" : 204446.28818611056, + "score" : 1778419.8031546003, + "scoreError" : 88448.6938156392, "scoreConfidence" : [ - 1615441.5780947285, - 2024334.1544669496 + 1689971.109338961, + 1866868.4969702396 ], "scorePercentiles" : { - "0.0" : 1787500.8132402124, - "50.0" : 1822320.2268073645, - "90.0" : 1847410.1982684152, - "95.0" : 1847410.1982684152, - "99.0" : 1847410.1982684152, - "99.9" : 1847410.1982684152, - "99.99" : 1847410.1982684152, - "99.999" : 1847410.1982684152, - "99.9999" : 1847410.1982684152, - "100.0" : 1847410.1982684152 + "0.0" : 1758832.1560085032, + "50.0" : 1782241.358763401, + "90.0" : 1790364.3390830962, + "95.0" : 1790364.3390830962, + "99.0" : 1790364.3390830962, + "99.9" : 1790364.3390830962, + "99.99" : 1790364.3390830962, + "99.999" : 1790364.3390830962, + "99.9999" : 1790364.3390830962, + "100.0" : 1790364.3390830962 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1847410.1982684152, - 1846658.5849204224, - 1797981.8686943066, - 1787500.8132402124 + 1790364.3390830962, + 1780440.0720526075, + 1784042.645474194, + 1758832.1560085032 ] ] }, @@ -56,7 +112,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -70,41 +126,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128b", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 6036735.562977094, + "scoreError" : 100765.38118882457, + "scoreConfidence" : [ + 5935970.181788269, + 6137500.944165919 + ], + "scorePercentiles" : { + "0.0" : 6016191.768396554, + "50.0" : 6038824.112508209, + "90.0" : 6053102.258495404, + "95.0" : 6053102.258495404, + "99.0" : 6053102.258495404, + "99.9" : 6053102.258495404, + "99.99" : 6053102.258495404, + "99.999" : 6053102.258495404, + "99.9999" : 6053102.258495404, + "100.0" : 6053102.258495404 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 6053102.258495404, + 6042750.721029169, + 6016191.768396554, + 6034897.50398725 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 1109155.6927294005, - "scoreError" : 11722.369381590592, + "score" : 1153651.396793026, + "scoreError" : 43091.44637822818, "scoreConfidence" : [ - 1097433.32334781, - 1120878.062110991 + 1110559.9504147978, + 1196742.8431712543 ], "scorePercentiles" : { - "0.0" : 1107159.6211813919, - "50.0" : 1109266.9122699166, - "90.0" : 1110929.3251963765, - "95.0" : 1110929.3251963765, - "99.0" : 1110929.3251963765, - "99.9" : 1110929.3251963765, - "99.99" : 1110929.3251963765, - "99.999" : 1110929.3251963765, - "99.9999" : 1110929.3251963765, - "100.0" : 1110929.3251963765 + "0.0" : 1148131.5116133774, + "50.0" : 1151584.3156358043, + "90.0" : 1163305.4442871178, + "95.0" : 1163305.4442871178, + "99.0" : 1163305.4442871178, + "99.9" : 1163305.4442871178, + "99.99" : 1163305.4442871178, + "99.999" : 1163305.4442871178, + "99.9999" : 1163305.4442871178, + "100.0" : 1163305.4442871178 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1107159.6211813919, - 1110432.569796542, - 1110929.3251963765, - 1108101.2547432913 + 1150813.2051328751, + 1163305.4442871178, + 1152355.4261387335, + 1148131.5116133774 ] ] }, @@ -112,7 +224,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -126,41 +238,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "circe" }, "primaryMetric" : { - "score" : 1348593.2665467546, - "scoreError" : 16725.42285120894, + "score" : 1393264.287496805, + "scoreError" : 75110.6708440298, "scoreConfidence" : [ - 1331867.8436955458, - 1365318.6893979635 + 1318153.616652775, + 1468374.9583408348 ], "scorePercentiles" : { - "0.0" : 1344915.614381369, - "50.0" : 1349258.9734497662, - "90.0" : 1350939.5049061175, - "95.0" : 1350939.5049061175, - "99.0" : 1350939.5049061175, - "99.9" : 1350939.5049061175, - "99.99" : 1350939.5049061175, - "99.999" : 1350939.5049061175, - "99.9999" : 1350939.5049061175, - "100.0" : 1350939.5049061175 + "0.0" : 1379468.2130725912, + "50.0" : 1395013.830156555, + "90.0" : 1403561.2766015183, + "95.0" : 1403561.2766015183, + "99.0" : 1403561.2766015183, + "99.9" : 1403561.2766015183, + "99.99" : 1403561.2766015183, + "99.999" : 1403561.2766015183, + "99.9999" : 1403561.2766015183, + "100.0" : 1403561.2766015183 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1348957.93100329, - 1349560.0158962426, - 1350939.5049061175, - 1344915.614381369 + 1402184.3592851234, + 1387843.3010279862, + 1403561.2766015183, + 1379468.2130725912 ] ] }, @@ -168,7 +280,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -182,41 +294,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 327191.60334054136, - "scoreError" : 16833.393674908828, + "score" : 329380.55274132255, + "scoreError" : 7549.991291541363, "scoreConfidence" : [ - 310358.2096656325, - 344024.9970154502 + 321830.5614497812, + 336930.5440328639 ], "scorePercentiles" : { - "0.0" : 323284.24612123315, - "50.0" : 328479.9201514422, - "90.0" : 328522.32693804795, - "95.0" : 328522.32693804795, - "99.0" : 328522.32693804795, - "99.9" : 328522.32693804795, - "99.99" : 328522.32693804795, - "99.999" : 328522.32693804795, - "99.9999" : 328522.32693804795, - "100.0" : 328522.32693804795 + "0.0" : 328093.33984041726, + "50.0" : 329517.90137734724, + "90.0" : 330393.0683701786, + "95.0" : 330393.0683701786, + "99.0" : 330393.0683701786, + "99.9" : 330393.0683701786, + "99.99" : 330393.0683701786, + "99.999" : 330393.0683701786, + "99.9999" : 330393.0683701786, + "100.0" : 330393.0683701786 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 323284.24612123315, - 328473.80463527696, - 328486.03566760744, - 328522.32693804795 + 328688.61623730283, + 330393.0683701786, + 330347.18651739164, + 328093.33984041726 ] ] }, @@ -224,7 +336,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -238,41 +350,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 1172370.1949547157, - "scoreError" : 44623.57635400855, + "score" : 1167053.2311822318, + "scoreError" : 5257.9839040498, "scoreConfidence" : [ - 1127746.618600707, - 1216993.7713087243 + 1161795.247278182, + 1172311.2150862815 ], "scorePercentiles" : { - "0.0" : 1164002.9183628866, - "50.0" : 1172433.802146774, - "90.0" : 1180610.257162428, - "95.0" : 1180610.257162428, - "99.0" : 1180610.257162428, - "99.9" : 1180610.257162428, - "99.99" : 1180610.257162428, - "99.999" : 1180610.257162428, - "99.9999" : 1180610.257162428, - "100.0" : 1180610.257162428 + "0.0" : 1165914.62029355, + "50.0" : 1167305.0975543554, + "90.0" : 1167688.1093266667, + "95.0" : 1167688.1093266667, + "99.0" : 1167688.1093266667, + "99.9" : 1167688.1093266667, + "99.99" : 1167688.1093266667, + "99.999" : 1167688.1093266667, + "99.9999" : 1167688.1093266667, + "100.0" : 1167688.1093266667 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1180610.257162428, - 1164002.9183628866, - 1174037.1951503523, - 1170830.4091431957 + 1165914.62029355, + 1167688.1093266667, + 1167587.8000121168, + 1167022.395096594 ] ] }, @@ -280,7 +392,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -294,41 +406,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128b", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 2191564.9811769784, - "scoreError" : 20901.204115111937, + "score" : 2034331.7853433017, + "scoreError" : 45080.59303419907, + "scoreConfidence" : [ + 1989251.1923091027, + 2079412.3783775007 + ], + "scorePercentiles" : { + "0.0" : 2024697.6544727832, + "50.0" : 2035622.184185423, + "90.0" : 2041385.1185295777, + "95.0" : 2041385.1185295777, + "99.0" : 2041385.1185295777, + "99.9" : 2041385.1185295777, + "99.99" : 2041385.1185295777, + "99.999" : 2041385.1185295777, + "99.9999" : 2041385.1185295777, + "100.0" : 2041385.1185295777 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 2035855.4805261057, + 2041385.1185295777, + 2035388.8878447404, + 2024697.6544727832 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1kb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 549741.974487169, + "scoreError" : 18510.860445075177, "scoreConfidence" : [ - 2170663.7770618666, - 2212466.1852920903 + 531231.1140420937, + 568252.8349322441 ], "scorePercentiles" : { - "0.0" : 2187130.045963812, - "50.0" : 2192139.318299738, - "90.0" : 2194851.2421446256, - "95.0" : 2194851.2421446256, - "99.0" : 2194851.2421446256, - "99.9" : 2194851.2421446256, - "99.99" : 2194851.2421446256, - "99.999" : 2194851.2421446256, - "99.9999" : 2194851.2421446256, - "100.0" : 2194851.2421446256 + "0.0" : 547187.0258438425, + "50.0" : 549027.1151022253, + "90.0" : 553726.6419003825, + "95.0" : 553726.6419003825, + "99.0" : 553726.6419003825, + "99.9" : 553726.6419003825, + "99.99" : 553726.6419003825, + "99.999" : 553726.6419003825, + "99.9999" : 553726.6419003825, + "100.0" : 553726.6419003825 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2187130.045963812, - 2192498.331993979, - 2194851.2421446256, - 2191780.3046054975 + 549798.4790281476, + 553726.6419003825, + 547187.0258438425, + 548255.751176303 ] ] }, @@ -336,7 +504,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -350,41 +518,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 403267.4027522539, - "scoreError" : 43515.31402068551, + "score" : 434317.73653196916, + "scoreError" : 8887.004496772832, "scoreConfidence" : [ - 359752.0887315684, - 446782.7167729394 + 425430.7320351963, + 443204.741028742 ], "scorePercentiles" : { - "0.0" : 397328.9014945461, - "50.0" : 402949.1987281769, - "90.0" : 409842.3120581157, - "95.0" : 409842.3120581157, - "99.0" : 409842.3120581157, - "99.9" : 409842.3120581157, - "99.99" : 409842.3120581157, - "99.999" : 409842.3120581157, - "99.9999" : 409842.3120581157, - "100.0" : 409842.3120581157 + "0.0" : 432850.8425543397, + "50.0" : 434339.8306456285, + "90.0" : 435740.44228227995, + "95.0" : 435740.44228227995, + "99.0" : 435740.44228227995, + "99.9" : 435740.44228227995, + "99.99" : 435740.44228227995, + "99.999" : 435740.44228227995, + "99.9999" : 435740.44228227995, + "100.0" : 435740.44228227995 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 397594.5620112011, - 397328.9014945461, - 408303.8354451526, - 409842.3120581157 + 433474.58847059973, + 435740.44228227995, + 435205.07282065734, + 432850.8425543397 ] ] }, @@ -392,7 +560,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -406,41 +574,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1kb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 892874.5689568161, + "scoreError" : 29854.73803129264, + "scoreConfidence" : [ + 863019.8309255234, + 922729.3069881088 + ], + "scorePercentiles" : { + "0.0" : 887656.8064194502, + "50.0" : 892500.8721891862, + "90.0" : 898839.7250294421, + "95.0" : 898839.7250294421, + "99.0" : 898839.7250294421, + "99.9" : 898839.7250294421, + "99.99" : 898839.7250294421, + "99.999" : 898839.7250294421, + "99.9999" : 898839.7250294421, + "100.0" : 898839.7250294421 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 898839.7250294421, + 887656.8064194502, + 893189.0795532095, + 891812.6648251629 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 427421.9247585598, - "scoreError" : 24518.728600886658, + "score" : 420722.9597510017, + "scoreError" : 6008.59118247858, "scoreConfidence" : [ - 402903.19615767314, - 451940.65335944644 + 414714.3685685231, + 426731.55093348026 ], "scorePercentiles" : { - "0.0" : 421863.8499176919, - "50.0" : 428821.3012850069, - "90.0" : 430181.24654653337, - "95.0" : 430181.24654653337, - "99.0" : 430181.24654653337, - "99.9" : 430181.24654653337, - "99.99" : 430181.24654653337, - "99.999" : 430181.24654653337, - "99.9999" : 430181.24654653337, - "100.0" : 430181.24654653337 + "0.0" : 419381.737651805, + "50.0" : 420989.7569802229, + "90.0" : 421530.58739175607, + "95.0" : 421530.58739175607, + "99.0" : 421530.58739175607, + "99.9" : 421530.58739175607, + "99.99" : 421530.58739175607, + "99.999" : 421530.58739175607, + "99.9999" : 421530.58739175607, + "100.0" : 421530.58739175607 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 428201.79164324555, - 430181.24654653337, - 429440.8109267683, - 421863.8499176919 + 421530.58739175607, + 419381.737651805, + 420977.8180895547, + 421001.695870891 ] ] }, @@ -448,7 +672,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -462,41 +686,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 177497.0107319799, - "scoreError" : 14364.31626843296, + "score" : 176604.56851430555, + "scoreError" : 5367.441061591579, "scoreConfidence" : [ - 163132.69446354694, - 191861.3270004129 + 171237.12745271396, + 181972.00957589713 ], "scorePercentiles" : { - "0.0" : 174194.40738251232, - "50.0" : 178388.27788455077, - "90.0" : 179017.07977630582, - "95.0" : 179017.07977630582, - "99.0" : 179017.07977630582, - "99.9" : 179017.07977630582, - "99.99" : 179017.07977630582, - "99.999" : 179017.07977630582, - "99.9999" : 179017.07977630582, - "100.0" : 179017.07977630582 + "0.0" : 175891.7547813067, + "50.0" : 176498.65950984042, + "90.0" : 177529.2002562346, + "95.0" : 177529.2002562346, + "99.0" : 177529.2002562346, + "99.9" : 177529.2002562346, + "99.99" : 177529.2002562346, + "99.999" : 177529.2002562346, + "99.9999" : 177529.2002562346, + "100.0" : 177529.2002562346 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 179017.07977630582, - 178481.170435525, - 174194.40738251232, - 178295.38533357653 + 175913.72561930353, + 175891.7547813067, + 177529.2002562346, + 177083.59340037729 ] ] }, @@ -504,7 +728,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -518,41 +742,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 47736.65476696285, - "scoreError" : 1345.094348191517, + "score" : 48283.89499745317, + "scoreError" : 1656.4635042534533, "scoreConfidence" : [ - 46391.56041877133, - 49081.749115154365 + 46627.43149319972, + 49940.35850170662 ], "scorePercentiles" : { - "0.0" : 47555.57243850504, - "50.0" : 47686.14025700826, - "90.0" : 48018.76611532986, - "95.0" : 48018.76611532986, - "99.0" : 48018.76611532986, - "99.9" : 48018.76611532986, - "99.99" : 48018.76611532986, - "99.999" : 48018.76611532986, - "99.9999" : 48018.76611532986, - "100.0" : 48018.76611532986 + "0.0" : 48041.62315484572, + "50.0" : 48229.64055126053, + "90.0" : 48634.67573244592, + "95.0" : 48634.67573244592, + "99.0" : 48634.67573244592, + "99.9" : 48634.67573244592, + "99.99" : 48634.67573244592, + "99.999" : 48634.67573244592, + "99.9999" : 48634.67573244592, + "100.0" : 48634.67573244592 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 48018.76611532986, - 47607.06985357856, - 47765.210660437944, - 47555.57243850504 + 48160.72011965533, + 48298.56098286572, + 48041.62315484572, + 48634.67573244592 ] ] }, @@ -560,7 +784,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -574,41 +798,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 148580.55389673484, - "scoreError" : 7903.956542352092, + "score" : 149003.2664377889, + "scoreError" : 4034.6402888002817, "scoreConfidence" : [ - 140676.59735438274, - 156484.51043908694 + 144968.6261489886, + 153037.9067265892 ], "scorePercentiles" : { - "0.0" : 147110.3341399147, - "50.0" : 148799.92883293395, - "90.0" : 149612.0237811568, - "95.0" : 149612.0237811568, - "99.0" : 149612.0237811568, - "99.9" : 149612.0237811568, - "99.99" : 149612.0237811568, - "99.999" : 149612.0237811568, - "99.9999" : 149612.0237811568, - "100.0" : 149612.0237811568 + "0.0" : 148126.13096242023, + "50.0" : 149196.69225041344, + "90.0" : 149493.5502879085, + "95.0" : 149493.5502879085, + "99.0" : 149493.5502879085, + "99.9" : 149493.5502879085, + "99.99" : 149493.5502879085, + "99.999" : 149493.5502879085, + "99.9999" : 149493.5502879085, + "100.0" : 149493.5502879085 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 148036.21042685886, - 147110.3341399147, - 149563.64723900906, - 149612.0237811568 + 148126.13096242023, + 149493.5502879085, + 148990.63016748935, + 149402.75433333754 ] ] }, @@ -616,7 +840,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -630,41 +854,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1kb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 266636.44019146083, - "scoreError" : 3831.5862190856205, + "score" : 277147.0123110621, + "scoreError" : 10341.200741446326, + "scoreConfidence" : [ + 266805.81156961573, + 287488.2130525084 + ], + "scorePercentiles" : { + "0.0" : 275522.27501270274, + "50.0" : 276875.17110009305, + "90.0" : 279315.4320313594, + "95.0" : 279315.4320313594, + "99.0" : 279315.4320313594, + "99.9" : 279315.4320313594, + "99.99" : 279315.4320313594, + "99.999" : 279315.4320313594, + "99.9999" : 279315.4320313594, + "100.0" : 279315.4320313594 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 276564.2150378382, + 277186.12716234784, + 279315.4320313594, + 275522.27501270274 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128kb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 4728.468432101119, + "scoreError" : 169.23274141134854, "scoreConfidence" : [ - 262804.8539723752, - 270468.02641054644 + 4559.23569068977, + 4897.701173512468 ], "scorePercentiles" : { - "0.0" : 266038.3930441509, - "50.0" : 266528.46815931366, - "90.0" : 267450.43140306504, - "95.0" : 267450.43140306504, - "99.0" : 267450.43140306504, - "99.9" : 267450.43140306504, - "99.99" : 267450.43140306504, - "99.999" : 267450.43140306504, - "99.9999" : 267450.43140306504, - "100.0" : 267450.43140306504 + "0.0" : 4689.218535377044, + "50.0" : 4740.948460607137, + "90.0" : 4742.758271813157, + "95.0" : 4742.758271813157, + "99.0" : 4742.758271813157, + "99.9" : 4742.758271813157, + "99.99" : 4742.758271813157, + "99.999" : 4742.758271813157, + "99.9999" : 4742.758271813157, + "100.0" : 4742.758271813157 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 267450.43140306504, - 266453.6905350327, - 266603.24578359467, - 266038.3930441509 + 4689.218535377044, + 4742.758271813157, + 4741.763264207704, + 4740.1336570065705 ] ] }, @@ -672,7 +952,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -686,41 +966,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 3265.018485513807, - "scoreError" : 211.52248659255656, + "score" : 3577.79887805201, + "scoreError" : 26.18467106574972, "scoreConfidence" : [ - 3053.4959989212502, - 3476.5409721063634 + 3551.6142069862603, + 3603.9835491177596 ], "scorePercentiles" : { - "0.0" : 3220.6314731712628, - "50.0" : 3274.0272158278585, - "90.0" : 3291.388037228249, - "95.0" : 3291.388037228249, - "99.0" : 3291.388037228249, - "99.9" : 3291.388037228249, - "99.99" : 3291.388037228249, - "99.999" : 3291.388037228249, - "99.9999" : 3291.388037228249, - "100.0" : 3291.388037228249 + "0.0" : 3572.0979436748175, + "50.0" : 3579.15086827506, + "90.0" : 3580.795831983102, + "95.0" : 3580.795831983102, + "99.0" : 3580.795831983102, + "99.9" : 3580.795831983102, + "99.99" : 3580.795831983102, + "99.999" : 3580.795831983102, + "99.9999" : 3580.795831983102, + "100.0" : 3580.795831983102 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3260.1250600835074, - 3220.6314731712628, - 3291.388037228249, - 3287.929371572209 + 3577.715499233423, + 3580.795831983102, + 3580.5862373166974, + 3572.0979436748175 ] ] }, @@ -728,7 +1008,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -742,41 +1022,97 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "128kb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 5576.712066017241, + "scoreError" : 56.87910417352511, + "scoreConfidence" : [ + 5519.8329618437165, + 5633.591170190766 + ], + "scorePercentiles" : { + "0.0" : 5569.143715740925, + "50.0" : 5574.144810654081, + "90.0" : 5589.414927019879, + "95.0" : 5589.414927019879, + "99.0" : 5589.414927019879, + "99.9" : 5589.414927019879, + "99.99" : 5589.414927019879, + "99.999" : 5589.414927019879, + "99.9999" : 5589.414927019879, + "100.0" : 5589.414927019879 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 5574.6956819036295, + 5573.593939404532, + 5589.414927019879, + 5569.143715740925 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 3096.090303694719, - "scoreError" : 114.1578488303212, + "score" : 3177.920949665991, + "scoreError" : 58.74788909478107, "scoreConfidence" : [ - 2981.9324548643976, - 3210.24815252504 + 3119.17306057121, + 3236.668838760772 ], "scorePercentiles" : { - "0.0" : 3075.4612594160444, - "50.0" : 3098.1337498176445, - "90.0" : 3112.6324557275416, - "95.0" : 3112.6324557275416, - "99.0" : 3112.6324557275416, - "99.9" : 3112.6324557275416, - "99.99" : 3112.6324557275416, - "99.999" : 3112.6324557275416, - "99.9999" : 3112.6324557275416, - "100.0" : 3112.6324557275416 + "0.0" : 3169.9666905993176, + "50.0" : 3176.9266137796594, + "90.0" : 3187.8638805053274, + "95.0" : 3187.8638805053274, + "99.0" : 3187.8638805053274, + "99.9" : 3187.8638805053274, + "99.99" : 3187.8638805053274, + "99.999" : 3187.8638805053274, + "99.9999" : 3187.8638805053274, + "100.0" : 3187.8638805053274 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3108.8275968787652, - 3075.4612594160444, - 3087.439902756524, - 3112.6324557275416 + 3170.4518266905884, + 3187.8638805053274, + 3183.40140086873, + 3169.9666905993176 ] ] }, @@ -784,7 +1120,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -798,41 +1134,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 1255.0932751513285, - "scoreError" : 46.22591384242437, + "score" : 1279.9729785390234, + "scoreError" : 20.191937356816076, "scoreConfidence" : [ - 1208.867361308904, - 1301.319188993753 + 1259.7810411822074, + 1300.1649158958394 ], "scorePercentiles" : { - "0.0" : 1244.6630457421552, - "50.0" : 1257.3824405600344, - "90.0" : 1260.9451737430902, - "95.0" : 1260.9451737430902, - "99.0" : 1260.9451737430902, - "99.9" : 1260.9451737430902, - "99.99" : 1260.9451737430902, - "99.999" : 1260.9451737430902, - "99.9999" : 1260.9451737430902, - "100.0" : 1260.9451737430902 + "0.0" : 1275.6939320168879, + "50.0" : 1280.6173242904092, + "90.0" : 1282.9633335583874, + "95.0" : 1282.9633335583874, + "99.0" : 1282.9633335583874, + "99.9" : 1282.9633335583874, + "99.99" : 1282.9633335583874, + "99.999" : 1282.9633335583874, + "99.9999" : 1282.9633335583874, + "100.0" : 1282.9633335583874 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1257.4225286709386, - 1257.34235244913, - 1260.9451737430902, - 1244.6630457421552 + 1275.6939320168879, + 1281.394784055471, + 1282.9633335583874, + 1279.8398645253476 ] ] }, @@ -840,7 +1176,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -854,41 +1190,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 360.73397668574137, - "scoreError" : 2.9849183809715094, + "score" : 375.69147856459927, + "scoreError" : 26.87803980719791, "scoreConfidence" : [ - 357.74905830476985, - 363.7188950667129 + 348.81343875740134, + 402.5695183717972 ], "scorePercentiles" : { - "0.0" : 360.1643871524679, - "50.0" : 360.74936066459566, - "90.0" : 361.27279826130626, - "95.0" : 361.27279826130626, - "99.0" : 361.27279826130626, - "99.9" : 361.27279826130626, - "99.99" : 361.27279826130626, - "99.999" : 361.27279826130626, - "99.9999" : 361.27279826130626, - "100.0" : 361.27279826130626 + "0.0" : 369.66421529142144, + "50.0" : 377.01959904759076, + "90.0" : 379.062500871794, + "95.0" : 379.062500871794, + "99.0" : 379.062500871794, + "99.9" : 379.062500871794, + "99.99" : 379.062500871794, + "99.999" : 379.062500871794, + "99.9999" : 379.062500871794, + "100.0" : 379.062500871794 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 360.86088286252544, - 361.27279826130626, - 360.6378384666658, - 360.1643871524679 + 377.6037578935196, + 376.43544020166183, + 379.062500871794, + 369.66421529142144 ] ] }, @@ -896,7 +1232,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -910,41 +1246,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 1053.7816934132636, - "scoreError" : 17.98846836879526, + "score" : 1109.5044338432704, + "scoreError" : 14.324446676367586, "scoreConfidence" : [ - 1035.7932250444683, - 1071.770161782059 + 1095.1799871669027, + 1123.828880519638 ], "scorePercentiles" : { - "0.0" : 1051.7004653852352, - "50.0" : 1052.8498041712742, - "90.0" : 1057.726699925271, - "95.0" : 1057.726699925271, - "99.0" : 1057.726699925271, - "99.9" : 1057.726699925271, - "99.99" : 1057.726699925271, - "99.999" : 1057.726699925271, - "99.9999" : 1057.726699925271, - "100.0" : 1057.726699925271 + "0.0" : 1108.0686828247212, + "50.0" : 1108.6025725651468, + "90.0" : 1112.7439074180666, + "95.0" : 1112.7439074180666, + "99.0" : 1112.7439074180666, + "99.9" : 1112.7439074180666, + "99.99" : 1112.7439074180666, + "99.999" : 1112.7439074180666, + "99.9999" : 1112.7439074180666, + "100.0" : 1112.7439074180666 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1057.726699925271, - 1053.7487275819278, - 1051.9508807606203, - 1051.7004653852352 + 1108.0737445954674, + 1108.0686828247212, + 1109.1314005348263, + 1112.7439074180666 ] ] }, @@ -952,7 +1288,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -966,41 +1302,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "128kb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 2092.4795061467858, - "scoreError" : 3.4526544775530565, + "score" : 1934.2735060465438, + "scoreError" : 145.3142718068349, "scoreConfidence" : [ - 2089.0268516692327, - 2095.932160624339 + 1788.9592342397088, + 2079.5877778533786 ], "scorePercentiles" : { - "0.0" : 2092.002062716033, - "50.0" : 2092.3633355152065, - "90.0" : 2093.189290840698, - "95.0" : 2093.189290840698, - "99.0" : 2093.189290840698, - "99.9" : 2093.189290840698, - "99.99" : 2093.189290840698, - "99.999" : 2093.189290840698, - "99.9999" : 2093.189290840698, - "100.0" : 2093.189290840698 + "0.0" : 1900.5575849264953, + "50.0" : 1945.0995448027188, + "90.0" : 1946.3373496542426, + "95.0" : 1946.3373496542426, + "99.0" : 1946.3373496542426, + "99.9" : 1946.3373496542426, + "99.99" : 1946.3373496542426, + "99.999" : 1946.3373496542426, + "99.9999" : 1946.3373496542426, + "100.0" : 1946.3373496542426 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2092.142316922251, - 2093.189290840698, - 2092.5843541081617, - 2092.002062716033 + 1945.5256149916627, + 1946.3373496542426, + 1944.6734746137747, + 1900.5575849264953 ] ] }, @@ -1008,7 +1344,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1022,41 +1358,153 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1mb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 582.9115264911206, + "scoreError" : 3.036982655418544, + "scoreConfidence" : [ + 579.8745438357021, + 585.9485091465392 + ], + "scorePercentiles" : { + "0.0" : 582.5104232647693, + "50.0" : 582.7768370434262, + "90.0" : 583.5820086128612, + "95.0" : 583.5820086128612, + "99.0" : 583.5820086128612, + "99.9" : 583.5820086128612, + "99.99" : 583.5820086128612, + "99.999" : 583.5820086128612, + "99.9999" : 583.5820086128612, + "100.0" : 583.5820086128612 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 582.687615523413, + 582.8660585634393, + 583.5820086128612, + 582.5104232647693 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 300.50175605365195, - "scoreError" : 16.628260772796597, + "score" : 499.2126793098415, + "scoreError" : 16.65777230462521, + "scoreConfidence" : [ + 482.5549070052163, + 515.8704516144667 + ], + "scorePercentiles" : { + "0.0" : 495.5221775965387, + "50.0" : 500.12397993581743, + "90.0" : 501.08057977119233, + "95.0" : 501.08057977119233, + "99.0" : 501.08057977119233, + "99.9" : 501.08057977119233, + "99.99" : 501.08057977119233, + "99.999" : 501.08057977119233, + "99.9999" : 501.08057977119233, + "100.0" : 501.08057977119233 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 495.5221775965387, + 499.36059915298074, + 501.08057977119233, + 500.8873607186541 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "1mb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 727.8444320522649, + "scoreError" : 5.4201652188762965, "scoreConfidence" : [ - 283.87349528085537, - 317.1300168264485 + 722.4242668333886, + 733.2645972711412 ], "scorePercentiles" : { - "0.0" : 296.7270976551244, - "50.0" : 301.46267892494234, - "90.0" : 302.35456870959865, - "95.0" : 302.35456870959865, - "99.0" : 302.35456870959865, - "99.9" : 302.35456870959865, - "99.99" : 302.35456870959865, - "99.999" : 302.35456870959865, - "99.9999" : 302.35456870959865, - "100.0" : 302.35456870959865 + "0.0" : 726.9083542675232, + "50.0" : 727.7621565394252, + "90.0" : 728.9450608626861, + "95.0" : 728.9450608626861, + "99.0" : 728.9450608626861, + "99.9" : 728.9450608626861, + "99.99" : 728.9450608626861, + "99.999" : 728.9450608626861, + "99.9999" : 728.9450608626861, + "100.0" : 728.9450608626861 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 302.35456870959865, - 301.0521833779614, - 301.87317447192333, - 296.7270976551244 + 727.8309841229437, + 727.6933289559067, + 728.9450608626861, + 726.9083542675232 ] ] }, @@ -1064,7 +1512,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1078,41 +1526,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 392.0619134376386, - "scoreError" : 1.6351377127176903, + "score" : 394.10021533585757, + "scoreError" : 4.34839951102803, "scoreConfidence" : [ - 390.42677572492096, - 393.6970511503563 + 389.7518158248295, + 398.4486148468856 ], "scorePercentiles" : { - "0.0" : 391.8480127263682, - "50.0" : 391.98575996613056, - "90.0" : 392.42812109192516, - "95.0" : 392.42812109192516, - "99.0" : 392.42812109192516, - "99.9" : 392.42812109192516, - "99.99" : 392.42812109192516, - "99.999" : 392.42812109192516, - "99.9999" : 392.42812109192516, - "100.0" : 392.42812109192516 + "0.0" : 393.5074744682836, + "50.0" : 393.93577760972846, + "90.0" : 395.0218316556898, + "95.0" : 395.0218316556898, + "99.0" : 395.0218316556898, + "99.9" : 395.0218316556898, + "99.99" : 395.0218316556898, + "99.999" : 395.0218316556898, + "99.9999" : 395.0218316556898, + "100.0" : 395.0218316556898 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 392.00345893627724, - 391.96806099598393, - 392.42812109192516, - 391.8480127263682 + 393.5074744682836, + 394.16344469985165, + 395.0218316556898, + 393.7081105196052 ] ] }, @@ -1120,7 +1568,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1134,41 +1582,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 157.51845044919145, - "scoreError" : 0.680659832435363, + "score" : 156.18364150517357, + "scoreError" : 3.967924463335569, "scoreConfidence" : [ - 156.83779061675608, - 158.19911028162682 + 152.215717041838, + 160.15156596850915 ], "scorePercentiles" : { - "0.0" : 157.41259901009516, - "50.0" : 157.51990966919712, - "90.0" : 157.62138344827642, - "95.0" : 157.62138344827642, - "99.0" : 157.62138344827642, - "99.9" : 157.62138344827642, - "99.99" : 157.62138344827642, - "99.999" : 157.62138344827642, - "99.9999" : 157.62138344827642, - "100.0" : 157.62138344827642 + "0.0" : 155.33978014796764, + "50.0" : 156.33363491224395, + "90.0" : 156.72751604823878, + "95.0" : 156.72751604823878, + "99.0" : 156.72751604823878, + "99.9" : 156.72751604823878, + "99.99" : 156.72751604823878, + "99.999" : 156.72751604823878, + "99.9999" : 156.72751604823878, + "100.0" : 156.72751604823878 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 157.44414342450114, - 157.62138344827642, - 157.5956759138931, - 157.41259901009516 + 155.33978014796764, + 156.13584663979125, + 156.72751604823878, + 156.53142318469662 ] ] }, @@ -1176,7 +1624,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1190,41 +1638,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 49.41659582197183, - "scoreError" : 4.128650672278151, + "score" : 49.42348078315202, + "scoreError" : 4.458763358666092, "scoreConfidence" : [ - 45.287945149693684, - 53.54524649424998 + 44.96471742448593, + 53.88224414181811 ], "scorePercentiles" : { - "0.0" : 48.76101535916496, - "50.0" : 49.306450172914005, - "90.0" : 50.29246758289436, - "95.0" : 50.29246758289436, - "99.0" : 50.29246758289436, - "99.9" : 50.29246758289436, - "99.99" : 50.29246758289436, - "99.999" : 50.29246758289436, - "99.9999" : 50.29246758289436, - "100.0" : 50.29246758289436 + "0.0" : 48.56102653054879, + "50.0" : 49.49864645891251, + "90.0" : 50.135603684234304, + "95.0" : 50.135603684234304, + "99.0" : 50.135603684234304, + "99.9" : 50.135603684234304, + "99.99" : 50.135603684234304, + "99.999" : 50.135603684234304, + "99.9999" : 50.135603684234304, + "100.0" : 50.135603684234304 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 49.34785823381571, - 50.29246758289436, - 48.76101535916496, - 49.2650421120123 + 50.135603684234304, + 48.56102653054879, + 49.78678526110603, + 49.21050765671899 ] ] }, @@ -1232,7 +1680,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1246,41 +1694,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 142.1640514746978, - "scoreError" : 1.4225526580210257, + "score" : 138.30900400088186, + "scoreError" : 2.3224502928310424, "scoreConfidence" : [ - 140.74149881667677, - 143.58660413271883 + 135.98655370805082, + 140.6314542937129 ], "scorePercentiles" : { - "0.0" : 141.90393517724456, - "50.0" : 142.1560858061505, - "90.0" : 142.4400991092456, - "95.0" : 142.4400991092456, - "99.0" : 142.4400991092456, - "99.9" : 142.4400991092456, - "99.99" : 142.4400991092456, - "99.999" : 142.4400991092456, - "99.9999" : 142.4400991092456, - "100.0" : 142.4400991092456 + "0.0" : 137.8065551308592, + "50.0" : 138.3862375523468, + "90.0" : 138.65698576797462, + "95.0" : 138.65698576797462, + "99.0" : 138.65698576797462, + "99.9" : 138.65698576797462, + "99.99" : 138.65698576797462, + "99.999" : 138.65698576797462, + "99.9999" : 138.65698576797462, + "100.0" : 138.65698576797462 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 142.18251762104686, - 142.4400991092456, - 141.90393517724456, - 142.12965399125414 + 138.65698576797462, + 138.4181173369827, + 137.8065551308592, + 138.35435776771092 ] ] }, @@ -1288,7 +1736,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1302,41 +1750,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "1mb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 239.59825470502108, - "scoreError" : 12.090993885755951, + "score" : 238.86175823829703, + "scoreError" : 0.6522379606193902, "scoreConfidence" : [ - 227.50726081926513, - 251.68924859077703 + 238.20952027767763, + 239.51399619891643 ], "scorePercentiles" : { - "0.0" : 237.33856097367186, - "50.0" : 239.95747440052935, - "90.0" : 241.13950904535375, - "95.0" : 241.13950904535375, - "99.0" : 241.13950904535375, - "99.9" : 241.13950904535375, - "99.99" : 241.13950904535375, - "99.999" : 241.13950904535375, - "99.9999" : 241.13950904535375, - "100.0" : 241.13950904535375 + "0.0" : 238.71235571423296, + "50.0" : 238.9030442351787, + "90.0" : 238.92858876859776, + "95.0" : 238.92858876859776, + "99.0" : 238.92858876859776, + "99.9" : 238.92858876859776, + "99.99" : 238.92858876859776, + "99.999" : 238.92858876859776, + "99.9999" : 238.92858876859776, + "100.0" : 238.92858876859776 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 241.1328951147916, - 241.13950904535375, - 238.78205368626712, - 237.33856097367186 + 238.71235571423296, + 238.88950084360954, + 238.92858876859776, + 238.91658762674786 ] ] }, @@ -1344,7 +1792,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1358,41 +1806,153 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "32mb", + "processorName" : "tethys" + }, + "primaryMetric" : { + "score" : 16.222819445110638, + "scoreError" : 0.12297511014609462, + "scoreConfidence" : [ + 16.099844334964544, + 16.34579455525673 + ], + "scorePercentiles" : { + "0.0" : 16.197468097538295, + "50.0" : 16.226136279571865, + "90.0" : 16.24153712376053, + "95.0" : 16.24153712376053, + "99.0" : 16.24153712376053, + "99.9" : 16.24153712376053, + "99.99" : 16.24153712376053, + "99.999" : 16.24153712376053, + "99.9999" : 16.24153712376053, + "100.0" : 16.24153712376053 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 16.197468097538295, + 16.24153712376053, + 16.220159716115297, + 16.232112843028432 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "tethys-jackson" }, "primaryMetric" : { - "score" : 11.786317004742862, - "scoreError" : 0.14575827384743734, + "score" : 11.343010380444237, + "scoreError" : 0.15542330497021425, + "scoreConfidence" : [ + 11.187587075474024, + 11.49843368541445 + ], + "scorePercentiles" : { + "0.0" : 11.322620898539569, + "50.0" : 11.337904510310192, + "90.0" : 11.373611602616997, + "95.0" : 11.373611602616997, + "99.0" : 11.373611602616997, + "99.9" : 11.373611602616997, + "99.99" : 11.373611602616997, + "99.999" : 11.373611602616997, + "99.9999" : 11.373611602616997, + "100.0" : 11.373611602616997 + }, + "scoreUnit" : "ops/s", + "rawData" : [ + [ + 11.350772692027288, + 11.325036328593098, + 11.373611602616997, + 11.322620898539569 + ] + ] + }, + "secondaryMetrics" : { + } + }, + { + "jmhVersion" : "1.37", + "benchmark" : "json.bench.JmhWriterBench.bench", + "mode" : "thrpt", + "threads" : 1, + "forks" : 1, + "jvm" : "/Users/gosha/Library/Java/JavaVirtualMachines/corretto-17.0.7/Contents/Home/bin/java", + "jvmArgs" : [ + "-Xms1G", + "-Xmx1G" + ], + "jdkVersion" : "17.0.7", + "vmName" : "OpenJDK 64-Bit Server VM", + "vmVersion" : "17.0.7+7-LTS", + "warmupIterations" : 4, + "warmupTime" : "1 s", + "warmupBatchSize" : 1, + "measurementIterations" : 4, + "measurementTime" : "1 s", + "measurementBatchSize" : 1, + "params" : { + "jsonSize" : "32mb", + "processorName" : "jsoniter" + }, + "primaryMetric" : { + "score" : 19.49130674162078, + "scoreError" : 0.06554510920029963, "scoreConfidence" : [ - 11.640558730895425, - 11.9320752785903 + 19.42576163242048, + 19.55685185082108 ], "scorePercentiles" : { - "0.0" : 11.758114656066427, - "50.0" : 11.78703829894285, - "90.0" : 11.81307676501932, - "95.0" : 11.81307676501932, - "99.0" : 11.81307676501932, - "99.9" : 11.81307676501932, - "99.99" : 11.81307676501932, - "99.999" : 11.81307676501932, - "99.9999" : 11.81307676501932, - "100.0" : 11.81307676501932 + "0.0" : 19.48264722596688, + "50.0" : 19.488354876861465, + "90.0" : 19.505869986793307, + "95.0" : 19.505869986793307, + "99.0" : 19.505869986793307, + "99.9" : 19.505869986793307, + "99.99" : 19.505869986793307, + "99.999" : 19.505869986793307, + "99.9999" : 19.505869986793307, + "100.0" : 19.505869986793307 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 11.758114656066427, - 11.78967041235971, - 11.784406185525992, - 11.81307676501932 + 19.486914415176994, + 19.505869986793307, + 19.48264722596688, + 19.489795338545935 ] ] }, @@ -1400,7 +1960,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1414,41 +1974,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "pure-jackson" }, "primaryMetric" : { - "score" : 10.441401348799962, - "scoreError" : 0.4257042267370256, + "score" : 9.97226934026093, + "scoreError" : 0.14099093351999584, "scoreConfidence" : [ - 10.015697122062937, - 10.867105575536987 + 9.831278406740934, + 10.113260273780925 ], "scorePercentiles" : { - "0.0" : 10.349816758614011, - "50.0" : 10.460552010194798, - "90.0" : 10.494684616196244, - "95.0" : 10.494684616196244, - "99.0" : 10.494684616196244, - "99.9" : 10.494684616196244, - "99.99" : 10.494684616196244, - "99.999" : 10.494684616196244, - "99.9999" : 10.494684616196244, - "100.0" : 10.494684616196244 + "0.0" : 9.943063947678294, + "50.0" : 9.978283708721236, + "90.0" : 9.98944599592296, + "95.0" : 9.98944599592296, + "99.0" : 9.98944599592296, + "99.9" : 9.98944599592296, + "99.99" : 9.98944599592296, + "99.999" : 9.98944599592296, + "99.9999" : 9.98944599592296, + "100.0" : 9.98944599592296 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 10.494684616196244, - 10.437536011173183, - 10.483568009216413, - 10.349816758614011 + 9.988476929796445, + 9.943063947678294, + 9.968090487646025, + 9.98944599592296 ] ] }, @@ -1456,7 +2016,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1470,41 +2030,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "circe" }, "primaryMetric" : { - "score" : 2.7059660558815626, - "scoreError" : 0.5529906205263425, + "score" : 2.1917481848188074, + "scoreError" : 0.19753919868090142, "scoreConfidence" : [ - 2.15297543535522, - 3.258956676407905 + 1.994208986137906, + 2.389287383499709 ], "scorePercentiles" : { - "0.0" : 2.5821394278212177, - "50.0" : 2.7386367255520128, - "90.0" : 2.7644513446010057, - "95.0" : 2.7644513446010057, - "99.0" : 2.7644513446010057, - "99.9" : 2.7644513446010057, - "99.99" : 2.7644513446010057, - "99.999" : 2.7644513446010057, - "99.9999" : 2.7644513446010057, - "100.0" : 2.7644513446010057 + "0.0" : 2.161174770863264, + "50.0" : 2.1931165391649596, + "90.0" : 2.2195848900820465, + "95.0" : 2.2195848900820465, + "99.0" : 2.2195848900820465, + "99.9" : 2.2195848900820465, + "99.99" : 2.2195848900820465, + "99.999" : 2.2195848900820465, + "99.9999" : 2.2195848900820465, + "100.0" : 2.2195848900820465 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 2.715383754518447, - 2.5821394278212177, - 2.7618896965855786, - 2.7644513446010057 + 2.2164629075394187, + 2.161174770863264, + 2.169770170790501, + 2.2195848900820465 ] ] }, @@ -1512,7 +2072,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1526,41 +2086,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "play-json" }, "primaryMetric" : { - "score" : 1.0644466719884713, - "scoreError" : 0.10828100329407624, + "score" : 1.0653568016661057, + "scoreError" : 0.10740651196183097, "scoreConfidence" : [ - 0.9561656686943951, - 1.1727276752825475 + 0.9579502897042748, + 1.1727633136279367 ], "scorePercentiles" : { - "0.0" : 1.0488479596654356, - "50.0" : 1.0605586721042153, - "90.0" : 1.0878213840800195, - "95.0" : 1.0878213840800195, - "99.0" : 1.0878213840800195, - "99.9" : 1.0878213840800195, - "99.99" : 1.0878213840800195, - "99.999" : 1.0878213840800195, - "99.9999" : 1.0878213840800195, - "100.0" : 1.0878213840800195 + "0.0" : 1.0433927257114266, + "50.0" : 1.0689022836621562, + "90.0" : 1.0802299136286833, + "95.0" : 1.0802299136286833, + "99.0" : 1.0802299136286833, + "99.9" : 1.0802299136286833, + "99.99" : 1.0802299136286833, + "99.999" : 1.0802299136286833, + "99.9999" : 1.0802299136286833, + "100.0" : 1.0802299136286833 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 1.0639069080349006, - 1.0488479596654356, - 1.05721043617353, - 1.0878213840800195 + 1.0802299136286833, + 1.0759743955128906, + 1.0618301718114218, + 1.0433927257114266 ] ] }, @@ -1568,7 +2128,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1582,41 +2142,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "spray-json" }, "primaryMetric" : { - "score" : 3.4253825528465027, - "scoreError" : 0.39600842164497063, + "score" : 2.949846107789613, + "scoreError" : 1.2234104146252418, "scoreConfidence" : [ - 3.029374131201532, - 3.8213909744914734 + 1.7264356931643714, + 4.173256522414855 ], "scorePercentiles" : { - "0.0" : 3.385092868844048, - "50.0" : 3.400474031886585, - "90.0" : 3.5154892787687935, - "95.0" : 3.5154892787687935, - "99.0" : 3.5154892787687935, - "99.9" : 3.5154892787687935, - "99.99" : 3.5154892787687935, - "99.999" : 3.5154892787687935, - "99.9999" : 3.5154892787687935, - "100.0" : 3.5154892787687935 + "0.0" : 2.668916069460295, + "50.0" : 3.0243580598737383, + "90.0" : 3.0817522419506798, + "95.0" : 3.0817522419506798, + "99.0" : 3.0817522419506798, + "99.9" : 3.0817522419506798, + "99.99" : 3.0817522419506798, + "99.999" : 3.0817522419506798, + "99.9999" : 3.0817522419506798, + "100.0" : 3.0817522419506798 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 3.5154892787687935, - 3.385092868844048, - 3.4123772610553913, - 3.388570802717778 + 3.0817522419506798, + 3.017081868987934, + 2.668916069460295, + 3.031634250759543 ] ] }, @@ -1624,7 +2184,7 @@ } }, { - "jmhVersion" : "1.36", + "jmhVersion" : "1.37", "benchmark" : "json.bench.JmhWriterBench.bench", "mode" : "thrpt", "threads" : 1, @@ -1638,41 +2198,41 @@ "vmName" : "OpenJDK 64-Bit Server VM", "vmVersion" : "17.0.7+7-LTS", "warmupIterations" : 4, - "warmupTime" : "5 s", + "warmupTime" : "1 s", "warmupBatchSize" : 1, "measurementIterations" : 4, - "measurementTime" : "5 s", + "measurementTime" : "1 s", "measurementBatchSize" : 1, "params" : { "jsonSize" : "32mb", "processorName" : "zio-json" }, "primaryMetric" : { - "score" : 7.347089488260391, - "scoreError" : 0.025160682828075858, + "score" : 7.332264236852336, + "scoreError" : 0.0252345687851438, "scoreConfidence" : [ - 7.321928805432315, - 7.372250171088466 + 7.307029668067193, + 7.35749880563748 ], "scorePercentiles" : { - "0.0" : 7.344136033159427, - "50.0" : 7.345870502020608, - "90.0" : 7.35248091584092, - "95.0" : 7.35248091584092, - "99.0" : 7.35248091584092, - "99.9" : 7.35248091584092, - "99.99" : 7.35248091584092, - "99.999" : 7.35248091584092, - "99.9999" : 7.35248091584092, - "100.0" : 7.35248091584092 + "0.0" : 7.32926086007502, + "50.0" : 7.330910436590052, + "90.0" : 7.337975214154221, + "95.0" : 7.337975214154221, + "99.0" : 7.337975214154221, + "99.9" : 7.337975214154221, + "99.99" : 7.337975214154221, + "99.999" : 7.337975214154221, + "99.9999" : 7.337975214154221, + "100.0" : 7.337975214154221 }, "scoreUnit" : "ops/s", "rawData" : [ [ - 7.344334413439188, - 7.347406590602027, - 7.35248091584092, - 7.344136033159427 + 7.330437252894339, + 7.337975214154221, + 7.32926086007502, + 7.331383620285766 ] ] }, diff --git a/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala b/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala index ecb5e3b6..8cc4c84f 100644 --- a/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala +++ b/modules/benchmarks/src/main/scala-2/json/bench/tethysjson/TethysBench.scala @@ -7,13 +7,21 @@ import tethys.jackson._ object TethysBench { - implicit val dataWriter: JsonWriter[Data] = tethys.derivation.semiauto.jsonWriter[Data] - implicit val dataReader: JsonReader[Data] = tethys.derivation.semiauto.jsonReader[Data] + implicit val dataWriter: JsonWriter[Data] = + tethys.derivation.semiauto.jsonWriter[Data] + implicit val dataReader: JsonReader[Data] = + tethys.derivation.semiauto.jsonReader[Data] + + object TethysDataProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = seq.asJson + override def read(json: String): Seq[Data] = ??? + } object TethysJacksonDataProcessor extends DataWriter with DataReader { override def write(seq: Seq[Data]): String = seq.asJson - override def read(json: String): Seq[Data] = json.jsonAs[Seq[Data]].toOption.get + override def read(json: String): Seq[Data] = + json.jsonAs[Seq[Data]].toOption.get } } diff --git a/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala b/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala index 18a22c21..2f5f8c29 100644 --- a/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala +++ b/modules/benchmarks/src/main/scala-3/json/bench/tethysjson/TethysBench.scala @@ -2,21 +2,28 @@ package json.bench.tethysjson import json.bench.model.Data import json.bench.{DataReader, DataWriter} -import tethys.* -import tethys.jackson.* +import tethys._ -/** - * Created by eld0727 on 21.04.17. +/** Created by eld0727 on 21.04.17. */ object TethysBench { - implicit val dataWriter: JsonWriter[Data] = JsonWriter.derived[Data] - implicit val dataReader: JsonReader[Data] = JsonReader.derived[Data] + implicit val dataWriter: JsonWriter[Seq[Data]] = + JsonWriter.iterableWriter(tethys.derivation.semiauto.jsonWriter[Data]) + implicit val dataReader: JsonReader[Seq[Data]] = + JsonReader.iterableReader(tethys.derivation.semiauto.jsonReader[Data]) + + object TethysDataProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = seq.asJson + override def read(json: String): Seq[Data] = ??? + } object TethysJacksonDataProcessor extends DataWriter with DataReader { + import tethys.jackson._ override def write(seq: Seq[Data]): String = seq.asJson - override def read(json: String): Seq[Data] = json.jsonAs[Seq[Data]].toOption.get + override def read(json: String): Seq[Data] = + json.jsonAs[Seq[Data]].toOption.get } } diff --git a/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala b/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala index a3ec4d3d..74d8484b 100644 --- a/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala +++ b/modules/benchmarks/src/main/scala/json/bench/BenchMarkdown.scala @@ -19,22 +19,24 @@ object BenchMarkdown { val benchmarksOrdering: Ordering[String] = Ordering.by[String, Int] { case "Parsing" | "json.bench.JmhReaderBench.bench" => 1 case "Writing" | "json.bench.JmhWriterBench.bench" => 2 - case _ => 3 + case _ => 3 } val processorsOrdering: Ordering[String] = Ordering.by[String, Int] { + case "tethys" => 0 case "tethys-jackson" => 1 - case "pure-jackson" => 2 - case "circe" => 3 - case "circe-jawn" => 4 - case "circe-jackson" => 5 + case "pure-jackson" => 2 + case "circe" => 3 + case "circe-jawn" => 4 + case "circe-jackson" => 5 + case "jsoniter" => 7 case "json4s-jackson" => 8 - case "json4s-native" => 9 - case "play-json" => 10 - case "spray-json" => 11 - case "pushka" => 12 - case "zio-json" => 13 - case _ => 13 + case "json4s-native" => 9 + case "play-json" => 10 + case "spray-json" => 11 + case "pushka" => 12 + case "zio-json" => 13 + case _ => 13 } val namesMapping = Map( @@ -42,17 +44,30 @@ object BenchMarkdown { "json.bench.JmhWriterBench.bench" -> "Writing" ) - implicit val mbScore: JsonReader[Either[String, Double]] = new JsonReader[Either[String, Double]] { - override def read(it: TokenIterator)(implicit fieldName: FieldName): Either[String, Double] = { - if(it.currentToken().isNumberValue) Right(Math.round(JsonReader.doubleReader.read(it) * 1000) / 1000.0) - else Left(JsonReader.stringReader.read(it)) + implicit val mbScore: JsonReader[Either[String, Double]] = + new JsonReader[Either[String, Double]] { + override def read( + it: TokenIterator + )(implicit fieldName: FieldName): Either[String, Double] = { + if (it.currentToken().isNumberValue) + Right(Math.round(JsonReader.doubleReader.read(it) * 1000) / 1000.0) + else Left(JsonReader.stringReader.read(it)) + } } - } - implicit val primaryMetricsReader: JsonReader[PrimaryMetrics] = jsonReader[PrimaryMetrics] + implicit val primaryMetricsReader: JsonReader[PrimaryMetrics] = + jsonReader[PrimaryMetrics] implicit val benchmarkReader: JsonReader[Benchmark] = jsonReader[Benchmark] - case class Benchmark(benchmark: String, mode: String, params: mutable.LinkedHashMap[String, String], primaryMetric: PrimaryMetrics) - case class PrimaryMetrics(score: Either[String, Double], scoreError: Either[String, Double]) + case class Benchmark( + benchmark: String, + mode: String, + params: mutable.LinkedHashMap[String, String], + primaryMetric: PrimaryMetrics + ) + case class PrimaryMetrics( + score: Either[String, Double], + scoreError: Either[String, Double] + ) def readBenchmarks(dir: String, file: String): Seq[Benchmark] = { val jhmResultsPath = Paths.get(dir, file) @@ -61,15 +76,22 @@ object BenchMarkdown { } def main(args: Array[String]): Unit = { - val List(dir) = args.toList - val benchs = readBenchmarks(dir, "jmh-reader.json") ++ readBenchmarks(dir, "jmh-writer.json") + val dir = util.Try(args(0)).getOrElse(".") + val benchs = readBenchmarks(dir, "jmh-reader.json") ++ readBenchmarks( + dir, + "jmh-writer.json" + ) val grouped = benchs.groupBy(_.benchmark) val mainTables = grouped.toList.sortBy(_._1)(benchmarksOrdering).map { case (name, benchmarks) => - val rows = benchmarks.map(_.params(nameColumn)).distinct.sorted(processorsOrdering) + val rows = benchmarks + .map(_.params(nameColumn)) + .distinct + .sorted(processorsOrdering) val colls = benchmarks.map(_.params(sizeColumn)).distinct val data = benchmarks.map { b => - (b.params(nameColumn), b.params(sizeColumn)) -> b.primaryMetric.score.fold(identity, _.toString) + (b.params(nameColumn), b.params(sizeColumn)) -> b.primaryMetric.score + .fold(identity, _.toString) }.toMap s""" @@ -87,7 +109,7 @@ object BenchMarkdown { private def chart(name: String, bs: Seq[Benchmark], dir: String): String = { val title = namesMapping.getOrElse(name, name) val images = Paths.get(dir, "images") - if(Files.notExists(images)) { + if (Files.notExists(images)) { Files.createDirectory(images) } val imgPath = dir + "/images/" + title + "Performance" @@ -98,16 +120,25 @@ object BenchMarkdown { """.stripMargin } - private def table(rows: Seq[String], columns: Seq[String], data: Map[(String, String), String]): String = { + private def table( + rows: Seq[String], + columns: Seq[String], + data: Map[(String, String), String] + ): String = { val header = columns.foldLeft("name \\ size")(_ + "|" + _) val line = columns.map(_ => "---").foldLeft("---")(_ + "|" + _) val dataLines = rows.map { row => - columns.map(col => data.getOrElse((row, col), " ")).foldLeft(row)(_ + "|" + _) + columns + .map(col => data.getOrElse((row, col), " ")) + .foldLeft(row)(_ + "|" + _) } dataLines.foldLeft(header + "\n" + line)(_ + "\n" + _) } - private def buildPerformanceChart(title: String, benchmarks: Seq[Benchmark]): CategoryChart = { + private def buildPerformanceChart( + title: String, + benchmarks: Seq[Benchmark] + ): CategoryChart = { val chart: CategoryChart = new CategoryChartBuilder() .width(940) .height(400) @@ -116,12 +147,15 @@ object BenchMarkdown { .xAxisTitle("size") .build() - val maxs = benchmarks.groupBy(_.params(sizeColumn)).map { - case (size, bs) => size -> bs.map(_.primaryMetric.score.fold(_ => 0.0, identity)).max + val maxs = benchmarks.groupBy(_.params(sizeColumn)).map { case (size, bs) => + size -> bs.map(_.primaryMetric.score.fold(_ => 0.0, identity)).max } - benchmarks.groupBy(_.params(nameColumn)).toList.sortBy(_._1)(processorsOrdering).foreach { - case (name, bs) => + benchmarks + .groupBy(_.params(nameColumn)) + .toList + .sortBy(_._1)(processorsOrdering) + .foreach { case (name, bs) => import scala.collection.JavaConverters._ val data = bs.map { b => @@ -131,7 +165,7 @@ object BenchMarkdown { val xData = data.map(_._1).asJava val yData = data.map(t => Double.box(t._2)).asJava chart.addSeries(name, xData, yData) - } + } chart } diff --git a/modules/benchmarks/src/main/scala/json/bench/DataReader.scala b/modules/benchmarks/src/main/scala/json/bench/DataReader.scala index 151926a2..57ecabfc 100644 --- a/modules/benchmarks/src/main/scala/json/bench/DataReader.scala +++ b/modules/benchmarks/src/main/scala/json/bench/DataReader.scala @@ -3,6 +3,7 @@ package json.bench import json.bench.circe.CirceBench import json.bench.handwritten.HandwrittenBench import json.bench.json4s.Json4sBench +import json.bench.jsoniter.JsoniterBench import json.bench.model.Data import json.bench.play.PlayBench import json.bench.spray.SprayBench @@ -16,9 +17,11 @@ trait DataReader { object DataReader { val instances: Map[String, DataReader] = Map[String, DataReader]( "tethys-jackson" -> TethysBench.TethysJacksonDataProcessor, + "tethys" -> TethysBench.TethysDataProcessor, "pure-jackson" -> HandwrittenBench.HandwrittenJacksonDataProcessor, "circe-jawn" -> CirceBench.CirceJawnDataReader, "circe-jackson" -> CirceBench.CirceJacksonDataReader, + "jsoniter" -> JsoniterBench.JsoniterProcessor, "json4s-jackson" -> Json4sBench.Json4sJacksonDataProcessor, "json4s-native" -> Json4sBench.Json4sNativeDataProcessor, "play-json" -> PlayBench.PlayDataProcessor, diff --git a/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala b/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala index a54654bc..6f3a5e24 100644 --- a/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala +++ b/modules/benchmarks/src/main/scala/json/bench/DataWriter.scala @@ -3,6 +3,7 @@ package json.bench import json.bench.circe.CirceBench import json.bench.handwritten.HandwrittenBench import json.bench.json4s.Json4sBench +import json.bench.jsoniter.JsoniterBench import json.bench.model.Data import json.bench.play.PlayBench import json.bench.spray.SprayBench @@ -16,12 +17,14 @@ trait DataWriter { object DataWriter { val instances: Map[String, DataWriter] = Map( "tethys-jackson" -> TethysBench.TethysJacksonDataProcessor, + "tethys" -> TethysBench.TethysDataProcessor, "pure-jackson" -> HandwrittenBench.HandwrittenJacksonDataProcessor, "circe" -> CirceBench.CirceDataWriter, "java.lang.StringBuilder" -> HandwrittenBench.HandwrittenJavaDataWriter, "scala.StringBuilder" -> HandwrittenBench.HandwrittenScalaDataWriter, "json4s-jackson" -> Json4sBench.Json4sJacksonDataProcessor, "json4s-native" -> Json4sBench.Json4sNativeDataProcessor, + "jsoniter" -> JsoniterBench.JsoniterProcessor, "play-json" -> PlayBench.PlayDataProcessor, "spray-json" -> SprayBench.SprayDataProcessor, "zio-json" -> ZIOJsonBench.ZIOJsonDataProcesser diff --git a/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala b/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala index e32e0131..6e9346a8 100644 --- a/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/JmhReaderBench.scala @@ -13,40 +13,44 @@ import org.openjdk.jmh.annotations.{State, _} @Fork(value = 1, jvmArgsAppend = Array("-Xms1G", "-Xmx1G")) @State(Scope.Benchmark) class JmhReaderBench { - @Param(Array( - "128b", - "1kb", - "128kb", - "1mb", - "32mb" - )) + @Param( + Array( + "128b", + "1kb", + "128kb", + "1mb", + "32mb" + ) + ) var jsonSize: String = _ val seed = 10000 - var data: String =_ + var data: String = _ @Setup(Level.Trial) def setup(): Unit = { val entities = jsonSize match { - case "128b" => Data.dataSamples(1, seed) - case "1kb" => Data.dataSamples(8, seed) + case "128b" => Data.dataSamples(1, seed) + case "1kb" => Data.dataSamples(8, seed) case "128kb" => Data.dataSamples(128 * 8, seed) - case "1mb" => Data.dataSamples(8 * 128 * 8, seed) - case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) + case "1mb" => Data.dataSamples(8 * 128 * 8, seed) + case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) } data = TethysJacksonDataProcessor.write(entities) } - @Param(Array( - "tethys-jackson", - "pure-jackson", - "circe-jawn", - "circe-jackson", - "play-json", - "spray-json", - "zio-json" - )) + @Param( + Array( + "tethys-jackson", + "pure-jackson", + "circe-jawn", + "circe-jackson", + "play-json", + "spray-json", + "zio-json" + ) + ) var processorName: String = _ @Benchmark diff --git a/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala b/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala index 60bcae5c..b1f6ec4a 100644 --- a/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/JmhWriterBench.scala @@ -7,18 +7,20 @@ import org.openjdk.jmh.annotations._ @BenchmarkMode(Array(Mode.Throughput)) @OutputTimeUnit(TimeUnit.SECONDS) -@Warmup(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 4, time = 5, timeUnit = TimeUnit.SECONDS) +@Warmup(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) @Fork(value = 1, jvmArgsAppend = Array("-Xms1G", "-Xmx1G")) @State(Scope.Benchmark) class JmhWriterBench { - @Param(Array( - "128b", - "1kb", - "128kb", - "1mb", - "32mb" - )) + @Param( + Array( + "128b", + "1kb", + "128kb", + "1mb", + "32mb" + ) + ) var jsonSize: String = _ val seed = 10000 @@ -27,22 +29,26 @@ class JmhWriterBench { @Setup(Level.Trial) def setup(): Unit = { data = jsonSize match { - case "128b" => Data.dataSamples(1, seed) - case "1kb" => Data.dataSamples(8, seed) + case "128b" => Data.dataSamples(1, seed) + case "1kb" => Data.dataSamples(8, seed) case "128kb" => Data.dataSamples(128 * 8, seed) - case "1mb" => Data.dataSamples(8 * 128 * 8, seed) - case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) + case "1mb" => Data.dataSamples(8 * 128 * 8, seed) + case "32mb" => Data.dataSamples(32 * 8 * 128 * 8, seed) } } - @Param(Array( - "tethys-jackson", - "pure-jackson", - "circe", - "play-json", - "spray-json", - "zio-json" - )) + @Param( + Array( + "tethys", + "tethys-jackson", + "jsoniter", + "pure-jackson", + "circe", + "play-json", + "spray-json", + "zio-json" + ) + ) var processorName: String = _ @Benchmark diff --git a/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala b/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala index 87f8a2e6..1527cc68 100644 --- a/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/circe/CirceBench.scala @@ -15,10 +15,12 @@ object CirceBench { } object CirceJawnDataReader extends DataReader { - override def read(json: String): Seq[Data] = jawn.decode[Seq[Data]](json).toOption.get + override def read(json: String): Seq[Data] = + jawn.decode[Seq[Data]](json).toOption.get } object CirceJacksonDataReader extends DataReader { - override def read(json: String): Seq[Data] = jackson.decode[Seq[Data]](json).toOption.get + override def read(json: String): Seq[Data] = + jackson.decode[Seq[Data]](json).toOption.get } } diff --git a/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala b/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala index ce5c3656..c1a17b71 100644 --- a/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/json4s/Json4sBench.scala @@ -8,14 +8,18 @@ object Json4sBench { implicit val format: DefaultFormats.type = DefaultFormats object Json4sNativeDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = org.json4s.native.Serialization.write(seq) + override def write(seq: Seq[Data]): String = + org.json4s.native.Serialization.write(seq) - override def read(json: String): Seq[Data] = org.json4s.native.parseJson(json).extract[Seq[Data]] + override def read(json: String): Seq[Data] = + org.json4s.native.parseJson(json).extract[Seq[Data]] } object Json4sJacksonDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = org.json4s.jackson.Serialization.write(seq) + override def write(seq: Seq[Data]): String = + org.json4s.jackson.Serialization.write(seq) - override def read(json: String): Seq[Data] = org.json4s.jackson.parseJson(json).extract[Seq[Data]] + override def read(json: String): Seq[Data] = + org.json4s.jackson.parseJson(json).extract[Seq[Data]] } } diff --git a/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala b/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala new file mode 100644 index 00000000..fa6cbf19 --- /dev/null +++ b/modules/benchmarks/src/main/scala/json/bench/jsoniter/JsoniterBench.scala @@ -0,0 +1,17 @@ +package json.bench.jsoniter + +import json.bench.model.Data + +import com.github.plokhotnyuk.jsoniter_scala.macros._ +import com.github.plokhotnyuk.jsoniter_scala.core._ +import json.bench.{DataReader, DataWriter} + +object JsoniterBench { + implicit val dataWrites: JsonValueCodec[Seq[Data]] = JsonCodecMaker.make + + object JsoniterProcessor extends DataWriter with DataReader { + override def write(seq: Seq[Data]): String = writeToString(seq) + + override def read(json: String): Seq[Data] = readFromString[Seq[Data]](json) + } +} diff --git a/modules/benchmarks/src/main/scala/json/bench/model/Data.scala b/modules/benchmarks/src/main/scala/json/bench/model/Data.scala index a171da38..b6674018 100644 --- a/modules/benchmarks/src/main/scala/json/bench/model/Data.scala +++ b/modules/benchmarks/src/main/scala/json/bench/model/Data.scala @@ -2,16 +2,22 @@ package json.bench.model import scala.util.Random -case class Data(string: String, - int: Int, - boolean: Boolean, - bigDecimal: BigDecimal, - seqInt: Seq[Int], - mapStringInt: Map[String, Int]) +case class Data( + string: String, + int: Int, + boolean: Boolean, + bigDecimal: BigDecimal, + seqInt: Seq[Int], + mapStringInt: Map[String, Int] +) object Data { - def samples[JAst](dataBuilder: DataBuilder[JAst], count: Int, seed: Int): JAst = { + def samples[JAst]( + dataBuilder: DataBuilder[JAst], + count: Int, + seed: Int + ): JAst = { val asts = dataSamples(count, seed).map(dataBuilder.ast) dataBuilder.array(asts) } @@ -27,7 +33,7 @@ object Data { (1 to count).toList.map { i => val flag = (i % 2) == 0 - //128 bytes entity + // 128 bytes entity Data( // 2 bytes string = rndString(6 - (if (flag) 0 else 1)), // 9 + 8 (7) + 1 bytes int = rndInt(3), // 6 + 3 + 1 bytes diff --git a/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala b/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala index 085c61f8..7316cf45 100644 --- a/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/play/PlayBench.scala @@ -10,7 +10,8 @@ object PlayBench { implicit val dataReads: Reads[Data] = Json.reads[Data] object PlayDataProcessor extends DataWriter with DataReader { - override def write(seq: Seq[Data]): String = Json.stringify(Json.toJson(seq)) + override def write(seq: Seq[Data]): String = + Json.stringify(Json.toJson(seq)) override def read(json: String): Seq[Data] = Json.parse(json).as[Seq[Data]] } diff --git a/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala b/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala index a5f87a54..849fd6de 100644 --- a/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala +++ b/modules/benchmarks/src/main/scala/json/bench/spray/SprayBench.scala @@ -12,7 +12,8 @@ object SprayBench { object SprayDataProcessor extends DataWriter with DataReader { override def write(seq: Seq[Data]): String = seq.toJson.compactPrint - override def read(json: String): Seq[Data] = json.parseJson.convertTo[Seq[Data]] + override def read(json: String): Seq[Data] = + json.parseJson.convertTo[Seq[Data]] } } diff --git a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala index d71c6a98..e4046454 100644 --- a/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala +++ b/modules/core/src/main/scala/tethys/writers/tokens/TokenWriter.scala @@ -1,6 +1,5 @@ package tethys.writers.tokens - trait TokenWriter { /** Writes a JSON array start marker [ . */