Jsonal is part of the Kerr Scala Extensions. Jsonal is really fast.
It's pretty easy too:
import kse.jsonal._, JsonConverters._
val xs = Jast.parse("""{"data" : [1, 1.5, 2, 2.5]}""")("data").to[Array[Double]].right.get
val j = Json ~ ("data", xs.map(_ * 3)) ~ JsonTo get it, add the following to your build.sbt file (or the equivalent to your
build tool of choice, or use this information to download jars manually):
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
libraryDependencies += "com.github.ichoran" %% "kse" % "0.4-SNAPSHOT"There is currently no stable released version. 0.4-SNAPSHOT is the latest live version.
Jsonal is a very fast JSON parsing library, usually comparable in speed to Boon and considerably faster that Jackson, Play-Json, Argonaut, and even Jawn.
It's designed to provide serialization and deserialization of a Json AST. The
root class for the AST hierarchy is Jast. If you have a String, a
ByteBuffer, an Array[Byte], a CharBuffer, an Array[Char], or even
an InputStream containing valid JSON, you can get it like so:
import kse.jsonal._
Jast parse """{"fish": ["salmon", 42]}"""Jast contains two subtraits: JastError and Json. JastError contains
a String description of the error, optional positional information, and
possibly a child Jast that was the cause of this error. For instance:
scala> val wrong = Jast parse """{"fish": {"salmon" = 42}}"""
wrong: kse.jsonal.Jast =
error reading value 1 (key fish) in object
near character 9
because object's key salmon not followed with ':'
near character 19Json corresponds to a JSON Value as defined at www.json.org
and in RFC7159. Its subtraits are
Json.Nullto represent the JSON null valueJson.Boolwhich has a booleanvaluemethod and is one of two objects,Json.Bool.TrueorJson.Bool.FalseJson.Strwhich has atextmethodJson.Numwhich can always give you the best approximatingDoublevalue with itsdoublemethod but also can store and retrieve arbitrary precision numbers with other methodsJson.Arrfor JSON arrays; this is one ofJson.Arr.Dblwhich wraps an array of doubles accessible via thedoublesmethodJson.Arr.Allwhich wraps an array ofJsonvalues accessible via thevaluesmethod
Json.Objfor JSON objects, which can look up objects by (String) key but can also hold multiple values for the same key and keep track of key order.
Jast and its children implement a set of universal accessor methods that allow
you to optimistically try to look things up in a JSON syntax tree. These are
isErrorto tell you if this is actually an error state, not a JSON valueisNullto tell if the value isNullboolto give anOption[Boolean]containing aBooleanvalue if availabledoubleto give the best approximatingDoublevaluestringto give anOption[String]containing aStringvalue if availableapply(index: Int)to index into an array. You can use the Lift-style\also.apply(key: String)to look up a value for a given key in an object. Lift-style\works also.nullErrorto convert from aJastErrorto aNullif you don't care why something failed
and a few other methods. These allow you to optimistically index into JSON ASTs and only check at the end whether you got what you expected or whether you have an error.
Let's consider the following JSON:
val tree = Jast parse """{
"fish": ["salmon", "cod", "herring", "tuna"],
"price": 19.99,
"store": {"name": "seafood market", "open": true, "location": null }
}"""If we know the structure of the tree, we can index into it directly and pull out values:
scala> val myFish = tree("fish")(2).string
myFish: Option[String] = Some(herring)
scala> val myCost = tree("price").double
myCost: Double = 19.99
scala> val canGo = tree("store")("open").bool.getOrElse(false)
canGo: Boolean = true
scala> val wrongCanGo = tree("open").bool.getOrElse(false)
wrongCanGo: Boolean = false
scala> tree(42)("foo").nullError
res0: kse.jsonal.Json = null
scala> tree \ 42 \ "foo"
res1: kse.jsonal.Jast = Indexing into something that is not an arrayPattern matching can also be used to access parts of a JSON tree. Note that the
namespace is nested (e.g. JSON values all start with Json.); if brevity is
your thing you can import the short forms.
Let's retrieve all the fish this time:
tree match {
case o: Json.Obj => o("fish") match {
case j: Json.Arr =>
val b = Vector.newBuilder[String]
j.foreach(ji => ji match {
case s: Json.Str => b += s.text
case _ => throw new Exception("Fish name is not a string?!")
})
b.result
case _ => throw new Exception("fish isn't an array!")
}
case _ => throw new Exception("Tree isn't an object!")
}Obviously this is quite verbose, but it provides full control over how the tree is traversed and how errors are handled.
For a even more compact alternative, see the conversion section below!
JSON represents numbers in a human-friendly way, not a machine-friendly way.
Double precision floating point is ubiquitous, but JSON does not limit itself
to numbers that can be represented as a Double, nor can it represent the
non-finite Double values.
Because Jsonal is designed for high-performance parsing of numeric data, it has to address this mismatch.
Non-finite Double values are all encoded as a JSON null. Thus, values
of plus and minus Infinity are not preserved.
scala> val inf = Json(Double.PositiveInfinity)
inf: kse.jsonal.Json = null
scala> val ninf = Json(Double.NegativeInfinity)
ninf: kse.jsonal.Json = null
scala> val nan = Json(Double.NaN)
nan: kse.jsonal.Json = nullAll JSON numbers will be decoded to a Double or Long representation, but by
default the full version will also be accessible with Json.Num's big method (for a BigDecimal)
or simply the toString method.
scala> val jBig = Jast.parse("1234e999") match { case n: Json.Num => n; case _ => throw new Exception }
jBig: kse.jsonal.Json.Num = 1234e999
scala> val d = jBig.double
d: Double = Infinity
scala> val bd = jBig.big
bd: BigDecimal = 1.234E+1002
scala> val s = jBig.toString
s: String = 1234e999If a Json.Num is a Long value, its isLong method will return true. Regardless
of whether a number is a Long, a Long approximation can be had with the
long method:
scala> val is = Seq(Json.Num(1234567890L).isLong, Json.Num(BigDecimal(1.7)).isLong)
is: Seq[Boolean] = List(true, false)
scala> Seq(Json.Num(1234567890L).long, Json.Num(BigDecimal(1.7)).long)
res8: Seq[Long] = List(1234567890, 1)If you want fractional values to be rounded to the nearest long, you should use
if (jn.isLong) jn.long else math.round(jn.double).
If a Json.Num has a finite Double approximation the isDouble method will return true
even if it is inexact, while the isPrimitive method will pick out anything that can
be represented exactly by either a Long or a Double (but be warned, this can
be rather slow as it often requires creation of a string corresponding to the approximating
Double).
scala> val ds = Seq(
| Json.Num(Long.MaxValue).isDouble,
| Json.Num(BigDecimal("1e1000")).isDouble,
| Json.Num(BigDecimal("1.01234567890123456789")).isDouble
| )
ds: Seq[Boolean] = List(true, false, true)
scala> val ps = Seq(
| Json.Num(Long.MaxValue).isPrimitive,
| Json.Num(BigDecimal("1e1000")).isPrimitive,
| Json.Num(BigDecimal("1.01234567890123456789")).isPrimitive,
| Json.Num(BigDecimal("1.7")).isPrimitive
| )
ps: Seq[Boolean] = List(true, false, false, true)The situation is different with arrays. If possible, arrays will be stored
as an array of Doubles inside a Json.Arr.Dbl. Any values of null will be converted
to Double.NaN; during parsing, normally an exact Double representation is required,
but if an approximation is okay there is a relaxed set of parsers:
scala> val strictly = (Jast parse """[1, 1.0123456789012345]""").getClass
strictly: Class[_ <: kse.jsonal.Jast] = class kse.jsonal.Json$Arr$All
scala> val relaxedly = Jast.relaxed.parse("""[1, 1.0123456789012345]""").getClass
relaxedly: Class[_ <: kse.jsonal.Jast] = class kse.jsonal.Json$Arr$DblThe Json.Arr and Json.Obj methods contain a small set of high-level methods for
traversing or transforming the contents.
The contents of arrays and objects is (usually) mutable. Usually they are backed by arrays, and you can get access to those and change them. Only do this if you really know what you're doing (e.g. speed is critical)! Normally this would be poor design, but Jsonal is for when speed is critical so it exposes these methods to you.
In addition, Json.Arr contains foreach, filter, and iterator methods; the first two are
specialized for speed if you know you have a Json.Arr.Dbl.
scala> import JsonConverters._
import JsonConverters._
scala> val a = (Json.Arr ~ "fish" ~ 2.7 ~ null ~ Json.Arr)
a: kse.jsonal.Json.Arr = ["fish", 2.7, null]
scala> a.foreach(println)
"fish"
2.7
null
scala> val simples = a.filter(_.simple)
simples: kse.jsonal.Json.Arr = ["fish", 2.7, null]
scala> val notDoubles = a.filter(_.double.isNaN)
notDoubles: kse.jsonal.Json.Arr = ["fish", null]
scala> val vec = a.iterator.toVector
vec: Vector[kse.jsonal.Json] = Vector("fish", 2.7, null)Json.Obj contains the same set of methods, plus a transformValues method that
will modify the values in place if possible, or return a new object if not.
Since modification in place is not always possible, one should always use the returned
object:
scala> val o = tree match { case o: Json.Obj => o; case _ => ??? }
o: kse.jsonal.Json.Obj = { "fish":["salmon", "cod", "herring", "tuna"], "price":19.99, "store":{ "name":"seafood market", "open":true, "location":null } }
scala> val o2 = o.transformValues((key, value) => if (key == "price") Json("on sale for "+value.double.toString) else value)
o2: kse.jsonal.Json.Obj = { "fish":["salmon", "cod", "herring", "tuna"], "price":"on sale for on sale for 19.99", "store":{ "name":"seafood market", "open":true, "location":null } }
scala> val itChanged = tree
itChanged: kse.jsonal.Jast = { "fish":["salmon", "cod", "herring", "tuna"], "price":"on sale for on sale for 19.99", "store":{ "name":"seafood market", "open":true, "location":null } }You can create a JSON value by putting the desired contents as an argument to Json():
scala> import kse.jsonal._
import kse.jsonal._
scala> Json()
res0: kse.jsonal.Json = null
scala> Json(true)
res1: kse.jsonal.Json = true
scala> Json("salmon")
res2: kse.jsonal.Json = "salmon"
scala> Json(912837498173991L)
res3: kse.jsonal.Json = 912837498173991
scala> Json(1.415e-16)
res4: kse.jsonal.Json = 1.415E-16
scala> Json(Array(Json(false), Json("fish")))
res5: kse.jsonal.Json = [false, "fish"]
scala> Json(Map("fish" -> Json("salmon")))
res6: kse.jsonal.Json = {"fish":"salmon"}
scala> Json(Array(2.7, 3.3, Double.NaN))
res7: kse.jsonal.Json = [2.7, 3.3, null]If you include the contents of JsonConverters, you can also generate Json from
anything that has implemented the AsJson trait, or for anything that has a
Jsonize typeclass:
scala> import JsonConverters._
import JsonConverters._
scala> Json(Vector("herring", "pike"))
res9: kse.jsonal.Json = ["herring", "pike"]
scala> class X extends AsJson { def json = Json(Map("X" -> true)) }
defined class X
scala> Json(new X)
res10: kse.jsonal.Json = {"X":true}
scala> implicit val jsonizeSymbol = new Jsonize[Symbol] { def jsonize(s: Symbol) = Json(s.toString) }
jsonizeSymbol: kse.jsonal.Jsonize[Symbol] = $anon$1@485f4044
scala> Json('widget)
res11: kse.jsonal.Json = "'widget"Alternatively, you can create arrays and objects with builders: Json.Arr.All.builder,
Json.Arr.Dbl.builder, and Json.Obj.builder will get one for you, or you can simply state
the type you want to start building.
To add an element, use ~. To add a collection of elements, use ~~. To optionally
add a key-value pair only when the value is non-empty, use ~?. Maps take pairs (String, Json),
while arrays take single values; either can take advantage of the presence of the AsJson trait
or the Jsonize typeclass.
To end, call .result on the builder, or re-state the type you were building with after a ~ or ~~ (either is fine).
Some examples:
scala> Json ~ "true" ~ true ~ 1 ~ Json
res12: kse.jsonal.Json = ["true", true, 1]
scala> Json ~ ("true", true) ~ ("false", 'widget) ~ Json
res13: kse.jsonal.Json = { "true":true, "false":"'widget" }
scala> // This one won't work: start and end types are not the same!
scala> Json.Obj ~ ("true", true) ~ ("false", 'widget) ~ Json
<console>:18: error: type mismatch;
found : kse.jsonal.Json.type
required: kse.jsonal.JsonBuildTerminator[kse.jsonal.Json.Obj]
Note: kse.jsonal.Json >: kse.jsonal.Json.Obj (and kse.jsonal.Json.type <: kse.jsonal.JsonBuildTerminator[kse.jsonal.Json]), but trait JsonBuildTerminator is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
Json.Obj ~ ("true", true) ~ ("false", 'widget) ~ Json
^
scala> Json.Obj ~ ("true", true) ~ ("false", 'widget) ~ Json.Obj
res15: kse.jsonal.Json.Obj = { "true":true, "false":"'widget" }
scala> Json ~? ("fish", Seq("pike", "tuna")) ~? ("wish", List.empty[String]) ~ Json
res16: kse.jsonal.Json = {"fish":["pike", "tuna"]}
scala> val b = Json ~~ Seq(true, false, false, false)
b: kse.jsonal.Json.Arr.All.Build[kse.jsonal.Json] = kse.jsonal.Json$Arr$All$Build@50ec5ab8
scala> b ~~ Iterator("this", "is", "more", "stuff")
res17: b.type = kse.jsonal.Json$Arr$All$Build@50ec5ab8
scala> b ~ Json
res18: kse.jsonal.Json = [true, false, false, false, "this", "is", "more", "stuff"]Builders are mutable (as always!) so take care!
Parsing is handled by four separate high-performance implementations of parsing functionality:
JsonStringParser, JsonCharBufferParser, JsonByteBufferParser, and JsonRecyclingParser.
Of these, JsonStringParser is the fastest, followed by JsonRecyclingParser (which is designed to
take InputStreams but can be used in other contexts also), followed by the buffer parsers. Note
that JsonRecylingParser uses sun.misc.unsafe, so you may wish to avoid it in certain contexts.
Normally, you never need to interface with these parsers directly. Jast and each of the JSON value
types have their own parse methods on the companion object. For Jast, every method simply returns
a Jast. For the others, parse returns an Either with the desired type as the right branch
and JastError as the left.
Here are some examples of parsing:
scala> import kse.jsonal._
import kse.jsonal._
scala> Json.Bool parse "true"
res0: Either[kse.jsonal.JastError,kse.jsonal.Json.Bool] = Right(true)
scala> Jast parse "true"
res1: kse.jsonal.Jast = true
scala> Jast parse "grue"
res2: kse.jsonal.Jast =
invalid character: 'g'
near character 0
scala> Json.Bool parse "grue"
res3: Either[kse.jsonal.JastError,kse.jsonal.Json.Bool] =
Left(Expected boolean but found character g
near character 0)
scala> Jast parse java.nio.CharBuffer.wrap(Array('f', 'a', 'l', 's', 'e'))
res4: kse.jsonal.Jast = false
scala> Json.Arr parse java.nio.ByteBuffer.wrap("[2, 3, 2]".getBytes)
res5: Either[kse.jsonal.JastError,kse.jsonal.Json.Arr] = Right([2, 3, 2])
scala> Json.Str parse (new java.io.ByteArrayInputStream("\"salmon\"".getBytes))
res6: Either[kse.jsonal.JastError,kse.jsonal.Json.Str] = Right("salmon")
scala> Json.Obj parse """{"a": {"b": {"c": {"d": null}}}}"""
res7: Either[kse.jsonal.JastError,kse.jsonal.Json.Obj] = Right({"a":{"b":{"c":{"d":null}}}})With Jast you can even parse a file (as long as you're happy with basic file error handling).
Scala classes that are not listed in JsonConverters can be converted to a JSON syntax tree
by having them implement the AsJson trait, or by providing a Jsonize typeclass for them.
In the former case, you simply define a json method on the class, which then works
with the JSON assembly methods:
scala> import kse.jsonal._, JsonConverters._
import kse.jsonal._
import JsonConverters._
scala> case class Person(first: String, last: String) extends AsJson {
| def json = Json ~ ("first name", first) ~ ("surname", last) ~ Json
| }
defined class Person
scala> val obviously = Person("John", "Smith").json
obviously: kse.jsonal.Json = { "first name":"John", "surname":"Smith" }In the latter case, if an implicit Jsonize typeclass is provided you can use Json()
to convert a class to its JSON representation:
scala> import kse.jsonal._, JsonConverters._
import kse.jsonal._
import JsonConverters._
scala> case class Person(first: String, last: String) {}
defined class Person
scala> implicit val jsonizePerson = new Jsonize[Person] {
| def jsonize(p: Person) = Json ~ p.last ~ p.first ~ Json
| }
jsonizePerson: kse.jsonal.Jsonize[Person] = $anon$1@57a92d5a
scala> Json(Person("John", "Smith"))
res0: kse.jsonal.Json = ["Smith", "John"]
scala> Json ~ ("dude", Person("Bill", "Surfer")) ~ Json
res1: kse.jsonal.Json = {"dude":["Surfer", "Bill"]}No macros are provided to make case classes work automatically, but it's usually
very easy to write a json method or Jsonize typeclass.
Data types that correspond perfectly to the JSON values can be extracted with pattern matching or accessor methods. In particular, numeric arrays can be pattern-matched out for high-efficiency reading of numeric data.
In addition, the asMap method on Json.Obj can be handy to get a map view of
the data.
scala> import kse.jsonal._
import kse.jsonal._
scala> Jast.parse("[47, 13, 19291, null, 16, 2.2]") match {
| case jad: Json.Arr.Dbl => jad.doubles
| case _ => throw new Exception("oh noes")
| }
res0: Array[Double] = Array(47.0, 13.0, 19291.0, NaN, 16.0, 2.2)
scala> Json.Bool(true).value
res1: Boolean = true
scala> Json.Str("herring").text
res2: String = herring
scala> Json.Num(1234567890L).double
res3: Double = 1.23456789E9
scala> (Json.Obj ~ ("one", Json(1)) ~ ("two", Json(2)) ~ Json.Obj).asMap
res4: scala.collection.Map[String,kse.jsonal.Json] = Map(two -> 2, one -> 1)More complex data types can be pulled out with the to method. This
requires a FromJson typeclass to convert from a JSON syntax tree to
a particular target class. Typeclasses exist for the standard Scala
collections, basic data types, Option, Either, and several of the
most common time structures from java.time.
The requested type is returned in the Right branch; if there is an
error in parsing, a JastError is returned in the Left branch.
scala> import kse.jsonal._, JsonConverters._
import kse.jsonal._
import JsonConverters._
scala> Jast.parse("[47, 13, 19291, null, 16, 2.2]").to[Array[Double]]
res0: Either[kse.jsonal.JastError,Array[Double]] = Right([D@394d78aa)
scala> Jast.parse("[47, 13, 19291, null, 16, 2.2]").to[Vector[Double]]
res1: Either[kse.jsonal.JastError,Vector[Double]] = Right(Vector(47.0, 13.0, 19291.0, NaN, 16.0, 2.2))
scala> Jast.parse("true").to[Option[Boolean]]
res2: Either[kse.jsonal.JastError,Option[Boolean]] = Right(Some(true))
scala> Jast.parse("77.7").to[Either[Double, String]]
res3: Either[kse.jsonal.JastError,Either[Double,String]] = Right(Left(77.7))
scala> Jast.parse("""{"fish": [1, 2, 3], "wish": [3, 2, 1]}""").to[Map[String, Array[Double]]]
res4: Either[kse.jsonal.JastError,Map[String,Array[Double]]] = Right(Map(fish -> [D@35217067, wish -> [D@12957644))
scala> Jast.parse("""{"fish": [1, 2, 3], "wish": [3, 2, 1]}""").to[Map[String, collection.mutable.ArrayBuffer[Double]]]
res5: Either[kse.jsonal.JastError,Map[String,scala.collection.mutable.ArrayBuffer[Double]]] =
Right(Map(fish -> ArrayBuffer(1.0, 2.0, 3.0), wish -> ArrayBuffer(3.0, 2.0, 1.0)))
scala> Jast.parse("\"PT99H44M2.2S\"").to[java.time.Duration]
res6: Either[kse.jsonal.JastError,java.time.Duration] = Right(PT99H44M2.2S)The to method relies upon instances of the FromJson typeclass for the method
being parsed. How complex it is to provide this functionality depends upon how
much error handling you want to perform. Here is a simple example where correct
JSON is parsed, but various incorrect forms may also pass (e.g. JSON with severalkeys
keys corresponding to the same value).
Note that you can explicitly pass a FromJson--for example, a companion object--to
the to method even if it's not implicit.
scala> import kse.jsonal._, JsonConverters._
import kse.jsonal._
import JsonConverters._
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class Person(first: String, last: String) extends AsJson {
def json = Json ~ ("first", first) ~ ("last", last) ~ Json
}
object Person extends FromJson[Person] {
def parse(j: Json): Either[JastError, Person] = Right(Person(
j("first") match { case s: Json.Str => s.text; case _ => return Left(JastError("No first name")) },
j("last") match { case s: Json.Str => s.text; case _ => return Left(JastError("No last name")) }
))
}
// Exiting paste mode, now interpreting.
defined class Person
defined object Person
scala> Json(Map("first" -> "John", "last" -> "Smith")).to(Person)
res1: Either[kse.jsonal.JastError,Person] = Right(Person(John,Smith))
scala> Person("Jane", "Doe").json.to(Person)
res2: Either[kse.jsonal.JastError,Person] = Right(Person(Jane,Doe))However, to take advantage of more complex destructuring, you need to place the typeclass in scope implicitly:
scala> implicit val implicitPersonFromJson: FromJson[Person] = Person
implicitPersonFromJson: kse.jsonal.FromJson[Person] = Person$@5d4f6cb8
scala> (Json ~ Person("Yi", "Li") ~ Person("Mo", "Slo") ~ Json).to[Vector[Person]]
res3: Either[kse.jsonal.JastError,Vector[Person]] = Right(Vector(Person(Yi,Li), Person(Mo,Slo)))
scala> implicit val implicitPersonFromJson: FromJson[Person] = Person
implicitPersonFromJson: kse.jsonal.FromJson[Person] = Person$@5d4f6cb8
scala> (Json ~ Person("Yi", "Li") ~ Person("Mo", "Slo") ~ Json).to[Vector[Person]]
res3: Either[kse.jsonal.JastError,Vector[Person]] = Right(Vector(Person(Yi,Li), Person(Mo,Slo)))Finally, note that instances of FromJson can parse from strings and ByteBuffers and the like too:
scala> Person parse """{"first": "Arthur", "last": "Pendragon"}"""
res5: Either[kse.jsonal.JastError,Person] = Right(Person(Arthur,Pendragon))All members of the JSON syntax tree will serialize themselves to valid JSON when
their toString method is called. In addition, both Json and subclasses, plus
anything implementing AsJson can push its representation onto a java.lang.StringBuilder,
a ByteBuffer (with an operation to refresh the buffer when its full), or a CharBuffer.
Note: the Java StringBuilder was chosen because the JIT compiler will sometimes optimize operations using it. (Whether that actually matters in this library was not tested.)
Examples:
scala> import kse.jsonal._, JsonConverters._
import kse.jsonal._
import JsonConverters._
scala> val j = Json ~ true ~ (Json ~ ("fish", "salmon") ~ ("wish", true) ~ Json) ~ Json
j: kse.jsonal.Json = [true, { "fish":"salmon", "wish":true }]
scala> j.toString
res0: String = [true, { "fish":"salmon", "wish":true }]
scala> { val sb = new java.lang.StringBuilder
| j.jsonString(sb)
| sb.toString
| }
res1: String = [true, { "fish":"salmon", "wish":true }]
scala> { val b = new Array[Byte](128)
| val bb = java.nio.ByteBuffer.wrap(b)
| j.jsonBytes(bb, _ => throw new Exception("no space left!"))
| new String(b, 0, bb.position)
| }
res2: String = [true, { "fish":"salmon", "wish":true }]Jsonal includes a prettyprinting facility also, if a human may read the JSON output.
By default it has a two-space indent and a right margin of 78, but these can be set
if one creates a PrettyJson instance with different defaults.
scala> PrettyJson(j)
res3: String = [ true, { "fish": "salmon", "wish": true } ]
scala> val pj = new PrettyJson(indentation = 4, rightMargin = 10)
pj: kse.jsonal.PrettyJson = kse.jsonal.PrettyJson@18bc05f1
scala> { pj traverse j; pj.asString }
res4: String =
[ true,
{ "fish": "salmon",
"wish": true
} ]
scala> val pj2 = new PrettyJson(indentation = 2, rightMargin = 10)
pj2: kse.jsonal.PrettyJson = kse.jsonal.PrettyJson@4ce9a0fe
scala> { pj2 traverse j; pj2.asString }
res5: String =
[ true,
{ "fish": "salmon",
"wish": true
} ]Note that despite being supposedly "pretty", the 4-space indent isn't lined up as well as the 2-space.
If you have a lot of numeric data, you may wish to use the PrettyNumberJson class, which will
store a list of object keys and array indices as history, and can use those to decide at what precision
to print a number. For instance,
scala> val j2 = Json ~ ("x", 1.12345678) ~ ("y", 1.12345678) ~ ("x", Array(1.12345678, 98765432.1)) ~ Json
j2: kse.jsonal.Json = { "x":1.12345678, "y":1.12345678, "x":[1.12345678, 9.87654321E7] }
scala> // Format a max 3 of digits after decimal point, and max 5 digits after the first
scala> val three = PrettyNumberJson.FormatTo(3, 5)
three: kse.jsonal.PrettyNumberJson.FormatTo = FormatTo(3,5)
scala> val pnj = new PrettyNumberJson(contextFormatter = _ match {
| case "x" :: more => three
| case _ => PrettyNumberJson.defaultFormatter
| }, rightMargin = 10)
pnj: kse.jsonal.PrettyNumberJson = kse.jsonal.PrettyNumberJson@16b1471e
scala> { pnj traverse j2; pnj.asString }
res6: String =
{ "x": 1.123,
"y": 1.12345678,
"x": [
1.123,
9.87654e+07
] }This is far slower than standard output, but it can be worth it.