Skip to content

Commit 796eb5d

Browse files
authored
Merge branch 'master' into decimal64
2 parents e7b72ba + 6e91c7f commit 796eb5d

File tree

8 files changed

+86
-23
lines changed

8 files changed

+86
-23
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Actions Build](https://github.com/plokhotnyuk/jsoniter-scala/workflows/build/badge.svg)](https://github.com/plokhotnyuk/jsoniter-scala/actions)
44
[![Scala Steward](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)
55
[![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/plokhotnyuk/jsoniter-scala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6-
[![Maven Central](https://img.shields.io/badge/maven--central-2.27.7-blue.svg)](https://repo1.maven.org/maven2/com/github/plokhotnyuk/jsoniter-scala/)
6+
[![Maven Central](https://img.shields.io/badge/maven--central-2.28.0-blue.svg)](https://repo1.maven.org/maven2/com/github/plokhotnyuk/jsoniter-scala/)
77

88
Scala macros for compile-time generation of safe and ultra-fast JSON codecs.
99

@@ -23,7 +23,7 @@ serialization performance of jsoniter-scala with: [borer](https://github.com/sir
2323
[weePickle](https://github.com/rallyhealth/weePickle), [zio-json](https://github.com/zio/zio-json)
2424
libraries using different JDK and GraalVM versions on the following environment: Intel® Core™ i9-13900K CPU @ 3.0GHz
2525
(max 5.8GHz, performance-cores only), RAM 64Gb DDR5-4800, Ubuntu 23.10 (Linux 6.6), and latest versions of JDK 17/21/23-ea[*](https://docs.google.com/spreadsheets/d/1IxIvLoLlLb0bxUaRgSsaaRuXV0RUQ3I04vFqhDc2Bt8/edit?usp=sharing),
26-
GraalVM Community JDK 17/21, and GraalVM JDK 17/21.
26+
GraalVM Community JDK 21/22-dev, and GraalVM JDK 17/21.
2727

2828
[**Latest results of benchmarks on browsers**](https://plokhotnyuk.github.io/jsoniter-scala/index-scalajs.html) that
2929
compares libraries which supports Scala.js compiled by Scala.js 1.15.0 to ES 2015 with
@@ -232,9 +232,9 @@ list of dependencies:
232232
```sbt
233233
libraryDependencies ++= Seq(
234234
// Use the %%% operator instead of %% for Scala.js and Scala Native
235-
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.27.7",
235+
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.28.0",
236236
// Use the "provided" scope instead when the "compile-internal" scope is not supported
237-
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.27.7" % "compile-internal"
237+
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.28.0" % "compile-internal"
238238
)
239239
```
240240

jsoniter-scala-circe/shared/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/circe/JsoniterScalaCodec.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.github.plokhotnyuk.jsoniter_scala.circe
22

3-
import com.github.plokhotnyuk.jsoniter_scala.core.{JsonReader, JsonValueCodec}
3+
import com.github.plokhotnyuk.jsoniter_scala.core.{JsonReader, JsonValueCodec, JsonWriter}
44
import io.circe._
55

66
object JsoniterScalaCodec {
@@ -20,10 +20,28 @@ object JsoniterScalaCodec {
2020
* @param numberParser a function that parses JSON numbers
2121
* @return The JSON codec
2222
*/
23+
def jsonCodec(
24+
maxDepth: Int,
25+
initialSize: Int,
26+
doSerialize: Json => Boolean,
27+
numberParser: JsonReader => Json): JsonValueCodec[Json] =
28+
jsonCodec(maxDepth, initialSize, doSerialize, numberParser, io.circe.JsoniterScalaCodec.defaultNumberSerializer)
29+
30+
/**
31+
* Creates a JSON value codec that parses and serialize to/from circe's JSON AST.
32+
*
33+
* @param maxDepth the maximum depth for decoding
34+
* @param initialSize the initial size hint for object and array collections
35+
* @param doSerialize a predicate that determines whether a value should be serialized
36+
* @param numberParser a function that parses JSON numbers
37+
* @param numberSerializer a routine that serializes JSON numbers
38+
* @return The JSON codec
39+
*/
2340
def jsonCodec(
2441
maxDepth: Int = 128,
2542
initialSize: Int = 8,
2643
doSerialize: Json => Boolean = _ => true,
27-
numberParser: JsonReader => Json = io.circe.JsoniterScalaCodec.defaultNumberParser): JsonValueCodec[Json] =
28-
new io.circe.JsoniterScalaCodec(maxDepth, initialSize, doSerialize, numberParser)
44+
numberParser: JsonReader => Json = io.circe.JsoniterScalaCodec.defaultNumberParser,
45+
numberSerializer: (JsonWriter, JsonNumber) => Unit = io.circe.JsoniterScalaCodec.defaultNumberSerializer): JsonValueCodec[Json] =
46+
new io.circe.JsoniterScalaCodec(maxDepth, initialSize, doSerialize, numberParser, numberSerializer)
2947
}

jsoniter-scala-circe/shared/src/main/scala/io/circe/JsoniterScalaCodec.scala

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,39 @@ object JsoniterScalaCodec {
3535
} else new JsonBigDecimal(in.readBigDecimal(null).bigDecimal)
3636
})
3737

38+
val defaultNumberSerializer: (JsonWriter, JsonNumber) => Unit = (out: JsonWriter, x: JsonNumber) => x match {
39+
case l: JsonLong => out.writeVal(l.value)
40+
case f: JsonFloat => out.writeVal(f.value)
41+
case d: JsonDouble => out.writeVal(d.value)
42+
case bd: JsonBigDecimal => out.writeVal(bd.value)
43+
case _ => out.writeRawVal(x.toString.getBytes(StandardCharsets.UTF_8))
44+
}
45+
46+
val jsCompatibleNumberSerializer: (JsonWriter, JsonNumber) => Unit = (out: JsonWriter, x: JsonNumber) => x match {
47+
case l: JsonLong =>
48+
val v = l.value
49+
if (v >= -4503599627370496L && v < 4503599627370496L) out.writeVal(v)
50+
else out.writeValAsString(v)
51+
case f: JsonFloat => out.writeVal(f.value)
52+
case d: JsonDouble => out.writeVal(d.value)
53+
case bd: JsonBigDecimal =>
54+
val v = bd.value
55+
val bl = v.unscaledValue.bitLength
56+
val s = v.scale
57+
if (bl <= 52 && s >= -256 && s <= 256) out.writeVal(v)
58+
else out.writeValAsString(v)
59+
case _ => x.toBigDecimal match {
60+
case Some(bd) =>
61+
val u = bd.underlying
62+
val bl = u.unscaledValue.bitLength
63+
val s = u.scale
64+
if (bl <= 52 && s >= -256 && s <= 256) out.writeVal(u)
65+
else out.writeValAsString(u)
66+
case _ =>
67+
out.writeVal(x.toString)
68+
}
69+
}
70+
3871
/**
3972
* Converts an ASCII byte array to a JSON string.
4073
*
@@ -94,13 +127,27 @@ object JsoniterScalaCodec {
94127
* @param initialSize the initial size hint for object and array collections
95128
* @param doSerialize a predicate that determines whether a value should be serialized
96129
* @param numberParser a function that parses JSON numbers
130+
* @param numberSerializer a function that serializes JSON numbers
97131
* @return The JSON codec
98132
*/
99133
final class JsoniterScalaCodec(
100134
maxDepth: Int,
101135
initialSize: Int,
102136
doSerialize: Json => Boolean,
103-
numberParser: JsonReader => Json) extends JsonValueCodec[Json] {
137+
numberParser: JsonReader => Json,
138+
numberSerializer: (JsonWriter, JsonNumber) => Unit) extends JsonValueCodec[Json] {
139+
140+
/**
141+
* An auxiliary constructor for backward binary compatibility.
142+
*
143+
* @param maxDepth the maximum depth for decoding
144+
* @param initialSize the initial size hint for object and array collections
145+
* @param doSerialize a predicate that determines whether a value should be serialized
146+
* @param numberParser a function that parses JSON numbers
147+
*/
148+
def this(maxDepth: Int, initialSize: Int, doSerialize: Json => Boolean, numberParser: JsonReader => Json) =
149+
this(maxDepth, initialSize, doSerialize, numberParser, JsoniterScalaCodec.defaultNumberSerializer)
150+
104151
private[this] val trueValue = True
105152
private[this] val falseValue = False
106153
private[this] val emptyArrayValue = new JArray(Vector.empty)
@@ -161,7 +208,7 @@ final class JsoniterScalaCodec(
161208
if (str.length != 1) out.writeVal(str)
162209
else out.writeVal(str.charAt(0))
163210
case b: JBoolean => out.writeVal(b.value)
164-
case n: JNumber => encodeJsonNumber(n.value, out)
211+
case n: JNumber => numberSerializer(out, n.value)
165212
case a: JArray =>
166213
val depthM1 = depth - 1
167214
if (depthM1 < 0) out.encodeError("depth limit exceeded")
@@ -183,12 +230,4 @@ final class JsoniterScalaCodec(
183230
out.writeObjectEnd()
184231
case _ => out.writeNull()
185232
}
186-
187-
private[this] def encodeJsonNumber(x: JsonNumber, out: JsonWriter): Unit = x match {
188-
case l: JsonLong => out.writeVal(l.value)
189-
case f: JsonFloat => out.writeVal(f.value)
190-
case d: JsonDouble => out.writeVal(d.value)
191-
case bd: JsonBigDecimal => out.writeVal(bd.value)
192-
case _ => out.writeRawVal(x.toString.getBytes(StandardCharsets.UTF_8))
193-
}
194233
}

jsoniter-scala-circe/shared/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/circe/JsoniterScalaCodecSpec.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ class JsoniterScalaCodecSpec extends AnyWordSpec with Matchers {
3535
val json = readFromString(jsonStr)
3636
writeToString(json) shouldBe jsonStr
3737
}
38+
"allow customization for number serialization" in {
39+
val codec = jsonCodec(numberSerializer = io.circe.JsoniterScalaCodec.jsCompatibleNumberSerializer)
40+
val jsonStr = """{"s":"VVV","n1":1.0,"n2":4503599627370497,"a":[null,"WWW",[],{}],"o":{"a":4e+297}}"""
41+
val json = readFromString(jsonStr)
42+
writeToString(json)(codec) shouldBe """{"s":"VVV","n1":1.0,"n2":"4503599627370497","a":[null,"WWW",[],{}],"o":{"a":"4E+297"}}"""
43+
}
3844
"not serialize invalid json" in {
3945
val json1 = parse("\"\ud800\"").getOrElse(null)
4046
assert(intercept[Throwable](writeToString(json1)).getMessage.contains("illegal char sequence of surrogate pair"))

jsoniter-scala-examples/example01.sc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.27.7"
2-
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.27.7"
1+
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.28.0"
2+
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.28.0"
33

44
import com.github.plokhotnyuk.jsoniter_scala.macros._
55
import com.github.plokhotnyuk.jsoniter_scala.core._

jsoniter-scala-examples/example02.sc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.27.7"
1+
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.28.0"
22

33
import com.github.plokhotnyuk.jsoniter_scala.core._
44

jsoniter-scala-examples/example03.sc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.27.7"
2-
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.27.7"
1+
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.28.0"
2+
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.28.0"
33

44
import com.github.plokhotnyuk.jsoniter_scala.macros._
55
import com.github.plokhotnyuk.jsoniter_scala.core._

version.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ThisBuild / version := "2.28.0-SNAPSHOT"
1+
ThisBuild / version := "2.29.0-SNAPSHOT"

0 commit comments

Comments
 (0)