little-json
The Scala library that provides extension methods to javax.json.
Getting Started
To use little-json, start by adding it to your project:
libraryDependencies += "com.github.losizm" %% "little-json" % "6.0.0"
Using Implementation of javax.json
little-json has a runtime dependency to javax.json 1.1.x, and you must add an implementation to your project.
So, for example, include the following in your build to add the Glassfish reference implementation as a dependency:
libraryDependencies += "org.glassfish" % "javax.json" % "1.1.4"
A Taste of little-json
Here's a taste of what little-json offers.
Reading and Writing JSON
little-json is powered by a pair of traits, JsonInput
and JsonOutput
. You provide implementations of these to read and write JSON values.
import javax.json.{ JsonException, JsonObject, JsonValue }
import little.json.{ Json, JsonInput, JsonOutput }
import little.json.Implicits._ // Unleash magic
case class User(id: Int, name: String)
// Define how to read User from JsonValue
implicit val userJsonInput: JsonInput[User] = {
case json: JsonObject => User(json.getInt("id"), json.getString("name"))
case json: JsonValue => throw new JsonException("Unexpected value type")
}
// Define how to write User to JsonValue
implicit val userJsonOutput: JsonOutput[User] = {
case User(id, name) => Json.obj("id" -> id, "name" -> name)
}
// Parse String to JsonValue
val json = Json.parse("""{ "id": 0, "name": "root" }""")
// Read User from JsonValue
val user = json.as[User]
// Write User to JsonValue
val jsonUser = Json.toJson(user)
A special implementation of JsonOutput
is available for writing a collection of objects to a JsonArray
. So, for example, if you define JsonOutput[User]
, you automagically get JsonOutput[Seq[User]]
.
The same applies to JsonInput[User]
. You get JsonInput[Seq[User]]
for free.
val json = Json.parse("""
[{ "id": 0, "name": "root" },
{ "id": 500, "name": "guest" }]
""")
// Read Seq[User] from JsonArray
val users = json.as[Seq[User]]
// In fact, any Traversable will do
val userList = json.as[List[User]]
val userIter = json.as[Iterator[User]]
val userSet = json.as[Set[User]]
// Or even Array
val userArray = json.as[Array[User]]
// Write Seq[User] to JsonArray
val jsonUsers = Json.toJson(users)
Extracting Values from JSON Structure
You can navigate your way through a JsonArray
or JsonObject
to extract values deep inside its structure.
val json = Json.parse("""
{
"computer": {
"name": "localhost",
"users": [
{ "id": 0, "name": "root" },
{ "id": 500, "name": "guest" }
]
}
}
""")
// Get users array from computer object
val users = (json \ "computer" \ "users").as[Seq[User]]
// Get first user (at index 0) in users array
val user = (json \ "computer" \ "users" \ 0).as[User]
// Get name of second user (at index 1) in users array
val name = (json \ "computer" \ "users" \ 1 \ "name").as[String]
// Use long form
val alias = json.get("computer").get("users").get(1).get("name").as[String]
You can also do a recursive lookup.
// Get all "name" values
val names = (json \\ "name") // Seq[JsonValue]("localhost", "root", "guest")
Note, in above example, computer name (localhost) and user names (root and guest) are included in the result.
Streaming JSON
JsonGenerator
and JsonParser
are defined in javax.json.stream
for generating and parsing potentially large JSON structures. JSON is written to and read from streams, instead of entire structure being managed in memory.
little-json gives these classes a bit more power, making it easier for you to read and write your objects in JSON.
Here's the enhanced generator in action.
import java.io.StringWriter
val users = Seq(User(0, "root"), User(500, "guest"))
val writer = new StringWriter()
val generator = Json.createGenerator(writer)
generator.writeStartObject()
// Write array of users one user at a time
// Implicitly convert each user to JsonObject before writing
generator.writeStartArray("one-by-one")
users.foreach(user => generator.write(user))
generator.writeEnd()
// Write array of users in one swoop
// Implicitly convert users to JsonArray before writing
generator.write("all-at-once", users)
generator.writeEnd()
generator.close()
And the enhanced parser in action.
import java.io.StringReader
import javax.json.stream.JsonParser.{ Event => ParserEvent }
val reader = new StringReader(writer.toString)
val parser = Json.createParser(reader)
// Pop events to get to first array
assert(parser.next() == ParserEvent.START_OBJECT)
assert(parser.next() == ParserEvent.KEY_NAME)
assert(parser.getString() == "one-by-one")
assert(parser.next() == ParserEvent.START_ARRAY)
// Get both users one by one (little-json adds JsonParser.nextObject())
val root = parser.nextObject().as[User]
val guest = parser.nextObject().as[User]
// Pop events to get to second array
assert(parser.next() == ParserEvent.END_ARRAY)
assert(parser.next() == ParserEvent.KEY_NAME)
assert(parser.getString() == "all-at-once")
// Get both users all at once (little-json adds JsonParser.nextArray())
val users = parser.nextArray().as[Seq[User]]
assert(parser.next() == ParserEvent.END_OBJECT)
parser.close()
Working with JSON-RPC 2.0
As a bonus, an API is defined for working with JSON-RPC 2.0.
You can build requests and responses with Scala objects.
import javax.json.JsonValue
import little.json.{ Json, JsonAdapter }
import little.json.Implicits._
import little.json.rpc._
case class Params(values: Int*)
// Define adapter for converting params to and from JSON
implicit object ParamsAdapter extends JsonAdapter[Params] {
def reading(json: JsonValue): Params =
Params(json.as[Seq[Int]] : _*)
def writing(params: Params): JsonValue =
Json.toJson(params.values)
}
// Create request with builder
val request = JsonRpcRequest.builder()
.version("2.0")
.id("590d24ae-500a-486c-8d73-8035e78529bd")
.method("sum")
.params(Params(1, 2, 3))
.build()
// Initialize response builder
val responseBuilder = JsonRpcResponse.builder()
.version(request.version)
.id(request.id)
request.method match {
case "sum" =>
val params = request.params.get.as[Params]
// Set result
responseBuilder.result(params.values.sum)
case name =>
// Or set error
responseBuilder.error(MethodNotFound(name))
}
// Create response with builder
val response = responseBuilder.build()
And you can parse them from JSON text.
import little.json.JsonInput
import little.json.Implicits._
import little.json.rpc._
case class Params(values: Int*)
implicit val paramsInput: JsonInput[Params] = {
json => Params(json.as[Seq[Int]] : _*)
}
val request = JsonRpc.parseRequest("""
{
"jsonrpc": "2.0",
"id": "590d24ae-500a-486c-8d73-8035e78529bd",
"method": "sum",
"params": [1, 2, 3]
}
""")
assert(request.version == "2.0")
assert(request.id.stringValue == "590d24ae-500a-486c-8d73-8035e78529bd")
assert(request.method == "sum")
assert(request.params.exists(_.as[Params] == Params(1, 2, 3)))
val response = JsonRpc.parseResponse("""
{
"jsonrpc": "2.0",
"id": "590d24ae-500a-486c-8d73-8035e78529bd",
"result": 6
}
""")
assert(response.version == "2.0")
assert(response.id.stringValue == "590d24ae-500a-486c-8d73-8035e78529bd")
assert(response.result.as[Int] == 6)
API Documentation
See scaladoc for additional details.
License
little-json is licensed under the Apache License, Version 2. See LICENSE file for more information.