This repository is a fork of the spray-json project. Its purpose is to make spray available for ScalaJS and ScalaNative.

It remains fully binary compatible with the upstream project and can be used as a drop-in replacement on the JVM.

libraryDependencies +="io.crashbox"%%%"spray-json"%"<latest_version>"

spray-json is a lightweight, clean and efficient JSON implementation in Scala.

It supports the following features:

A simple immutable model of the JSON language elements

An efficient JSON parser

Choice of either compact or pretty JSON-to-string printing

Type-class based (de)serialization of custom objects (no reflection, no intrusion)

No external dependencies

spray-json allows you to convert between

String JSON documents

JSON Abstract Syntax Trees (ASTs) with base type JsValue

instances of arbitrary Scala types

as depicted in this diagram:

spray-json is available for Scala on the JVM, ScalaJS and Scala Native1.

1: The versions for ScalaJS and Scala Native do not support generating case class formats through introspection, meaning that there are no jsonFormatX methods available on these platforms. See section Providing JsonFormats for Case Classes below for an explanation and workaround.

Installation

spray-json is available from maven central.

Latest release:

If you use SBT you can include spray-json in your project with

libraryDependencies +="io.spray"%%"spray-json"%"1.3.5"

Usage

spray-json is really easy to use. Just bring all relevant elements in scope with

importspray.json._importDefaultJsonProtocol._// if you don't supply your own Protocol (see below)

Print a JSON AST back to a String using either the CompactPrinter or the PrettyPrinter

valjson= jsonAst.prettyPrint // or .compactPrint

Convert any Scala object to a JSON AST using the toJson extension method

valjsonAst=List(1, 2, 3).toJson

Convert a JSON AST to a Scala object with the convertTo method

valmyObject= jsonAst.convertTo[MyObjectType]

In order to make steps 3 and 4 work for an object of type T you need to bring implicit values in scope that provide JsonFormat[T] instances for T and all types used by T (directly or indirectly). The way you normally do this is via a "JsonProtocol".

JsonProtocol

spray-json uses SJSONs Scala-idiomatic type-class-based approach to connect an existing type T with the logic how to (de)serialize its instances to and from JSON. (In fact spray-json even reuses some of SJSONs code, see the 'Credits' section below).

This approach has the advantage of not requiring any change (or even access) to Ts source code. All (de)serialization logic is attached 'from the outside'. There is no reflection involved, so the resulting conversions are fast. Scalas excellent type inference reduces verbosity and boilerplate to a minimum, while the Scala compiler will make sure at compile time that you provided all required (de)serialization logic.

In spray-jsons terminology a 'JsonProtocol' is nothing but a bunch of implicit values of type JsonFormat[T], whereby each JsonFormat[T] contains the logic of how to convert instance of T to and from JSON. All JsonFormat[T]s of a protocol need to be "mece" (mutually exclusive, collectively exhaustive), i.e. they are not allowed to overlap and together need to span all types required by the application.

This may sound more complicated than it is. spray-json comes with a DefaultJsonProtocol, which already covers all of Scala's value types as well as the most important reference and collection types. As long as your code uses nothing more than these you only need the DefaultJsonProtocol. Here are the types already taken care of by the DefaultJsonProtocol:

Byte, Short, Int, Long, Float, Double, Char, Unit, Boolean

String, Symbol

BigInt, BigDecimal

Option, Either, Tuple1 - Tuple7

List, Array

immutable.{Map, Iterable, Seq, IndexedSeq, LinearSeq, Set, Vector}

collection.{Iterable, Seq, IndexedSeq, LinearSeq, Set}

JsValue

In most cases however you'll also want to convert types not covered by the DefaultJsonProtocol. In these cases you need to provide JsonFormat[T]s for your custom types. This is not hard at all.

Providing JsonFormats for Case Classes

If your custom type T is a case class then augmenting the DefaultJsonProtocol with a JsonFormat[T] is really easy:

The jsonFormatX methods reduce the boilerplate to a minimum, just pass the right one the companion object of your case class and it will return a ready-to-use JsonFormat for your type (the right one is the one matching the number of arguments to your case class constructor, e.g. if your case class has 13 fields you need to use the jsonFormat13 method). The jsonFormatX methods try to extract the field names of your case class before calling the more general jsonFormat overloads, which let you specify the field name manually. So, if spray-json has trouble determining the field names or if your JSON objects use member names that differ from the case class fields you can also use jsonFormat directly.

Note that spray-json for ScalaJS or Scala Native does not support the jsonFormatX methods, and hence using the jsonFormat overloads is required on these platforms.

There is one additional quirk: If you explicitly declare the companion object for your case class the notation above will stop working. You'll have to explicitly refer to the companion objects apply method to fix this:

If your case class is generic in that it takes type parameters itself the jsonFormat methods can also help you. However, there is a little more boilerplate required as you need to add context bounds for all type parameters and explicitly refer to the case classes apply method as in this example:

NullOptions

The NullOptions trait supplies an alternative rendering mode for optional case class members. Normally optional members that are undefined (None) are not rendered at all. By mixing in this trait into your custom JsonProtocol you can enforce the rendering of undefined members as null. (Note that this only affect JSON writing, spray-json will always read missing optional members as well as null optional members as None.)

Providing JsonFormats for other Types

Of course you can also supply (de)serialization logic for types that aren't case classes. Here is one way to do it:

can be handled as above with jsonFormatX, etc. It may be preferable, however, to serialize such instances without object boxing: as "USD 100" instead of {"currency":"USD","amount":100}. This requires explicit (de)serialization logic:

JsonFormat vs. RootJsonFormat

According to the JSON specification not all of the defined JSON value types are allowed at the root level of a JSON document. A JSON string for example (like "foo") does not constitute a legal JSON document by itself. Only JSON objects or JSON arrays are allowed as JSON document roots.

In order to distinguish, on the type-level, "regular" JsonFormats from the ones producing root-level JSON objects or arrays spray-json defines the RootJsonFormat type, which is nothing but a marker specialization of JsonFormat. Libraries supporting spray-json as a means of document serialization might choose to depend on a RootJsonFormat[T] for a custom type T (rather than a "plain" JsonFormat[T]), so as to not allow the rendering of illegal document roots. E.g., the SprayJsonSupport trait of spray-routing is one notable example of such a case.

All default converters in the DefaultJsonProtocol producing JSON objects or arrays are actually implemented as RootJsonFormat. When "manually" implementing a JsonFormat for a custom type T (rather than relying on case class support) you should think about whether you'd like to use instances of T as JSON document roots and choose between a "plain" JsonFormat and a RootJsonFormat accordingly.

JsonFormats for recursive Types

If your type is recursive such as

caseclassFoo(i: Int, foo: Foo)

you need to wrap your format constructor with lazyFormat and supply an explicit type annotation:

Otherwise your code will either not compile (no explicit type annotation) or throw an NPE at runtime (no lazyFormat wrapper). Note, that lazyFormat returns a JsonFormat even if it was given a RootJsonFormat which means it isn't picked up by SprayJsonSupport. To get back a RootJsonFormat just wrap the complete lazyFormat call with another call to rootFormat.

Customizing Parser Settings

The parser can be customized by providing a custom instance of JsonParserSettings to JsonParser.apply or String.parseJson:

Credits

Most of type-class (de)serialization code is nothing but a polished copy of what Debasish Ghosh made available with his SJSON library. These code parts therefore bear his copyright. Additionally the JSON AST model is heavily inspired by the one contributed by Jorge Ortiz to Databinder-Dispatch.