• Stars
    star
    241
  • Rank 167,643 (Top 4 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created over 12 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A library to query and update JSON data in Scala.

json-lenses is a library to query and update JSON data.

It has the following features

  • Create queries similar to xpath in a native Scala DSL
  • Retrieve selected elements
  • Easily update selected elements of the immutable spray-json representation
  • Selected elements can be of several cardinalities: scalar values, optional values, and sequences of values
  • Experimental support for json-path syntax

Usage

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

"net.virtual-void" %%  "json-lenses" % "0.6.2"

(json-lenses supports spray-json 1.3.x. For support of an older spray-json version use json-lenses 0.5.4. See https://github.com/jrudolph/json-lenses/tree/v0.5.4-scala-2.11 for the old documentation).

Example

Given this example json document:

val json = """
{ "store": {
    "book": [
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99,
        "isbn": "0-553-21311-3"
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}""".parseJson

All authors in this document are addressed by

import spray.json.lenses.JsonLenses._
import spray.json.DefaultJsonProtocol._

val allAuthors = 'store / 'book / * / 'author

This is called a lens. You can use a lens to retrieve or update the addressed values.

val authorNames = json.extract[String](allAuthors)

To update values use one of the defined operations. To overwrite a value use set, to change a value based on the previous value use modify.

// overwrite all authors' names to "John Doe"
val newJson1 = json.update(allAuthors ! set[String]("John Doe"))

// prepend authors' names with "Ms or Mr "
val newJson2 = json.update(allAuthors ! modify[String]("Ms or Mr " + _))

Here are other interesting queries on the example data:

// The author of the first book in the store
val firstAuthor = "store" / "book" / element(0) / "author"

// The titles of books more expensive than $ 10
val expensiveBookTitles = "store" / "book" / filter("price".is[Double](_ >= 10)) / "title"

// ISBN of all books that have one
val allIsbn = 'store / 'book / * / 'isbn.? // not all books have ISBNs, so the selection must be optional

Documentation

The concept

The concept of lenses is a powerful concept not only applicable to json objects. A lens is an updatable, composable view into a data structure. There are simple lenses which provide just the functionality to "go one level deeper" in the data structure (for json: accessing fields of a json object or elements of a json array) and there are lenses which compose other lenses to allow a deeper view into the data structure.

See this answer on stack overflow and this presentation for more info on the general topic of lenses.

The json lens

The json lenses in this project are specialized lenses to extract and update json data. A lens has this form (almost, in reality it's a bit more complicated):

// not the real code
trait Lens {
  def retr: JsValue => JsValue
  def updated(f: JsValue => JsValue)(parent: JsValue): JsValue
}

It has a method retr which when called with a json value will return the value this lens points to. The other method updated takes a parent json value and returns an updated copy of it with function f applied to the child value this lens points to.

In contrast to a more general lens input and output types of the json lens are fixed: both have to be an instance of JsValue.

Support for multiple cardinalities

The simple scheme introduced in the last section is actually too simple to support more than the absolute simplest lenses. One basic requirement is to extract a list of values or an optional value. Therefore, lenses are parameterized by the container type for the cardinality of the lens.

// still not the real code
trait Lens[M[_]] {
  def retr: JsValue => M[JsValue]
  // ...

Scalar lenses are of type Lens[Id] (Id being defined as the identity type constructor, so Id[T] == T). Optional lenses are of type Lens[Option]. Lenses returning a sequence of values are of type Lens[Seq].

It's interesting what happens when two lenses of different cardinality are joined: The rule is to always return a lens of the more general container type, i.e. the one with the greater cardinality. Joining two lenses of sequences of values results in a flattened sequence of all the results. See the source code of JsonLenses.combine and the Join type class to see how this is done.

Error handling

To support proper error handling and recovery from errors (in some cases) failure is always assumed as a possible outcome. This is reflected by returning an Either value from almost all functions which may fail. The real declaration of the Lens type therefore looks more like this:

type Validated[T] = Either[Exception, T]
type SafeJsValue = Validated[JsValue]

trait Lens[M[_]] {
  def retr: JsValue => Validated[M[JsValue]]
  def updated(f: SafeJsValue => SafeJsValue)(parent: JsValue): SafeJsValue
}

The result of retr is not always a Right(jsValue) but may fail with Left(exception) as well. The same for updated: the update operation may also fail. However, in the update case there are two other possibilities encoded as well: Retrieval of a value may fail but the update operation f may still succeed (for example, when setting a previously undefined value) or the update operation itself fails in which case the complete update operation fails as well.

Predefined lenses

When working with lenses you normally don't have to worry about the details but you can just choose and combine lenses from the following list.

Field access
  • field(name: String): This lens assumes that the target value is a json object and selects the field with the specified name. Because this is the most common lens there are shortcut implicit conversions defined from String and Symbol values.

  • optionalField(name: String): This lens assumes a JsObject and tries to select the field of the given name. If the field is missing it just isn't selected. field.? is a shortcut for this lens (where field should be an expression of type Symbol or String).

Element access
  • elements or *: This lens selects all the elements of a json array. If you combine this lens with another one you have to make sure that the next lens will match for all the elements of the array, otherwise it is an error. Use allMatching if you want to exclude elements not matching nested lenses.
  • element(idx: Int): Selects the element of a json array with the given index.
  • find(predicate: JsValue => Boolean): Selects all elements of a json array which match a certain predicated. In the common case you don't want to work directly on JsValues so you can use lenses as well to define the predicate. Use Lens.is[T](pred: T => Boolean) to lift a predicate from the value level to the JsValue level. E.g. use 'fullName.is[String](_ startsWith "Joe") to create a predicate which checks if a value is a json object and its field fullName is a string starting with "Joe".
  • allMatching(next: Lens): A combination of combine and elements. Selects elements matching the next lens and then applies the next lens.
  • arrayOrSingletonAsArray: Makes sure that the result is always a json array by interpreting a value that is no array as an singleton array containing just that value.
Combination
  • combine: This lens combines two lenses by executing the second one on the result of the first one. Use it to access nested elements. Because its use is so common there's a shortcut operator, Lens./, which you can use to 'chain' lenses: e.g. use 'abc / 'def access field 'def' inside the object in field 'abc' of the root json value passed to the lens.

Predefined update operations

Currently, there are these update operations defined:

  • set[T: JsonWriter](t: => T) uses the JsonWriter to set the value to a constant value.
  • modify[T: JsonFormat](f: T => T) applies an update function to an existing value. The value has to be serializable from and to T.
  • modifyOrDeleteField[T: JsonFormat](f: T => Option[T]) can be used in conjunction with the optionalField lens. It applies an update function to an existing value. If the function returns Some(newValue) the field value will be updated to the new value. If the function returns None the field will be deleted.
  • setOrUpdateField[T: JsonFormat](default: => T)(f: T => T) can be used in conjunction with the optionalField lens. It allows to decide which value to set an maybe previously missing value to based on the previous state of the field.

Using lenses to extract or update json data

To extract a value from json data using a lens you can use either lens.get[T](json) or the equivalent json.extract[T](lens) which both throw an exception in case something fails or use lens.tryGet[T](json) which returns an Either value you can match on (or use Either's functions) to check for errors.

To update a value use either (lens ! operation).apply(json) or json.update(lens ! operation). You can also use the fancy val newJson = json(lens) = "abc" to set a value if you like that syntax. These operations throw an exception in the error case. Use lens.updated(operation)(json) to get an Either result to process errors.

json-path support

Use JsonLenses.fromPath which tries to create a lens from a json-path expression. Currently not all of the syntax of json-path is supported. Please file an issue if something is missing.

API Documentation

You can find the documentation for the json-lenses API here.

What's missing

  • Richer set of operations (e.g. append and delete elements from arrays)
  • ...

Please file an issue at the issue tracker if you need something.

Mailing list

Please use the spray-user mailing list to discuss json-lenses issues.

License

spray-json is licensed under APL 2.0.

More Repositories

1

pekko-http-scala-js-websocket-chat

An example app that integrates pekko-http and scala-js to implement a websocket chat
Scala
342
star
2

sbt-optimizer

An sbt plugin to analyze sbt task execution
Scala
87
star
3

speed

Looping like a pro
Scala
75
star
4

llama2.scala

Inference Llama 2 in Scala with AVX2 kernels in C (A port of llama2.c from Andrej Karpathy)
Scala
68
star
5

scala-world-2015

Source code for the scala.world 2015 talk about akka-stream / akka-http
Scala
66
star
6

sbt-cross-building

Enable cross-building of sbt plugins
Scala
47
star
7

bootpgm

Demonstration of a Windows Boot Program using Window's Native API
C++
30
star
8

scodec-cheatsheet

Inofficial scodec cheatsheet
Scala
27
star
9

scala-enhanced-strings

Variable interpolation and formatting strings for Scala
Scala
26
star
10

bytecode

Mnemonics - Bytecode generation for scala
Scala
21
star
11

restic-browser

A web interface to restic backup repositories
Scala
20
star
12

Pooling-web-server

A pooling web server in Java
Java
13
star
13

effective-akka-http-example

Example code used during the "Effective Akka HTTP" talk at Reactive Systems Hamburg, November 2016
Scala
13
star
14

better-future-exceptions

A POC for better exception reporting for futures
Scala
12
star
15

spray-client-example

A simple starting point for creating applications using spray-client
Scala
11
star
16

sbt-hackers-digest

An sbt plugin to create annotations for the Github UI when sbt is run in Github Actions
Scala
11
star
17

akka-graal

Testing Graal VM AOT compilation for Akka
Scala
8
star
18

decoders

A collection of file format decoders
Scala
8
star
19

rescue

Rescue from drowning in implicits
Scala
8
star
20

scalac-plugin.g8

A g8 template for a scalac plugin built with sbt
Scala
7
star
21

junit_xml_listener

Fork from Christoph Henkelmann
Scala
7
star
22

vesuvius-gui

Rust GUI for browsing vesuvius project data
Rust
7
star
23

dumpster

A small webdav server written in Scala
Scala
6
star
24

template-web-akka-http-scala-js

My default template to create akka-http / Scala JS web services
Scala
5
star
25

multi-scaladoc-browser

A simple webserver that can integrate several sources of scaladocs
Scala
5
star
26

scolorz

A NodeBox inspired Processing clone written for the Scala language
Java
4
star
27

vesuvius-browser

Unofficial Vesuvius Challenge data segment browser
JavaScript
4
star
28

object-browser

Android Object Browser
Java
4
star
29

scala-io-uring

Experiments with Scala and io_uring
Scala
4
star
30

akka-http-workshop

Source code and instructions for the akka-http tutorial at bobkonf 2016 in Berlin
Scala
4
star
31

jcosmos

Establishing order in the chaos of Java's modules
Java
3
star
32

signal-exporter

Scala
3
star
33

scala-reflectionator

Scala type-safe reflection helpers
Scala
3
star
34

scala-symbol-browser

A web interface into Scala compiler's symbol table
Scala
3
star
35

fast-interpolator

A faster string interpolator for scala with same semantics as s"" but expanding at compile-time
Scala
3
star
36

FUSE4Java

FUSE (Filesystem in Userspace) 4 Java
C
3
star
37

graphicterm

Scala
2
star
38

codecs

Scala framework for bidrectional functions
Scala
2
star
39

do-plugins

Inofficial git mirror of GNOME-do plugins (https://launchpad.net/do-plugins)
C#
2
star
40

scala-stuff

assorted Scala stuff
Scala
2
star
41

scala-release-train

Release train script
Scala
2
star
42

facelets

Inofficial mirror of the public CVS repository
Java
2
star
43

rogue-bytecodes

Switching-off the JVM verifier, what happens?
Java
2
star
44

java-direct-data-store

An experiment to dump Java objects into a memory-mapped data store accessible without (de)serialization
C
2
star
45

simple-macro-template

A simple sample project to play around with the upcoming Scala 2.10 version
Scala
2
star
46

sbt-error-navigator

Special error console for sbt when there are hundreds of compile errors to work through (work-in-progress)
Scala
2
star
47

bit-extractors

Scala extractors for pattern matching values from bits
Scala
2
star
48

PhoneApp

Inofficial mirror of an application to connect your android phone to your internet router for calls
Java
1
star
49

scala-broken-java-varargs

A small Java/Scala @varargs cabinet of horrors
Java
1
star
50

libscala

bash-centric tools for scala developers
Shell
1
star
51

spray-aspects

Scala
1
star
52

jboss-el

Inofficial repository for jboss-el
Java
1
star
53

dualis

Interactive data-structure (reverse) engineering
Scala
1
star
54

es-template

Template for using the scala enhanced string macro
Scala
1
star
55

scala-fast-map

A scala compiler plugin for easy lifting of operations to the monad level
Scala
1
star
56

jrudolph.github.com

HTML
1
star
57

typesafe-lambda

Typesafe-by-construction Simple Typed Lambda Calculus implementation
Scala
1
star
58

parboiled-compiled

A compiler for parboiled matchers for more speed
Scala
1
star
59

vold

Inofficial Android system/core mirror
C
1
star
60

fast-android-reloader

Java
1
star
61

r4

1
star
62

dancing-data

Utility functions to read different clipboard formats
1
star
63

scala-library-disassembled

Track bytecode changes in the scala library
1
star
64

sporophyte

Playground for experimenting with spore syntax (without the actual static type-checking)
Scala
1
star
65

dsp-stuff

Experimental DSP stuff in scala
Scala
1
star
66

SWeeTgui

Some helper tools to use SWT from Scala
Scala
1
star
67

spray-benchmark

Scala
1
star
68

strom

1
star
69

future-exception-aspects

A library of aspects that will improve exceptions reported by Scala Futures
Scala
1
star
70

scitter

Inofficial scitter mirror
1
star
71

scala-standalone-console

A small wrapper around the console which allows one-click Scala console access
Java
1
star