{{announcement.body}}
{{announcement.title}}

JSON Values and the Joy of Scala

DZone 's Guide to

JSON Values and the Joy of Scala

In this article, we introduce json-values, a Scala library that makes use of persistent data structures to allow users to better handle JSON.

· Java Zone ·
Free Resource

One of the most important aspects of functional programming is immutable data structures, better known as values. Updating these structures using the copy-on-write approach is very inefficient, and this is the reason why persistent data structures were created. 

On the other hand, JSON is a lightweight, text-based, language-independent data interchange format. It's become so popular due to its simplicity. There are a lot of libraries out there to work with JSON in the JVM ecosystem; however, none of them use persistent data structures.

In most cases, those libraries parse a string or array of bytes into an object. The thing is, why do that? JSON is a great structure. It's simple, easy to aggregate, ease to create, easy to reason about, so why create yet another abstraction over JSON?

Moreover, there are many architectures that work with JSON end-to-end. Going from JSON to objects or strings back and forth is not very efficient, especially when copy-on-write is the only option to avoid mutation. All these points are way better elaborated in the talk the value of valuesa masterpiece from Rich HickeyJson-values, the library I'm introducing here, was named after that talk.

json-values is a functional JSON library in Scala that uses persistent data structures. In this first article, we are going to focus on two more important aspects of software development where json-values can make a difference: data validation and testing.

To test json-values, I did property-based-testing with ScalaCheck. It's a great library where creating generators and composing them is very simple. To generate JSON values, I created json-values-generators, a series of generators to work with ScalaCheck. I argue that it's the most declarative and beautiful Json generator in the world!

You may also like: Using JSON With Play and Scala.

Validation is extremely important in software. Corrupt data can propagate throughout your system and cause a nightmare. Errors that blow up in your face are way better! If you think about it, the definition, validation, and generation of a JSON value could be implemented using the same data structure; after all, the three of them are just bindings with different elements: values, value generators, or value specs. Let's check out an example:

Scala




xxxxxxxxxx
1
50


1
import value.{JsObj,JsArray}
2
import value.Preamble._
3
 
4
// DEFINING A JSON
5
 
6
JsObj("name" -> "Rafael",
7
      "age" -> 37,
8
      "languages" -> JsArray("Haskell", "Scala", "Java", "Clojure")
9
      "github" -> "imrafaelmerino",
10
      "profession" -> "frustrated consultant",
11
      "address" -> JsObj("city" -> "Madrid",
12
                         "location" -> JsArray(40.566, 87.987),
13
                         "country" -> "ES"
14
                       )
15
     )
16
 
17
// DEFINING A JSON SPEC
18
 
19
import value.spec.{JsObjSpec,JsArraySpec}
20
import value.spec.JsNumberSpecs._
21
import value.spec.JsStrSpecs._
22
import value.spec.JsArraySpecs._
23
 
24
JsObjSpec("name" -> str,
25
          "age" -> int,
26
          "languages" -> arrayOfStr,
27
          "github" -> str,
28
          "profession" -> str,
29
          "address" -> JsObjSpec("number" -> str,
30
                                 "location" -> JsArraySpec(decimal, decimal),
31
                                 "country" -> str
32
                                 )
33
         )
34
 
35
// DEFINING A JSON GENERATOR
36
 
37
import valuegen.{JsObjGen,JsArrayGen}
38
import valuegen.Preamble._
39
import org.scalacheck.Arbitrary.arbitrary
40
import org.scalacheck.Gen
41
 
42
JsObjGen("name" -> arbitrary[String],
43
         "age" -> Gen.choose(18,120),
44
         "profession" -> arbitrary[String],
45
         "address" -> JsObjGen("city" -> arbitrary[String],
46
                               "location" -> JsArrayGen(doubleGen,doubleGen) 
47
                               "country" -> Gen.oneOf("ES","PO")
48
                               )
49
         )
50
 



As you can see, creating specs and generators is as simple as creating raw JSON. Writing specs and generators for our tests is child's play. It has enormous advantages for development, such as:

  • Increase productivity.
  • More readable code. The more readable code is, the easier it is to maintain and reason about that code.

Let's focus for a while on what I call json-spec. I named it after spec, a Clojure library. It's really powerful and composable. Any spec can be defined in terms of predicates. Let's put a more elaborated example:

Java




x


 
1
val ageSpec: Int => Result = age => 
2
  if(age < 0) Invalid("you must be kidding!")
3
  else if(age > 120) Invalid("you can stil fill out forms!!")
4
  else Valid  
5
 
6
val noLongerThat: Int => String => Result = max => value =>
7
  if(value.length > max) Invalid("too long")
8
  else Valid
9
 
10
val personSpec = JsObjSpec("name" -> str(noLongerThat(100)),
11
                           "last_name" -> str(noLongerThat(200)),
12
                           "profession" -> str(noLongerThat(200), nullable=true),
13
                           "country" -> enum("ES","PO"),               
14
                           "languages" -> arrayOfStr(elemNullable=false),
15
                           "age" -> intSuchThat(ageSpec,required=false),
16
                           * -> any 
17
                           )
18
// * -> any means: any binding different than the specified is allowed
19
 
20
// specs are easy to compose
21
 
22
 val address = JsObjSpec("number" -> str,
23
                         "street" -> str,
24
                         "city" -> str,
25
                         "location" -> JsArraySpec(double, double)
26
                       )
27
 
28
val personWithAddressSpec = personSpec + ("address", address)



A spec can be used to validate existing JSON:

Scala




xxxxxxxxxx
1
10


1
import value.JsObjParser
2
import value.Invalid
3
import value.JsPath
4
 
5
val spec:JsObjSpec = ??? // a json spec
6
val str:String = ???     // a string representing a json 
7
val json:Either[Invalid,JsObj] = JsObjParser.parse(str) 
8
 
9
// a lazy list of path/error pairs is returned
10
val errors:LazyList[(JsPath,InvalidValue)] = json.fold(throw _)
11
                                                     (obj => obj.validate(spec))



Returning a lazy list decouples the validate function from its consumers. Consumers can take only an error if they are just interested in whether the JSON is valid or not, or they can take them all to print out a report.

In both cases, they are invoking the same function. A json-spec can be also used to parse a string or an array of bytes into JSON. Instead of parsing the whole string and then validating it, we can validate the JSON while parsing it and stop the parsing as soon as an error happens. After all, failing fast is important as well!

Scala


Let's move on and introduce some of the most important functions in the library: 

Scala


One important thing about the map functions listed above is that they are functors, so the structure of the JSON doesn't change.

Some optics to work with json-values have been defined in a different library. I didn't want to be opinionated about anything. There are some libraries to create optics out there and the users of json-values are free to use their favorite ones. I use monocle. For example, Prisms are really useful to pass in functions to filter and map:

Scala




xxxxxxxxxx
1
19


 
1
import JsStrOptics.toJsStr
2
// toJsStr :: Prism[JsValue,String]
3
 
4
val toLowerCase = toJsStr.modify(_.toLowerCase)
5
// JsValue => JsValue
6
 
7
val trim = toJsStr.modify(_.trim)
8
// JsValue => JsValue
9
 
10
val isNotEmpty = toJsStr.exist(_ != "")
11
// JsValue => JsValue
12
 
13
// prism and map/filter are good friends:
14
 
15
obj map toLowerCase
16
 
17
obj map trim
18
 
19
obj filter isNotEmpty 



This is just a quick introduction to json-values. Go to the project page for further details. At this moment, I'm migrating the library to Scala 3 and adding some more documentation.

Wrapping up:

  • json-values implements an immutable Json with persistent data structures.
  • Specs and generators are composable data structures that are defined just as Jsons.
  • json-values is simple, and as Rich Hickey said simplicity matters.
  • Scala is a great language that makes it possible for developers to write expressive and beautiful code. 

Further Reading

Topics:
scala ,functional programming ,json ,library ,immutability ,persistent data

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}