2

I have a JSON that can change through time and using case Class might be unconvenient because I need to change the structure of it everytime the JSON change.

for example, if I have a JSON like this:

val json= """{
  "accounts": [
  { "emailAccount": {
    "accountName": "YMail",
    "username": "USERNAME",
    "password": "PASSWORD",
    "url": "imap.yahoo.com",
    "minutesBetweenChecks": 1,
    "usersOfInterest": ["barney", "betty", "wilma"]
  }},
  { "emailAccount": {
    "accountName": "Gmail",
    "username": "USER",
    "password": "PASS",
    "url": "imap.gmail.com",
    "minutesBetweenChecks": 1,
    "usersOfInterest": ["pebbles", "bam-bam"]
  }}
  ]
}"""

can I access to it with something like:

val parsedJSON = parse(json)
parsedJSON.accounts(0).emailAccount.accountName
salvob
  • 1,300
  • 3
  • 21
  • 41

1 Answers1

6

circe's optics module supports almost exactly the syntax you're asking for:

import io.circe.optics.JsonPath.root

val accountName = root.accounts.at(0).emailAccount.accountName.as[String]

And then if you've got this JSON value (I'm using circe's JSON literal support, but you could also parse a string with io.circe.jawn.parse (parse function) to get the Json value you're working with):

import io.circe.Json, io.circe.literal._

val json: Json = json"""{
  "accounts": [
  { "emailAccount": {
    "accountName": "YMail",
    "username": "USERNAME",
    "password": "PASSWORD",
    "url": "imap.yahoo.com",
    "minutesBetweenChecks": 1,
    "usersOfInterest": ["barney", "betty", "wilma"]
  }},
  { "emailAccount": {
    "accountName": "Gmail",
    "username": "USER",
    "password": "PASS",
    "url": "imap.gmail.com",
    "minutesBetweenChecks": 1,
    "usersOfInterest": ["pebbles", "bam-bam"]
  }}
  ]
}"""

You can do try to access the account name like this:

scala> accountName.getOption(json)
res0: Option[String] = Some(YMail)

Because circe-optics is built on Monocle, you get some other nice functionality, like immutable updates:

scala> accountName.modify(_.toLowerCase)(json)
res2: io.circe.Json =
{
  "accounts" : [
    {
      "emailAccount" : {
        "accountName" : "ymail",
        ...

And so on.


Update: circe is designed to be modular, so that you only "pay for" the pieces you need. The examples above expect something like the following setup for SBT:

scalaVersion := "2.11.8"

val circeVersion = "0.4.1"

libraryDependencies ++= Seq(
  "io.circe" %% "circe-core" % circeVersion,
  "io.circe" %% "circe-jawn" % circeVersion,
  "io.circe" %% "circe-literal" % circeVersion,
  "io.circe" %% "circe-optics" % circeVersion
)

…or for Maven:

<dependency>
  <groupId>io.circe</groupId>
  <artifactId>circe-core_2.11</artifactId>
  <version>0.4.1</version>
</dependency>
<dependency>
  <groupId>io.circe</groupId>
  <artifactId>circe-jawn_2.11</artifactId>
  <version>0.4.1</version>
</dependency>
<dependency>
  <groupId>io.circe</groupId>
  <artifactId>circe-literal_2.11</artifactId>
  <version>0.4.1</version>
</dependency>
<dependency>
  <groupId>io.circe</groupId>
  <artifactId>circe-optics_2.11</artifactId>
  <version>0.4.1</version>
</dependency>
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Thanks for your reply. But although your solution looks promising, it doesn't work. `error: not found: value optics` I guess it is not ready yet? Then your explanation gets a big confusing... Especially the first two rows of code. How can you access to the elements of the JSON? thanks – salvob Apr 19 '16 at 22:27
  • @salvob See my update about the dependencies. Not sure what you mean by "access to the elements"? You want to work with the JSON AST directly? – Travis Brown Apr 20 '16 at 00:36
  • now I see what you meant and it seems "magic". I found it weird that I have to define the "path" of the AST before importing the real json. this -> `val accountName = root.accounts.at(0).emailAccount.accountName.as[String]` has to be defined and then get the field with `getOption` But it looks doable. Thank you! – salvob Apr 20 '16 at 09:08
  • @salvob Right—lenses make paths first-class objects in the same way that functional programming makes functions first class. – Travis Brown Apr 20 '16 at 09:22
  • I added maven dependencies in case someone else need, as well as link to JawnParser, for a full understanding of how the jawn module works. Hope this helps – salvob Apr 20 '16 at 09:31
  • @salvo Thanks! Just merged the edits with a few small changes (e.g. from 0.4.0-RC2 to 0.4.1). – Travis Brown Apr 20 '16 at 09:36