From 67be8901bf14a730d65555c59602ac837b2f9e9d Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Fri, 26 Jul 2024 15:11:23 +0200 Subject: [PATCH 1/6] Move NativeJsonInput/Output from Udash, add possibility to customize serialization format for time and Long --- .../nativejs/NativeFormatOptions.scala | 40 +++++ .../nativejs/NativeJsonInput.scala | 160 ++++++++++++++++++ .../nativejs/NativeJsonOutput.scala | 98 +++++++++++ .../nativejs/NativeJsonInputOutputTest.scala | 56 ++++++ .../serialization/json/WrappedJson.scala | 18 ++ .../json/JsonStringInputOutputTest.scala | 6 + 6 files changed, 378 insertions(+) create mode 100644 core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala create mode 100644 core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala create mode 100644 core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala create mode 100644 core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala create mode 100644 core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala new file mode 100644 index 000000000..1b1ae93b6 --- /dev/null +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala @@ -0,0 +1,40 @@ +package com.avsystem.commons +package serialization.nativejs + +import com.avsystem.commons.misc.{AbstractValueEnum, AbstractValueEnumCompanion, EnumCtx} + +/** + * Specifies format used by `NativeJsonOutput.writeLong` / `NativeJsonInput.readLong` + * to represent [[Long]]. JS does not support 64-bit representation. + */ +final class NativeLongFormat(implicit ctx: EnumCtx) extends AbstractValueEnum +object NativeLongFormat extends AbstractValueEnumCompanion[NativeLongFormat] { + final val RawString: Value = new NativeLongFormat + final val JsNumber: Value = new NativeLongFormat + final val JsBigInt: Value = new NativeLongFormat +} + +/** + * Specifies format used by `NativeJsonOutput.writeTimestamp` / `NativeJsonInput.readTimestamp` + * to represent timestamps. + */ +final class NativeDateFormat(implicit ctx: EnumCtx) extends AbstractValueEnum +object NativeDateFormat extends AbstractValueEnumCompanion[NativeDateFormat] { + final val RawString: Value = new NativeDateFormat + final val JsNumber: Value = new NativeDateFormat + final val JsDate: Value = new NativeDateFormat +} + +/** + * Adjusts format produced by [[NativeJsonOutput]]. + * + * @param longFormat format used to [[Long]] + * @param dateFormat format used to represent timestamps + */ +final case class NativeFormatOptions( + longFormat: NativeLongFormat = NativeLongFormat.RawString, + dateFormat: NativeDateFormat = NativeDateFormat.RawString, +) +object NativeFormatOptions { + final val RawString = NativeFormatOptions() +} diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala new file mode 100644 index 000000000..5eba8b83c --- /dev/null +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala @@ -0,0 +1,160 @@ +package com.avsystem.commons +package serialization.nativejs + +import com.avsystem.commons.annotation.explicitGenerics +import com.avsystem.commons.serialization.GenCodec.ReadFailure +import com.avsystem.commons.serialization.* +import com.avsystem.commons.serialization.json.RawJson + +import scala.scalajs.js +import scala.scalajs.js.JSON + +class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends InputAndSimpleInput { self => + private def read[T](expected: String)(matcher: PartialFunction[Any, T]): T = + matcher.applyOrElse(value, (_: Any) => throw new ReadFailure(s"$expected expected.")) + + override def readNull(): Boolean = + value == null + + override def readString(): String = + read("String") { + case s: String => s + } + + override def readDouble(): Double = + read("Double") { + case v: Double => v + } + + override def readInt(): Int = + read("Int") { + case v: Int => v + } + + override def readLong(): Long = { + def longFromString(s: String): Long = + try s.toLong + catch { + case e: NumberFormatException => throw new ReadFailure(s"Cannot read Long", e) + } + (value: Any) match { + case s: String => longFromString(s) + case i: Int => i + case d: Double if d.isWhole => d.toLong + case b: js.BigInt => longFromString(b.toString) + case b if js.typeOf(b) == "bigint" => longFromString(b.asInstanceOf[js.BigInt].toString) + case o => throw new ReadFailure(s"Cannot read Long, got: ${js.typeOf(o)}") + } + } + + override def readBigInt(): BigInt = { + def fail = throw new ReadFailure(s"BigInt expected.") + (value: Any) match { + case s: String => + try BigInt(s) + catch { + case _: NumberFormatException => fail + } + case i: Int => BigInt(i) + case d: Double if d.isWhole => BigInt(d.toLong) + case _ => fail + } + } + + override def readBigDecimal(): BigDecimal = { + def fail = throw new ReadFailure(s"BigDecimal expected.") + (value: Any) match { + case s: String => + try BigDecimal(s) + catch { + case _: NumberFormatException => fail + } + case i: Int => BigDecimal(i) + case d: Double => BigDecimal(d) + case _ => fail + } + } + + override def readBoolean(): Boolean = + read("Boolean") { + case v: Boolean => v + } + + override def readList(): ListInput = + read("List") { + case array: js.Array[js.Any @unchecked] => new JsonListInput(array, options) + } + + override def readObject(): ObjectInput = + read("Object") { + case obj: js.Object => new JsonObjectInput(obj.asInstanceOf[js.Dictionary[js.Any]], options) + } + + override def readTimestamp(): Long = options.dateFormat match { + case NativeDateFormat.RawString | NativeDateFormat.JsNumber => + readLong() // lenient behaviour, accept any value that can be interpreted as Long + case NativeDateFormat.JsDate => + read("js.Date") { + case v: js.Date => v.getTime().toLong + } + } + + override def skip(): Unit = () + + override def readBinary(): Array[Byte] = + readList().iterator(_.readSimple().readInt().toByte).toArray + + override def readCustom[T](typeMarker: TypeMarker[T]): Opt[T] = + typeMarker match { + case RawJson => JSON.stringify(readRaw()).opt + case _ => Opt.Empty + } + + def readRaw(): js.Any = value +} + +final class JsonListInput(list: js.Array[js.Any], options: NativeFormatOptions) extends ListInput { + var it = 0 + + override def hasNext: Boolean = + it < list.length + + override def nextElement(): Input = { + js.BigInt + val in = new NativeJsonInput(list(it), options) + it += 1 + in + } +} + +final class JsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOptions) extends ObjectInput { + val it = dict.keysIterator + + override def hasNext: Boolean = + it.hasNext + + override def peekField(name: String): Opt[FieldInput] = + if (dict.contains(name)) Opt(new NativeJsonFieldInput(name, dict(name), options)) else Opt.Empty + + override def nextField(): FieldInput = { + val key = it.next() + new NativeJsonFieldInput(key, dict.apply(key), options) + } +} + +final class NativeJsonFieldInput( + val fieldName: String, + value: js.Any, + options: NativeFormatOptions, +) extends NativeJsonInput(value, options) + with FieldInput + +object NativeJsonInput { + @explicitGenerics + def read[T: GenCodec](value: js.Any, options: NativeFormatOptions = NativeFormatOptions.RawString): T = + GenCodec.read[T](new NativeJsonInput(value, options)) + + @explicitGenerics + def readString[T: GenCodec](value: String, options: NativeFormatOptions = NativeFormatOptions.RawString): T = + read[T](JSON.parse(value), options) +} diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala new file mode 100644 index 000000000..da2d9acc4 --- /dev/null +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala @@ -0,0 +1,98 @@ +package com.avsystem.commons +package serialization.nativejs + +import com.avsystem.commons.serialization._ +import com.avsystem.commons.serialization.json.RawJson + +import scala.scalajs.js +import scala.scalajs.js.JSON + +final class NativeJsonOutput( + valueConsumer: js.Any => Unit, + options: NativeFormatOptions, +) extends OutputAndSimpleOutput { + + override def writeNull(): Unit = + valueConsumer(null) + + override def writeString(str: String): Unit = + valueConsumer(str) + + override def writeDouble(double: Double): Unit = + valueConsumer(double) + + override def writeInt(int: Int): Unit = + valueConsumer(int) + + override def writeLong(long: Long): Unit = options.longFormat match { + case NativeLongFormat.RawString => writeString(long.toString) + case NativeLongFormat.JsNumber => writeDouble(long.toDouble) + case NativeLongFormat.JsBigInt => writeRaw(js.BigInt(long.toString)) + } + + override def writeBigInt(bigInt: BigInt): Unit = + writeString(bigInt.toString) + + override def writeBigDecimal(bigDecimal: BigDecimal): Unit = + writeString(bigDecimal.toString) + + override def writeBoolean(boolean: Boolean): Unit = + valueConsumer(boolean) + + override def writeList(): ListOutput = + new NativeJsonListOutput(valueConsumer, options) + + override def writeObject(): ObjectOutput = + new NativeJsonObjectOutput(valueConsumer, options) + + override def writeBinary(binary: Array[Byte]): Unit = { + val l = writeList() + binary.foreach(b => l.writeElement().writeSimple().writeInt(b)) + l.finish() + } + + override def writeTimestamp(millis: Long): Unit = options.dateFormat match { + case NativeDateFormat.RawString => writeString(millis.toString) + case NativeDateFormat.JsNumber => writeDouble(millis.toDouble) + case NativeDateFormat.JsDate => writeRaw(new js.Date(millis.toDouble)) + } + + override def writeCustom[T](typeMarker: TypeMarker[T], value: T): Boolean = + typeMarker match { + case RawJson => writeRaw(JSON.parse(value)); true + case _ => false + } + + def writeRaw(raw: js.Any): Unit = valueConsumer(raw) +} + +final class NativeJsonListOutput( + valueConsumer: js.Any => Unit, + options: NativeFormatOptions, +) extends ListOutput { + private val builder = new js.Array[js.Any]() + + override def writeElement(): Output = new NativeJsonOutput(el => builder.append(el), options) + override def finish(): Unit = valueConsumer(builder) +} + +final class NativeJsonObjectOutput( + valueConsumer: js.Any => Unit, + options: NativeFormatOptions, +) extends ObjectOutput { + private val builder = js.Dictionary.empty[js.Any] + + override def writeField(key: String): Output = new NativeJsonOutput(el => builder(key) = el, options) + override def finish(): Unit = valueConsumer(builder) +} + +object NativeJsonOutput { + def write[T: GenCodec](value: T, options: NativeFormatOptions = NativeFormatOptions.RawString): js.Any = { + var result: js.Any = null + GenCodec.write(new NativeJsonOutput(value => result = value, options), value) + result + } + + def writeAsString[T: GenCodec](value: T, options: NativeFormatOptions = NativeFormatOptions.RawString): String = + JSON.stringify(write(value, options)) +} diff --git a/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala new file mode 100644 index 000000000..52a95c5b5 --- /dev/null +++ b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala @@ -0,0 +1,56 @@ +package com.avsystem.commons +package serialization.nativejs + +import com.avsystem.commons.misc.Timestamp +import com.avsystem.commons.serialization.json.WrappedJson +import com.avsystem.commons.serialization.{GenCodec, HasGenCodec} +import org.scalatest.funsuite.AnyFunSuite + +object NativeJsonInputOutputTest { + + case class TestModel( + str: String, + int: Int, + long: Long, + time: Timestamp, + list: Seq[Int], + map: Map[String, String], + rawJson: WrappedJson, + ) + object TestModel extends HasGenCodec[TestModel] +} + +class NativeJsonInputOutputTest extends AnyFunSuite { + import NativeJsonInputOutputTest._ + + test("Bilateral serialization - raw string options") { + val options = NativeFormatOptions.RawString + bilateralTyped(testModel, options) + } + + test("Bilateral serialization - number options") { + val options = NativeFormatOptions(longFormat = NativeLongFormat.JsNumber, dateFormat = NativeDateFormat.JsNumber) + bilateralTyped(testModel, options) + } + + test("Bilateral serialization - typed options") { + val options = NativeFormatOptions(longFormat = NativeLongFormat.JsBigInt, dateFormat = NativeDateFormat.JsDate) + bilateralTyped(testModel, options) + } + + private def testModel: TestModel = TestModel( + str = "abc", + int = 123, + long = 10_000_000_123L, + time = Timestamp.now(), + list = Seq(1, 2, 3), + map = Map("Abc" -> "1", "xyz" -> "10000"), + rawJson = WrappedJson("""{"a":1,"b":"c"}"""), + ) + + private def bilateralTyped[T: GenCodec](input: T, options: NativeFormatOptions): Unit = { + val raw = NativeJsonOutput.write(input, options) + val deserialized = NativeJsonInput.read[T](raw, options) + assert(deserialized == input) + } +} diff --git a/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala b/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala new file mode 100644 index 000000000..ca11614d3 --- /dev/null +++ b/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala @@ -0,0 +1,18 @@ +package com.avsystem.commons +package serialization.json + +import com.avsystem.commons.serialization.GenCodec + +/** + * Wrapper for raw JSON string + * + * It will be serialized as JSON value when used with [[com.avsystem.commons.serialization.Output]] supporting + * [[RawJson]] marker. + */ +final case class WrappedJson(value: String) extends AnyVal +object WrappedJson { + implicit val codec: GenCodec[WrappedJson] = GenCodec.create( + in => WrappedJson(in.readCustom(RawJson).getOrElse(in.readSimple().readString())), + (out, v) => if (!out.writeCustom(RawJson, v.value)) out.writeSimple().writeString(v.value), + ) +} diff --git a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala index 4a6c1a8bb..217a1ed8e 100644 --- a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala +++ b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala @@ -60,6 +60,12 @@ class JsonStringInputOutputTest extends AnyFunSuite with SerializationTestUtils assert(resBuilder.result() == jsons) } + test("WrappedJson") { + val json = "{\"a\": 123, \"b\": 3.14}" + assert(JsonStringOutput.write(WrappedJson(json)) == json) + assert(JsonStringInput.read[WrappedJson](json) == WrappedJson(json)) + } + def roundtrip[T: GenCodec](name: String)(values: T*)(implicit pos: Position): Unit = { test(name) { val serialized = values.map(write[T](_)) From c8ec804b248e74a46ad329c011d3bb5f655b1744 Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Sat, 3 Aug 2024 12:05:19 +0200 Subject: [PATCH 2/6] Improve binary serialization, add support for typed BigInt --- .../nativejs/NativeFormatOptions.scala | 14 +++++ .../nativejs/NativeJsonInput.scala | 50 ++++++++++-------- .../nativejs/NativeJsonOutput.scala | 11 ++-- .../nativejs/NativeJsonInputOutputTest.scala | 52 ++++++++++++++----- .../serialization/json/WrappedJson.scala | 3 +- .../json/JsonStringInputOutputTest.scala | 2 +- 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala index 1b1ae93b6..9e87a6d9d 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala @@ -25,15 +25,29 @@ object NativeDateFormat extends AbstractValueEnumCompanion[NativeDateFormat] { final val JsDate: Value = new NativeDateFormat } +/** + * Specifies format used by `NativeJsonOutput.writeBigInt` / `NativeJsonInput.readBigInt` + * to represent [[BigInt]]. + * + * Note that [[scala.scalajs.js.JSON.stringify]] does not know how to serialize a BigInt and throws an error + */ +final class NativeBitIntFormat(implicit ctx: EnumCtx) extends AbstractValueEnum +object NativeBitIntFormat extends AbstractValueEnumCompanion[NativeBitIntFormat] { + final val RawString: Value = new NativeBitIntFormat + final val JsBigInt: Value = new NativeBitIntFormat +} + /** * Adjusts format produced by [[NativeJsonOutput]]. * * @param longFormat format used to [[Long]] * @param dateFormat format used to represent timestamps + * @param bigIntFormat format used to represent [[BigInt]] */ final case class NativeFormatOptions( longFormat: NativeLongFormat = NativeLongFormat.RawString, dateFormat: NativeDateFormat = NativeDateFormat.RawString, + bigIntFormat: NativeBitIntFormat = NativeBitIntFormat.RawString, ) object NativeFormatOptions { final val RawString = NativeFormatOptions() diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala index 5eba8b83c..e949ced9b 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala @@ -32,46 +32,49 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input } override def readLong(): Long = { - def longFromString(s: String): Long = + def fromString(s: String): Long = try s.toLong catch { case e: NumberFormatException => throw new ReadFailure(s"Cannot read Long", e) } (value: Any) match { - case s: String => longFromString(s) + case s: String => fromString(s) case i: Int => i case d: Double if d.isWhole => d.toLong - case b: js.BigInt => longFromString(b.toString) - case b if js.typeOf(b) == "bigint" => longFromString(b.asInstanceOf[js.BigInt].toString) + case b: js.BigInt => fromString(b.toString) + case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) case o => throw new ReadFailure(s"Cannot read Long, got: ${js.typeOf(o)}") } } override def readBigInt(): BigInt = { - def fail = throw new ReadFailure(s"BigInt expected.") + def fromString(s: String): BigInt = + try BigInt(s) + catch { + case e: NumberFormatException => throw new ReadFailure(s"Cannot read BitInt", e) + } + (value: Any) match { - case s: String => - try BigInt(s) - catch { - case _: NumberFormatException => fail - } + case s: String => fromString(s) case i: Int => BigInt(i) case d: Double if d.isWhole => BigInt(d.toLong) - case _ => fail + case b: js.BigInt => fromString(b.toString) + case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) + case o => throw new ReadFailure(s"Cannot read BitInt, got: ${js.typeOf(o)}") } } override def readBigDecimal(): BigDecimal = { - def fail = throw new ReadFailure(s"BigDecimal expected.") + def fromString(s: String): BigDecimal = + try BigDecimal(s) + catch { + case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigDecimal", e) + } (value: Any) match { - case s: String => - try BigDecimal(s) - catch { - case _: NumberFormatException => fail - } + case s: String => fromString(s) case i: Int => BigDecimal(i) case d: Double => BigDecimal(d) - case _ => fail + case o => throw new ReadFailure(s"Cannot read BigDecimal, got: ${js.typeOf(o)}") } } @@ -102,7 +105,9 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input override def skip(): Unit = () override def readBinary(): Array[Byte] = - readList().iterator(_.readSimple().readInt().toByte).toArray + read("List") { + case array: js.Array[Int @unchecked] => array.iterator.map(_.toByte).toArray + } override def readCustom[T](typeMarker: TypeMarker[T]): Opt[T] = typeMarker match { @@ -120,7 +125,6 @@ final class JsonListInput(list: js.Array[js.Any], options: NativeFormatOptions) it < list.length override def nextElement(): Input = { - js.BigInt val in = new NativeJsonInput(list(it), options) it += 1 in @@ -128,7 +132,7 @@ final class JsonListInput(list: js.Array[js.Any], options: NativeFormatOptions) } final class JsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOptions) extends ObjectInput { - val it = dict.keysIterator + val it = dict.iterator override def hasNext: Boolean = it.hasNext @@ -137,8 +141,8 @@ final class JsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOp if (dict.contains(name)) Opt(new NativeJsonFieldInput(name, dict(name), options)) else Opt.Empty override def nextField(): FieldInput = { - val key = it.next() - new NativeJsonFieldInput(key, dict.apply(key), options) + val (key, value) = it.next() + new NativeJsonFieldInput(key, value, options) } } diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala index da2d9acc4..43891727a 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala @@ -30,8 +30,10 @@ final class NativeJsonOutput( case NativeLongFormat.JsBigInt => writeRaw(js.BigInt(long.toString)) } - override def writeBigInt(bigInt: BigInt): Unit = - writeString(bigInt.toString) + override def writeBigInt(bigInt: BigInt): Unit = options.bigIntFormat match { + case NativeBitIntFormat.RawString => writeString(bigInt.toString) + case NativeBitIntFormat.JsBigInt => writeRaw(js.BigInt(bigInt.toString)) + } override def writeBigDecimal(bigDecimal: BigDecimal): Unit = writeString(bigDecimal.toString) @@ -46,9 +48,8 @@ final class NativeJsonOutput( new NativeJsonObjectOutput(valueConsumer, options) override def writeBinary(binary: Array[Byte]): Unit = { - val l = writeList() - binary.foreach(b => l.writeElement().writeSimple().writeInt(b)) - l.finish() + import js.JSConverters._ + valueConsumer(binary.toJSArray) } override def writeTimestamp(millis: Long): Unit = options.dateFormat match { diff --git a/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala index 52a95c5b5..9d7ba3e95 100644 --- a/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala +++ b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala @@ -1,7 +1,7 @@ package com.avsystem.commons package serialization.nativejs -import com.avsystem.commons.misc.Timestamp +import com.avsystem.commons.misc.{Bytes, Timestamp} import com.avsystem.commons.serialization.json.WrappedJson import com.avsystem.commons.serialization.{GenCodec, HasGenCodec} import org.scalatest.funsuite.AnyFunSuite @@ -15,6 +15,8 @@ object NativeJsonInputOutputTest { time: Timestamp, list: Seq[Int], map: Map[String, String], + binary: Bytes, + bigInt: BigInt, rawJson: WrappedJson, ) object TestModel extends HasGenCodec[TestModel] @@ -23,19 +25,37 @@ object NativeJsonInputOutputTest { class NativeJsonInputOutputTest extends AnyFunSuite { import NativeJsonInputOutputTest._ - test("Bilateral serialization - raw string options") { - val options = NativeFormatOptions.RawString - bilateralTyped(testModel, options) - } + case class BilateralTestCase(name: String, options: NativeFormatOptions, testStringRepr: Boolean = true) - test("Bilateral serialization - number options") { - val options = NativeFormatOptions(longFormat = NativeLongFormat.JsNumber, dateFormat = NativeDateFormat.JsNumber) - bilateralTyped(testModel, options) - } + private val testCases = Seq( + BilateralTestCase("raw string options", NativeFormatOptions.RawString), + BilateralTestCase( + "number options", + NativeFormatOptions( + longFormat = NativeLongFormat.JsNumber, + dateFormat = NativeDateFormat.JsNumber), + ), + BilateralTestCase( + "typed options", + NativeFormatOptions( + longFormat = NativeLongFormat.JsBigInt, + NativeDateFormat.JsDate, + bigIntFormat = NativeBitIntFormat.JsBigInt, + ), + testStringRepr = false, // scala.scalajs.js.JavaScriptException: TypeError: Do not know how to serialize a BigInt + ), + ) + + testCases.foreach { case BilateralTestCase(name, options, testStringRepr) => + test(s"Bilateral serialization - $name") { + bilateralTyped(testModel, options) + } - test("Bilateral serialization - typed options") { - val options = NativeFormatOptions(longFormat = NativeLongFormat.JsBigInt, dateFormat = NativeDateFormat.JsDate) - bilateralTyped(testModel, options) + if (testStringRepr) { + test(s"Bilateral serialization to string - $name") { + bilateralString(testModel, options) + } + } } private def testModel: TestModel = TestModel( @@ -45,6 +65,8 @@ class NativeJsonInputOutputTest extends AnyFunSuite { time = Timestamp.now(), list = Seq(1, 2, 3), map = Map("Abc" -> "1", "xyz" -> "10000"), + binary = new Bytes(Array(1, 2, 0, 5)), + bigInt = BigInt("10000000000000000000"), rawJson = WrappedJson("""{"a":1,"b":"c"}"""), ) @@ -53,4 +75,10 @@ class NativeJsonInputOutputTest extends AnyFunSuite { val deserialized = NativeJsonInput.read[T](raw, options) assert(deserialized == input) } + + private def bilateralString[T: GenCodec](input: T, options: NativeFormatOptions): Unit = { + val raw = NativeJsonOutput.writeAsString(input, options) + val deserialized = NativeJsonInput.readString[T](raw, options) + assert(deserialized == input) + } } diff --git a/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala b/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala index ca11614d3..a5f2ca6a5 100644 --- a/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala +++ b/core/src/main/scala/com/avsystem/commons/serialization/json/WrappedJson.scala @@ -1,6 +1,7 @@ package com.avsystem.commons package serialization.json +import com.avsystem.commons.misc.CaseMethods import com.avsystem.commons.serialization.GenCodec /** @@ -9,7 +10,7 @@ import com.avsystem.commons.serialization.GenCodec * It will be serialized as JSON value when used with [[com.avsystem.commons.serialization.Output]] supporting * [[RawJson]] marker. */ -final case class WrappedJson(value: String) extends AnyVal +final case class WrappedJson(value: String) extends AnyVal with CaseMethods object WrappedJson { implicit val codec: GenCodec[WrappedJson] = GenCodec.create( in => WrappedJson(in.readCustom(RawJson).getOrElse(in.readSimple().readString())), diff --git a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala index 217a1ed8e..0dde10e57 100644 --- a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala +++ b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala @@ -61,7 +61,7 @@ class JsonStringInputOutputTest extends AnyFunSuite with SerializationTestUtils } test("WrappedJson") { - val json = "{\"a\": 123, \"b\": 3.14}" + val json = """{"a": 123, "b": 3.14}""" assert(JsonStringOutput.write(WrappedJson(json)) == json) assert(JsonStringInput.read[WrappedJson](json) == WrappedJson(json)) } From 0c8ad072afef9f70d92c57d11422f223bbc906af Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Sat, 3 Aug 2024 12:13:08 +0200 Subject: [PATCH 3/6] Additional tests for WrappedJson --- .../json/JsonStringInputOutputTest.scala | 6 ----- .../serialization/json/WrappedJsonTest.scala | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 core/src/test/scala/com/avsystem/commons/serialization/json/WrappedJsonTest.scala diff --git a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala index 0dde10e57..4a6c1a8bb 100644 --- a/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala +++ b/core/src/test/scala/com/avsystem/commons/serialization/json/JsonStringInputOutputTest.scala @@ -60,12 +60,6 @@ class JsonStringInputOutputTest extends AnyFunSuite with SerializationTestUtils assert(resBuilder.result() == jsons) } - test("WrappedJson") { - val json = """{"a": 123, "b": 3.14}""" - assert(JsonStringOutput.write(WrappedJson(json)) == json) - assert(JsonStringInput.read[WrappedJson](json) == WrappedJson(json)) - } - def roundtrip[T: GenCodec](name: String)(values: T*)(implicit pos: Position): Unit = { test(name) { val serialized = values.map(write[T](_)) diff --git a/core/src/test/scala/com/avsystem/commons/serialization/json/WrappedJsonTest.scala b/core/src/test/scala/com/avsystem/commons/serialization/json/WrappedJsonTest.scala new file mode 100644 index 000000000..09bbad89c --- /dev/null +++ b/core/src/test/scala/com/avsystem/commons/serialization/json/WrappedJsonTest.scala @@ -0,0 +1,22 @@ +package com.avsystem.commons +package serialization.json + +import com.avsystem.commons.serialization.{SimpleValueInput, SimpleValueOutput} +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers + +class WrappedJsonTest extends AnyFunSuite with Matchers { + + private val testJson = """{"a": 123, "b": 3.14}""" + + test("WrappedJson with JSON input/output") { + assert(JsonStringOutput.write(WrappedJson(testJson)) == testJson) + assert(JsonStringInput.read[WrappedJson](testJson) == WrappedJson(testJson)) + } + + // SimpleValueInput/Output does not support RawJson marker + test("WrappedJson with plain input/output") { + assert(SimpleValueOutput.write(WrappedJson(testJson)) == testJson) + assert(SimpleValueInput.read[WrappedJson](testJson) == WrappedJson(testJson)) + } +} From d4e77b04fde88d445bff0dab97afe165e97185e9 Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Sat, 3 Aug 2024 12:22:06 +0200 Subject: [PATCH 4/6] Review suggestions, code deduplication --- .../nativejs/NativeJsonInput.scala | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala index e949ced9b..a2a359786 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala @@ -11,7 +11,7 @@ import scala.scalajs.js.JSON class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends InputAndSimpleInput { self => private def read[T](expected: String)(matcher: PartialFunction[Any, T]): T = - matcher.applyOrElse(value, (_: Any) => throw new ReadFailure(s"$expected expected.")) + matcher.applyOrElse(value, (o: Any) => throw new ReadFailure(s"Cannot read $expected, got: ${js.typeOf(o)}")) override def readNull(): Boolean = value == null @@ -37,13 +37,13 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input catch { case e: NumberFormatException => throw new ReadFailure(s"Cannot read Long", e) } - (value: Any) match { + read("Long") { case s: String => fromString(s) case i: Int => i case d: Double if d.isWhole => d.toLong case b: js.BigInt => fromString(b.toString) + // for some reason pattern match on js.BigInt type does not seem to work, check type manually case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) - case o => throw new ReadFailure(s"Cannot read Long, got: ${js.typeOf(o)}") } } @@ -54,13 +54,13 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input case e: NumberFormatException => throw new ReadFailure(s"Cannot read BitInt", e) } - (value: Any) match { + read("BitInt") { case s: String => fromString(s) case i: Int => BigInt(i) case d: Double if d.isWhole => BigInt(d.toLong) case b: js.BigInt => fromString(b.toString) + // for some reason pattern match on js.BigInt type does not seem to work, check type manually case b if js.typeOf(b) == "bigint" => fromString(b.asInstanceOf[js.BigInt].toString) - case o => throw new ReadFailure(s"Cannot read BitInt, got: ${js.typeOf(o)}") } } @@ -70,11 +70,10 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input catch { case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigDecimal", e) } - (value: Any) match { + read("BitInt") { case s: String => fromString(s) case i: Int => BigDecimal(i) case d: Double => BigDecimal(d) - case o => throw new ReadFailure(s"Cannot read BigDecimal, got: ${js.typeOf(o)}") } } @@ -85,12 +84,12 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input override def readList(): ListInput = read("List") { - case array: js.Array[js.Any @unchecked] => new JsonListInput(array, options) + case array: js.Array[js.Any @unchecked] => new NativeJsonListInput(array, options) } override def readObject(): ObjectInput = read("Object") { - case obj: js.Object => new JsonObjectInput(obj.asInstanceOf[js.Dictionary[js.Any]], options) + case obj: js.Object => new NativeJsonObjectInput(obj.asInstanceOf[js.Dictionary[js.Any]], options) } override def readTimestamp(): Long = options.dateFormat match { @@ -105,7 +104,7 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input override def skip(): Unit = () override def readBinary(): Array[Byte] = - read("List") { + read("Binary") { case array: js.Array[Int @unchecked] => array.iterator.map(_.toByte).toArray } @@ -118,21 +117,21 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input def readRaw(): js.Any = value } -final class JsonListInput(list: js.Array[js.Any], options: NativeFormatOptions) extends ListInput { - var it = 0 +final class NativeJsonListInput(array: js.Array[js.Any], options: NativeFormatOptions) extends ListInput { + private var it = 0 override def hasNext: Boolean = - it < list.length + it < array.length override def nextElement(): Input = { - val in = new NativeJsonInput(list(it), options) + val in = new NativeJsonInput(array(it), options) it += 1 in } } -final class JsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOptions) extends ObjectInput { - val it = dict.iterator +final class NativeJsonObjectInput(dict: js.Dictionary[js.Any], options: NativeFormatOptions) extends ObjectInput { + private val it = dict.iterator override def hasNext: Boolean = it.hasNext From c93f4feb2160448a8435c594acf2176b8a2fecdc Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Mon, 5 Aug 2024 11:38:00 +0200 Subject: [PATCH 5/6] Fix typo --- .../serialization/nativejs/NativeFormatOptions.scala | 10 +++++----- .../serialization/nativejs/NativeJsonInput.scala | 6 +++--- .../serialization/nativejs/NativeJsonOutput.scala | 4 ++-- .../nativejs/NativeJsonInputOutputTest.scala | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala index 9e87a6d9d..669d72ad7 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeFormatOptions.scala @@ -31,10 +31,10 @@ object NativeDateFormat extends AbstractValueEnumCompanion[NativeDateFormat] { * * Note that [[scala.scalajs.js.JSON.stringify]] does not know how to serialize a BigInt and throws an error */ -final class NativeBitIntFormat(implicit ctx: EnumCtx) extends AbstractValueEnum -object NativeBitIntFormat extends AbstractValueEnumCompanion[NativeBitIntFormat] { - final val RawString: Value = new NativeBitIntFormat - final val JsBigInt: Value = new NativeBitIntFormat +final class NativeBigIntFormat(implicit ctx: EnumCtx) extends AbstractValueEnum +object NativeBigIntFormat extends AbstractValueEnumCompanion[NativeBigIntFormat] { + final val RawString: Value = new NativeBigIntFormat + final val JsBigInt: Value = new NativeBigIntFormat } /** @@ -47,7 +47,7 @@ object NativeBitIntFormat extends AbstractValueEnumCompanion[NativeBitIntFormat] final case class NativeFormatOptions( longFormat: NativeLongFormat = NativeLongFormat.RawString, dateFormat: NativeDateFormat = NativeDateFormat.RawString, - bigIntFormat: NativeBitIntFormat = NativeBitIntFormat.RawString, + bigIntFormat: NativeBigIntFormat = NativeBigIntFormat.RawString, ) object NativeFormatOptions { final val RawString = NativeFormatOptions() diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala index a2a359786..615f658b9 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInput.scala @@ -51,10 +51,10 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input def fromString(s: String): BigInt = try BigInt(s) catch { - case e: NumberFormatException => throw new ReadFailure(s"Cannot read BitInt", e) + case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigInt", e) } - read("BitInt") { + read("BigInt") { case s: String => fromString(s) case i: Int => BigInt(i) case d: Double if d.isWhole => BigInt(d.toLong) @@ -70,7 +70,7 @@ class NativeJsonInput(value: js.Any, options: NativeFormatOptions) extends Input catch { case e: NumberFormatException => throw new ReadFailure(s"Cannot read BigDecimal", e) } - read("BitInt") { + read("BigDecimal") { case s: String => fromString(s) case i: Int => BigDecimal(i) case d: Double => BigDecimal(d) diff --git a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala index 43891727a..108df2c16 100644 --- a/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala +++ b/core/js/src/main/scala/com/avsystem/commons/serialization/nativejs/NativeJsonOutput.scala @@ -31,8 +31,8 @@ final class NativeJsonOutput( } override def writeBigInt(bigInt: BigInt): Unit = options.bigIntFormat match { - case NativeBitIntFormat.RawString => writeString(bigInt.toString) - case NativeBitIntFormat.JsBigInt => writeRaw(js.BigInt(bigInt.toString)) + case NativeBigIntFormat.RawString => writeString(bigInt.toString) + case NativeBigIntFormat.JsBigInt => writeRaw(js.BigInt(bigInt.toString)) } override def writeBigDecimal(bigDecimal: BigDecimal): Unit = diff --git a/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala index 9d7ba3e95..6520855d6 100644 --- a/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala +++ b/core/js/src/test/scala/com/avsystem/commons/serialization/nativejs/NativeJsonInputOutputTest.scala @@ -40,7 +40,7 @@ class NativeJsonInputOutputTest extends AnyFunSuite { NativeFormatOptions( longFormat = NativeLongFormat.JsBigInt, NativeDateFormat.JsDate, - bigIntFormat = NativeBitIntFormat.JsBigInt, + bigIntFormat = NativeBigIntFormat.JsBigInt, ), testStringRepr = false, // scala.scalajs.js.JavaScriptException: TypeError: Do not know how to serialize a BigInt ), From 16a31660d126f4b8a9112bde645f608c3cff01f9 Mon Sep 17 00:00:00 2001 From: Sebastian Haracz Date: Mon, 5 Aug 2024 12:09:34 +0200 Subject: [PATCH 6/6] Add benchmark testcases for NativeJsonInput/Output --- benchmark/js/README.md | 4 ++ .../avsystem/commons/ser/JsonBenchmarks.scala | 41 +++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 benchmark/js/README.md diff --git a/benchmark/js/README.md b/benchmark/js/README.md new file mode 100644 index 000000000..d1d92e9ea --- /dev/null +++ b/benchmark/js/README.md @@ -0,0 +1,4 @@ +How to run benchmark: +- compile `sbt commons-benchmark-js/fullOptJS` +- open `fullopt-2.13.html` file in a browser +- select test suite and run diff --git a/benchmark/js/src/main/scala/com/avsystem/commons/ser/JsonBenchmarks.scala b/benchmark/js/src/main/scala/com/avsystem/commons/ser/JsonBenchmarks.scala index 44cd995b1..82f75ceee 100644 --- a/benchmark/js/src/main/scala/com/avsystem/commons/ser/JsonBenchmarks.scala +++ b/benchmark/js/src/main/scala/com/avsystem/commons/ser/JsonBenchmarks.scala @@ -2,6 +2,7 @@ package com.avsystem.commons package ser import com.avsystem.commons.serialization.json.{JsonStringInput, JsonStringOutput} +import com.avsystem.commons.serialization.nativejs.{NativeJsonInput, NativeJsonOutput} import io.circe.parser._ import io.circe.syntax._ import japgolly.scalajs.benchmark.gui.GuiSuite @@ -10,18 +11,24 @@ import japgolly.scalajs.benchmark.{Benchmark, Suite} object JsonBenchmarks { val suite = GuiSuite( Suite("JSON serialization benchmarks")( - Benchmark("Writing case class: GenCodec") { + Benchmark("Writing case class: GenCodec, String Json format") { JsonStringOutput.write(Something.Example) }, + Benchmark("Writing case class: GenCodec, Native Json format") { + NativeJsonOutput.writeAsString(Something.Example) + }, Benchmark("Writing case class: Circe") { Something.Example.asJson.noSpaces }, Benchmark("Writing case class: uPickle") { upickle.default.write(Something.Example) }, - Benchmark("Reading case class: GenCodec") { + Benchmark("Reading case class: GenCodec, String Json format") { JsonStringInput.read[Something](Something.ExampleJsonString) }, + Benchmark("Reading case class: GenCodec, Native Json format") { + NativeJsonInput.readString[Something](Something.ExampleJsonString) + }, Benchmark("Reading case class: Circe") { decode[Something](Something.ExampleJsonString).fold(e => throw e, identity) }, @@ -29,24 +36,36 @@ object JsonBenchmarks { upickle.default.read[Something](Something.ExampleJsonString) }, - Benchmark("Writing sealed hierarchy: GenCodec") { + Benchmark("Writing sealed hierarchy: GenCodec, String Json format") { JsonStringOutput.write(SealedStuff.ExampleList) }, - Benchmark("Writing sealed hierarchy: GenCodec (flat)") { + Benchmark("Writing sealed hierarchy: GenCodec (flat), String Json format") { JsonStringOutput.write(FlatSealedStuff.ExampleList) }, + Benchmark("Writing sealed hierarchy: GenCodec, Native Json format") { + NativeJsonOutput.writeAsString(SealedStuff.ExampleList) + }, + Benchmark("Writing sealed hierarchy: GenCodec (flat), Native Json format") { + NativeJsonOutput.writeAsString(FlatSealedStuff.ExampleList) + }, Benchmark("Writing sealed hierarchy: Circe") { SealedStuff.ExampleList.asJson.noSpaces }, Benchmark("Writing sealed hierarchy: uPickle") { upickle.default.write(SealedStuff.ExampleList) }, - Benchmark("Reading sealed hierarchy: GenCodec") { + Benchmark("Reading sealed hierarchy: GenCodec, String Json format") { JsonStringInput.read[List[SealedStuff]](SealedStuff.ExampleJsonString) }, - Benchmark("Reading sealed hierarchy: GenCodec (flat)") { + Benchmark("Reading sealed hierarchy: GenCodec (flat), String Json format") { JsonStringInput.read[List[FlatSealedStuff]](FlatSealedStuff.ExampleJsonString) }, + Benchmark("Reading sealed hierarchy: GenCodec, Native Json format") { + NativeJsonInput.readString[List[SealedStuff]](SealedStuff.ExampleJsonString) + }, + Benchmark("Reading sealed hierarchy: GenCodec (flat), Native Json format") { + NativeJsonInput.readString[List[FlatSealedStuff]](FlatSealedStuff.ExampleJsonString) + }, Benchmark("Reading sealed hierarchy: Circe") { decode[List[SealedStuff]](SealedStuff.ExampleJsonString).fold(e => throw e, identity) }, @@ -54,18 +73,24 @@ object JsonBenchmarks { upickle.default.read[List[SealedStuff]](SealedStuff.ExampleUpickleJsonString) }, - Benchmark("Writing foos: GenCodec") { + Benchmark("Writing foos: GenCodec, String Json format") { JsonStringOutput.write(Foo.ExampleMap) }, + Benchmark("Writing foos: GenCodec, Native Json format") { + NativeJsonOutput.writeAsString(Foo.ExampleMap) + }, Benchmark("Writing foos: Circe") { Foo.ExampleMap.asJson.noSpaces }, Benchmark("Writing foos: uPickle") { upickle.default.write(Foo.ExampleMap) }, - Benchmark("Reading foos: GenCodec") { + Benchmark("Reading foos: GenCodec, String Json format") { JsonStringInput.read[Map[String, Foo]](Foo.ExampleJsonString) }, + Benchmark("Reading foos: GenCodec with Native Json format") { + NativeJsonInput.readString[Map[String, Foo]](Foo.ExampleJsonString) + }, Benchmark("Reading foos: Circe") { decode[Map[String, Foo]](Foo.ExampleJsonString).fold(e => throw e, identity) },