scalajson
scalajson copied to clipboard
ScalaJSON - JSON for Scala, currently contains minimal AST
ScalaJSON
ScalaJSON library, currently provides two minimal implementations of JSON AST, one that is designed for typical use and another that is designed for performance/corner cases.
Usage
Built for Scala 2.10.x, 2.11.x, 2.12.x, and 2.13.x milestones.
"org.scala-lang.platform" %% "scalajson" % "1.0.0-M4"
If you are using Scala.js, you need to do
"org.scala-lang.platform" %%% "scalajson" % "1.0.0-M4"
Standard AST
Implementation is in scalajson.ast.JValue
Goals
- Fully immutable (all underlying collections/types used are immutable)
constant/effective constantlookup time forscalajson.ast.JArray/scalajson.ast.JObject- Adherence to the typical use for the JSON standard.
- Number representation for
scalajson.ast.JNumberis aStringwhich checks if its a valid JSON representation of a number (http://stackoverflow.com/a/13502497/1519631)- Equals will properly detect if two numbers are equal, i.e.
scalajson.ast.JNumber("34.00") == scalajson.ast.JNumber("34") - Hashcode has been designed to provide consistent hash for numbers of unlimited precision.
- If you construct a JNumber with
Float.NaN/Float.PositiveInfinity/Float.NegativeInfinity/Double.NaN/Double.PositiveInfinity/Double.NegativeInfinityit will return aJNull - You can construct an unlimited precision number using a string, i.e.
JNumber("34324"). Returns anOption[JNumber](will returnNoneifStringisn't a valid number)- Note that this doesn't work for Scala 2.10 due to a restriction with how private constructor case classes are handled. For this reason a
JNumber.fromStringmethod is provided which compiles on all platforms and scala versions
- Note that this doesn't work for Scala 2.10 due to a restriction with how private constructor case classes are handled. For this reason a
- Equals will properly detect if two numbers are equal, i.e.
scalajson.ast.JObjectis an actualMap[String,JValue]. This means that it doesn't handle duplicate keys for ascalajson.ast.JObject, nor does it handle key ordering.scalajson.ast.JArrayis anVector.
- Number representation for
- Library does not allow invalid JSON in the representation and hence we can guarantee that a
scalajson.ast.JValuewill always contain a valid structure that can be serialized/rendered into JSON.- Note that you can lose precision when using
scalajson.ast.JNumberinScala.js(seeScala.jssection for more info). - The
.copymethod ofscalajson.ast.JNumberhas been overridden to make sure you can't replace the internalStringwith an incorrect number- Will throw a
NumberFormatExceptionif you use.copywith an invalid JSON number
- Will throw a
- Note that you can lose precision when using
- Due to the above, has properly implemented deep equality for all types of
scalajson.ast.JValue
Unsafe AST
Implementation is in scalajson.unsafe.JValue
Goals
- Uses the best performing datastructure's for high performance/low memory usage in construction of a
unsafe.JValuescalajson.ast.unsafe.JArraystored as anArrayfor JVM andjs.Arrayfor Scala.jsscalajson.ast.unsafe.JObjectstored as anArrayfor JVM andjs.Arrayfor Scala.jsscalajson.ast.unsafe.JNumberstored as aString
- Doesn't use
Scala'sstdlibcollection's library - Defer all runtime errors. We don't throw errors if you happen to provide Infinity/NaN or other invalid data.
- Allow duplicate and ordered keys for a
scalajson.ast.unsafe.JObject. - Allow any length/precision of numbers for
scalajson.ast.unsafe.JNumbersince its represented as aString- Equals/hashcode only checks for
Stringequality, not number equality.- Means that
scalajson.ast.unsafe.JNumber("34.00")is not equal toscalajson.ast.unsafe.JNumber("34")
- Means that
- Equals/hashcode only checks for
- This means that
scalajson.ast.unsafe.JValuecan represent everything that can can be considered valid under the official JSON spec, even if its not considered sane (i.e. duplicate keys for ascalajson.ast.unsafe.JObject).- Also means it can hold invalid data, due to not doing runtime checks
- Is referentially transparent in regards to
String->scalajson.ast.unsafe.JValue->Stringsincescalajson.ast.unsafe.JObjectpreserves ordering/duplicate keys - Implements structural equality for both
hashCodeandequals. If you need reference equality you can useeqand if you need referencehashCodeyou can use.value.hashCode. Also note that for deep comparison is used bothhashCodeandequals.
Conversion between scalajson.JValue and scalajson.ast.unsafe.JValue
Any scalajson.ast.JValue implements a conversion to scalajson.ast.unsafe.JValue with a toUnsafe method and vice versa with a
toStandard method. These conversion methods have been written to be as fast as possible.
There are some peculiarities when converting between the two AST's. When converting a scalajson.ast.unsafe.JNumber to a
scalajson.ast.JNumber, it is possible for this to fail at runtime (since the internal representation of
scalajson.ast.unsafe.JNumber is a String and it doesn't have a runtime check). It is up to the caller on how to handle this error (and when),
a runtime check is deliberately avoided on our end for performance reasons.
Converting from a scalajson.ast.JObject to a scalajson.ast.unsafe.JObject will produce
an scalajson.ast.unsafe.JObject with an undefined ordering for its internal Array/js.Array representation.
This is because a Map has no predefined ordering. If you wish to provide ordering, you will either need
to write your own custom conversion to handle this case. Duplicate keys will also be removed for the same reason
in an undefined manner.
Do note that according to the JSON spec, whether to order keys for a JObject is not specified. Also note that Map
disregards ordering for equality, however Array/js.Array equality takes ordering into account.
Number conversions
ScalaJSON JNumber provides conversions to various number types with the following conventions
toInt: Safe conversion toIntwhich accounts for values such as1.0and100.00e-2(which both evaluate to1). Also safely detects over/underflow.toLong: Safe conversion toLongwhich accounts for values such as1.0and100.00e-2(which both evaluate to1). Also safely detects over/underflow.toDouble: Converts to aDoubleassuming the same semantics ofDouble(i.e. precision loss is expected).toFloat: Converts to aFloatassuming the same semantics ofFloat(i.e. precision loss is expected).toBigInt: Converts to aBigIntwhich accounts for values such as1.0and100.00e-2(which evaluates to1). Can construct aBigIntfor as much as memory as the system has (if your system runs out of memory this is considered undefined behaviour).toBigDecimal: Converts to aBigDecimalwith all of the caveats ofBigDecimalconstruction. TheBigDecimalis constructed withMathContext.UNLIMITEDprecision.
With the .toFloat and .toDouble methods, if you don't want any loss in precision, its advisable to convert to
BigDecimal first and then work from there, i.e. when working with Decimal/Float, its implied that you will
have loss of precision.
Remember that in all cases if these methods are not applicable, you can always use the .value field to get the
original string representation of the number.
Scala.js
ScalaJSON also provides support for Scala.js.
The usage of Scala.js mirrors the usage of Scala on the JVM however Scala.js also implements
a .toJsAny method which allows you to convert any
scalajson.ast.JValue/scalajson.ast.unsafe.JValue to a Javascript value in Scala.js.
Note that, since a scalajson.ast.JNumber/scalajson.ast.unsafe.JNumber is unlimited
precision (represented internally as a String), calls to .toJsAny can lose precision on the
underlying number (numbers in Javascript are represented as double precision floating point number).
You can use the .value method on a scalajson.ast.JNumber/scalajson.ast.unsafe.JNumber to
get the raw string value as a solution to this problem.
Further, toFloat on JNumber (see Number Conversions ) can have different semantics on Scala.js, depending on whether you have
strict-floats enabled in your application. Please see the Scala.js semantics page
for more information.
jNumberRegex
scalajson.JNumber uses jNumberRegex to validate whether a number is a valid
JSON number. One can use jNumberRegex explicitly if you want to use the validation that
is used by scalajson.JNumber (for example, you may want to validate proper numbers
before creating a scalajson.unsafe.JNumber).
import scalajson.jNumberRegex
"3535353" match {
case jNumberRegex(_ *) => true
case _ => false
}
Code of Conduct
ScalaJSON uses the Scala Code of Conduct for all communication and discussion. This includes both GitHub, Gitter chat and other more direct lines of communication such as email.
Code formatting
The project is formatted using scalafmt. Please run scalafmt
in SBT before committing any changes