/circe

Yet another JSON library for Scala

Primary LanguageScalaApache License 2.0Apache-2.0

circe

Build status Coverage status Gitter Maven Central

circe (pronounced SUR-see, or KEER-kee in classical Greek) is a JSON library for Scala (and Scala.js). The rest of this page tries to give some justification for its existence. There are also API docs.

circe's working title was jfc, which stood for "JSON for cats". The name was changed for a number of reasons.

Table of contents

  1. Quick start
  2. Why?
  3. Dependencies and modularity
  4. Parsing
  5. Lenses
  6. Codec derivation
  7. Aliases
  8. Documentation
  9. Testing
  10. Performance
  11. Usage
  12. Encoding and decoding
  13. Transforming JSON
  14. Contributors and participation
  15. Warnings and known issues
  16. License

Quick start

circe is published to Maven Central and cross-built for Scala 2.10 and 2.11, so you can just add the following to your build:

val circeVersion = "0.4.1"

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core",
  "io.circe" %% "circe-generic",
  "io.circe" %% "circe-parser"
).map(_ % circeVersion)

If you are using circe's generic derivation (with Scala 2.10), or the macro annotation @JsonCodec (with Scala 2.10 or Scala 2.11), you'll also need to include the MacroParadise compiler plugin in your build:

addCompilerPlugin(
  "org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
)

Then type sbt console to start a REPL and then paste the following (this will also work from the root directory of this repository):

scala> import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
import io.circe._
import io.circe.generic.auto._
import io.circe.parser._
import io.circe.syntax._

scala> sealed trait Foo
defined trait Foo

scala> case class Bar(xs: List[String]) extends Foo
defined class Bar

scala> case class Qux(i: Int, d: Option[Double]) extends Foo
defined class Qux

scala> val foo: Foo = Qux(13, Some(14.0))
foo: Foo = Qux(13,Some(14.0))

scala> foo.asJson.noSpaces
res0: String = {"Qux":{"d":14.0,"i":13}}

scala> decode[Foo](foo.asJson.spaces4)
res1: cats.data.Xor[io.circe.Error,Foo] = Right(Qux(13,Some(14.0)))

No boilerplate, no runtime reflection.

Why?

Argonaut is a great library. It's by far the best JSON library for Scala, and the best JSON library on the JVM. If you're doing anything with JSON in Scala, you should be using Argonaut.

circe is a fork of Argonaut with a few important differences.

Dependencies and modularity

circe depends on cats instead of Scalaz, and the core project has only one dependency (cats-core).

Other subprojects bring in dependencies on Jawn (for parsing in the jawn subproject), Shapeless (for automatic codec derivation in generic), and Twitter Util (for tools for asynchronous parsing in async), but it would be possible to replace the functionality provided by these subprojects with alternative implementations that use other libraries.

Parsing

circe doesn't include a JSON parser in the core project, which is focused on the JSON AST, zippers, and codecs. The jawn subproject provides support for parsing JSON via a Jawn facade. Jawn is fast, it offers asynchronous parsing, and best of all it lets us drop a lot of the fussiest code in Argonaut. The jackson subproject supports using Jackson for both parsing and printing.

circe also provides a parser subproject that provides parsing support for Scala.js, with JVM parsing provided by io.circe.jawn and JavaScript parsing from scalajs.js.JSON.

Lenses

circe doesn't use or provide lenses in the core project. This is related to the first point above, since Monocle has a Scalaz dependency, but we also feel that it simplifies the API. The 0.3.0 release added an experimental optics subproject that provides Monocle lenses (note that this will require your project to depend on both Scalaz and cats).

Codec derivation

circe does not use macros or provide any kind of automatic derivation in the core project. Instead of Argonaut's limited macro-based derivation (which does not support sealed trait hierarchies, for example), circe includes a subproject (generic) that provides generic codec derivation using Shapeless.

This subproject is currently a simplified port of argonaut-shapeless that provides fully automatic derivation of instances for case classes and sealed trait hierarchies. It also includes derivation of "incomplete" case class instances (see my recent blog post for details).

Aliases

circe aims to simplify Argonaut's API by removing all operator aliases. This is largely a matter of personal taste, and may change in the future.

Documentation

The Argonaut documentation is good, but it could be better: to take just one example, it can be hard to tell at a glance why there are three different Cursor, HCursor, and ACursor types. In this particular case, circe introduces an abstraction over cursors that makes the relationship clearer and allows these three types to share API documentation.

Testing

I'd like to provide more complete test coverage (in part via Discipline), but it's early days for this.

Performance

circe aims to be more focused on performance. I'm still experimenting with the right balance, but I'm open to using mutability, inheritance, and all kinds of other horrible things under the hood if they make circe faster (the public API does not and will never expose any of this, though).

My initial benchmarks suggest this is at least kind of working (higher numbers are better):

Benchmark                          Mode  Cnt        Score       Error  Units
DecodingBenchmark.decodeFoosC     thrpt   40     3711.680 ±    22.766  ops/s
DecodingBenchmark.decodeFoosA     thrpt   40     1519.045 ±    11.373  ops/s
DecodingBenchmark.decodeFoosP     thrpt   40     2032.834 ±    27.033  ops/s
DecodingBenchmark.decodeFoosPico  thrpt   40     2003.106 ±    10.463  ops/s
DecodingBenchmark.decodeFoosS     thrpt   40     7053.699 ±    35.127  ops/s

DecodingBenchmark.decodeIntsC     thrpt   40    19101.875 ±   324.123  ops/s
DecodingBenchmark.decodeIntsA     thrpt   40     8000.093 ±   215.702  ops/s
DecodingBenchmark.decodeIntsP     thrpt   40    18160.031 ±    68.777  ops/s
DecodingBenchmark.decodeIntsPico  thrpt   40    11979.085 ±    89.793  ops/s
DecodingBenchmark.decodeIntsS     thrpt   40    81279.228 ±  1203.751  ops/s

EncodingBenchmark.encodeFoosC     thrpt   40     7353.158 ±   133.633  ops/s
EncodingBenchmark.encodeFoosA     thrpt   40     5638.358 ±    30.315  ops/s
EncodingBenchmark.encodeFoosP     thrpt   40     2324.075 ±    17.868  ops/s
EncodingBenchmark.encodeFoosPico  thrpt   40     5056.317 ±    45.876  ops/s
EncodingBenchmark.encodeFoosS     thrpt   40     5307.422 ±    29.666  ops/s

EncodingBenchmark.encodeIntsC     thrpt   40   117885.093 ±  2151.059  ops/s
EncodingBenchmark.encodeIntsA     thrpt   40    72986.276 ±  1561.295  ops/s
EncodingBenchmark.encodeIntsP     thrpt   40    55117.582 ±   650.154  ops/s
EncodingBenchmark.encodeIntsPico  thrpt   40    31602.757 ±   351.578  ops/s
EncodingBenchmark.encodeIntsS     thrpt   40    40509.667 ±   560.439  ops/s

ParsingBenchmark.parseFoosC       thrpt   40     2869.779 ±    61.898  ops/s
ParsingBenchmark.parseFoosA       thrpt   40     2615.299 ±    25.881  ops/s
ParsingBenchmark.parseFoosP       thrpt   40     1970.493 ±    90.383  ops/s
ParsingBenchmark.parseFoosPico    thrpt   40     3113.232 ±    29.081  ops/s
ParsingBenchmark.parseFoosS       thrpt   40     3725.056 ±    68.794  ops/s

ParsingBenchmark.parseIntsC       thrpt   40    13062.151 ±   209.713  ops/s
ParsingBenchmark.parseIntsA       thrpt   40    11066.850 ±   159.308  ops/s
ParsingBenchmark.parseIntsP       thrpt   40    18980.265 ±    91.351  ops/s
ParsingBenchmark.parseIntsPico    thrpt   40    15184.314 ±    37.808  ops/s
ParsingBenchmark.parseIntsS       thrpt   40    15495.935 ±   388.922  ops/s

PrintingBenchmark.printFoosC      thrpt   40     4090.218 ±    38.804  ops/s
PrintingBenchmark.printFoosA      thrpt   40     2863.570 ±    19.091  ops/s
PrintingBenchmark.printFoosP      thrpt   40     9042.816 ±    49.199  ops/s
PrintingBenchmark.printFoosPico   thrpt   40     4759.601 ±    20.467  ops/s
PrintingBenchmark.printFoosS      thrpt   40     7297.047 ±    28.168  ops/s

PrintingBenchmark.printIntsC      thrpt   40    24596.715 ±    66.366  ops/s
PrintingBenchmark.printIntsA      thrpt   40    15611.121 ±   140.017  ops/s
PrintingBenchmark.printIntsP      thrpt   40    66283.874 ±   731.534  ops/s
PrintingBenchmark.printIntsPico   thrpt   40    23703.796 ±   188.186  ops/s
PrintingBenchmark.printIntsS      thrpt   40    53015.753 ±   462.472  ops/s

And allocation rates (lower is better):

Benchmark                                              Mode  Cnt        Score        Error   Units

DecodingBenchmark.decodeFoosC:gc.alloc.rate.norm      thrpt   20  1308424.455 ±      0.881    B/op
DecodingBenchmark.decodeFoosA:gc.alloc.rate.norm      thrpt   20  3779097.640 ±      2.456    B/op
DecodingBenchmark.decodeFoosP:gc.alloc.rate.norm      thrpt   20  2201336.820 ±      1.588    B/op
DecodingBenchmark.decodeFoosPico:gc.alloc.rate.norm   thrpt   20   506696.832 ±      1.608    B/op
DecodingBenchmark.decodeFoosS:gc.alloc.rate.norm      thrpt   20   273184.238 ±      0.458    B/op

DecodingBenchmark.decodeIntsC:gc.alloc.rate.norm      thrpt   20   291360.090 ±      0.174    B/op
DecodingBenchmark.decodeIntsA:gc.alloc.rate.norm      thrpt   20   655448.200 ±      0.387    B/op
DecodingBenchmark.decodeIntsP:gc.alloc.rate.norm      thrpt   20   369144.097 ±      0.189    B/op
DecodingBenchmark.decodeIntsPico:gc.alloc.rate.norm   thrpt   20   235400.144 ±      0.280    B/op
DecodingBenchmark.decodeIntsS:gc.alloc.rate.norm      thrpt   20    38136.021 ±      0.041    B/op

EncodingBenchmark.encodeFoosC:gc.alloc.rate.norm      thrpt   20   395272.225 ±      0.433    B/op
EncodingBenchmark.encodeFoosA:gc.alloc.rate.norm      thrpt   20   521136.306 ±      0.595    B/op
EncodingBenchmark.encodeFoosP:gc.alloc.rate.norm      thrpt   20  1367800.719 ±      7.263    B/op
EncodingBenchmark.encodeFoosPico:gc.alloc.rate.norm   thrpt   20   281992.346 ±      0.674    B/op
EncodingBenchmark.encodeFoosS:gc.alloc.rate.norm      thrpt   20   377856.318 ±      0.615    B/op

EncodingBenchmark.encodeIntsC:gc.alloc.rate.norm      thrpt   20    64160.016 ±      7.129    B/op
EncodingBenchmark.encodeIntsA:gc.alloc.rate.norm      thrpt   20    80152.023 ±      0.044    B/op
EncodingBenchmark.encodeIntsP:gc.alloc.rate.norm      thrpt   20    71352.030 ±      0.058    B/op
EncodingBenchmark.encodeIntsPico:gc.alloc.rate.norm   thrpt   20    58992.057 ±      0.115    B/op
EncodingBenchmark.encodeIntsS:gc.alloc.rate.norm      thrpt   20    76176.042 ±      0.081    B/op

ParsingBenchmark.parseFoosC:gc.alloc.rate.norm        thrpt   20   765800.586 ±      1.133    B/op
ParsingBenchmark.parseFoosA:gc.alloc.rate.norm        thrpt   20  1488760.635 ±      1.228    B/op
ParsingBenchmark.parseFoosP:gc.alloc.rate.norm        thrpt   20   987720.805 ±      1.551    B/op
ParsingBenchmark.parseFoosPico:gc.alloc.rate.norm     thrpt   20   639464.525 ±      1.014    B/op
ParsingBenchmark.parseFoosS:gc.alloc.rate.norm        thrpt   20   252256.440 ±      0.838    B/op

ParsingBenchmark.parseIntsC:gc.alloc.rate.norm        thrpt   20   121272.129 ±      0.250    B/op
ParsingBenchmark.parseIntsA:gc.alloc.rate.norm        thrpt   20   310280.151 ±      0.289    B/op
ParsingBenchmark.parseIntsP:gc.alloc.rate.norm        thrpt   20   216448.089 ±      0.171    B/op
ParsingBenchmark.parseIntsPico:gc.alloc.rate.norm     thrpt   20   141808.118 ±      0.239    B/op
ParsingBenchmark.parseIntsS:gc.alloc.rate.norm        thrpt   20   109000.117 ±      0.229    B/op

PrintingBenchmark.printFoosC:gc.alloc.rate.norm       thrpt   20   425240.419 ±      0.810    B/op
PrintingBenchmark.printFoosA:gc.alloc.rate.norm       thrpt   20   621288.585 ±   1069.068    B/op
PrintingBenchmark.printFoosP:gc.alloc.rate.norm       thrpt   20   351360.184 ±      0.356    B/op
PrintingBenchmark.printFoosPico:gc.alloc.rate.norm    thrpt   20   431268.348 ±   1058.404    B/op
PrintingBenchmark.printFoosS:gc.alloc.rate.norm       thrpt   20   372992.228 ±      0.442    B/op

PrintingBenchmark.printIntsC:gc.alloc.rate.norm       thrpt   20    74464.067 ±      7.127    B/op
PrintingBenchmark.printIntsA:gc.alloc.rate.norm       thrpt   20   239712.107 ±      0.206    B/op
PrintingBenchmark.printIntsP:gc.alloc.rate.norm       thrpt   20    24144.025 ±      0.048    B/op
PrintingBenchmark.printIntsPico:gc.alloc.rate.norm    thrpt   20    95472.072 ±      0.140    B/op
PrintingBenchmark.printIntsS:gc.alloc.rate.norm       thrpt   20    24048.032 ±      0.062    B/op

The Foos benchmarks work with a map containing case class values, and the Ints ones are an array of integers. C suffixes indicate circe's throughput, A is for Argonaut, P is for play-json, Pico is for picopickle, and S is for spray-json. Note that spray-json's approach to failure handling is different from the approaches of the other libraries listed here (it simply throws exceptions), and this difference should be taken into account when comparing its results with the others.

Usage

This section needs a lot of expanding.

Encoding and decoding

circe uses Encoder and Decoder type classes for encoding and decoding. An Encoder[A] instance provides a function that will convert any A to a JSON, and a Decoder[A] takes a Json value to either an exception or an A. circe provides implicit instances of these type classes for many types from the Scala standard library, including Int, String, and others. It also provides instances for List[A], Option[A], and other generic types, but only if A has an Encoder instance.

Transforming JSON

Suppose we have the following JSON document:

import io.circe._, io.circe.generic.auto._, io.circe.jawn._, io.circe.syntax._
import cats.data.Xor

val json: String = """
  {
    "id": "c730433b-082c-4984-9d66-855c243266f0",
    "name": "Foo",
    "counts": [1, 2, 3],
    "values": {
      "bar": true,
      "baz": 100.001,
      "qux": ["a", "b"]
    }
  }
"""

val doc: Json = parse(json).getOrElse(Json.Null)

In order to transform this document we need to create an HCursor with the focus at the document's root:

val cursor: HCursor = doc.hcursor

We can then use various operations to move the focus of the cursor around the document and to "modify" the current focus:

val reversedNameCursor: ACursor =
  cursor.downField("name").withFocus(_.mapString(_.reverse))

We can then return to the root of the document and return its value with top:

val reversedName: Option[Json] = reversedNameCursor.top

The result will contain the original document with the "name" field reversed.

Contributors and participation

circe is a fork of Argonaut, and if you find it at all useful, you should thank Mark Hibberd, Tony Morris, Kenji Yoshida, and the rest of the Argonaut contributors.

circe is currently maintained by Travis Brown, Alexandre Archambault, and Vladimir Kostyukov. After the 0.4.0 release, all pull requests will require two sign-offs by a maintainer to be merged.

The circe project supports the Typelevel code of conduct and wants all of its channels (Gitter, GitHub, etc.) to be welcoming environments for everyone.

Please see the contributors' guide for details on how to submit a pull request.

Warnings and known issues

  1. Please note that generic derivation will not work on Scala 2.10 unless you've added the Macro Paradise plugin to your build. See the quick start section above for details.

  2. Generic derivation may not work as expected when the type definitions that you're trying to derive instances for are at the same level as the attempted derivation. For example:

scala> import io.circe.Decoder, io.circe.generic.auto._ import io.circe.Decoder import io.circe.generic.auto._

scala> sealed trait A; case object B extends A; object X { val d = Decoder[A] } defined trait A defined object B defined object X

scala> object X { sealed trait A; case object B extends A; val d = Decoder[A] } :19: error: could not find implicit value for parameter d: io.circe.Decoder[X.A] object X { sealed trait A; case object B extends A; val d = Decoder[A] } ^


This is unfortunately a limitation of the macro API that Shapeless uses to derive the generic
representation of the sealed trait. You can manually define these instances, or you can arrange
the sealed trait definition so that it is not in the same immediate scope as the attempted
derivation (which is typically what you want, anyway).
3. For large or deeply-nested case classes and sealed trait hierarchies, the generic derivation
provided by the `generic` subproject may stack overflow during compilation, which will result in
the derived encoders or decoders simply not being found. Increasing the stack size available to
the compiler (e.g. with `sbt -J-Xss64m` if you're using SBT) will help in many cases, but we have
at least [one report][very-large-adt] of a case where it doesn't.
4. More generally, the generic derivation provided by the `generic` subproject works for a wide
range of test cases, and is likely to _just work_ for you, but it relies on macros (provided by
Shapeless) that rely on compiler functionality that is not always perfectly robust
("[SI-7046][si-7046] is like [playing roulette][si-7046-roulette]"), and if you're running into
problems, it's likely that they're not your fault. Please file an issue here or ask a question on
the [Gitter channel][gitter], and we'll do our best to figure out whether the problem is
something we can fix.
5. When using the `io.circe.generic.JsonCodec` annotation, the following will not compile:

```scala
import io.circe.generic.JsonCodec

@JsonCodec sealed trait A
case class B(b: String) extends A
case class C(c: Int) extends A

In cases like this it's necessary to define a companion object for the root type after all of the leaf types:

import io.circe.generic.JsonCodec

@JsonCodec sealed trait A
case class B(b: String) extends A
case class C(c: Int) extends A

object A

See this issue for additional discussion (this workaround may not be necessary in future versions).

  1. circe's representation of numbers is designed not to lose precision during decoding into integral or arbitrary-precision types, but precision may still be lost during parsing. This shouldn't happen when using Jawn for parsing, but scalajs.js.JSON parses JSON numbers into a floating point representation that may lose precision (even when decoding into a type like BigDecimal; see this issue for an example).

License

circe is licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.