Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Manipulate JSON AST in Scala

I am experimenting with the json4s library (based on lift-json). One of the things I would like to do is to parse a JSON string into an AST, and then manipulate it.

For example, I would like to upsert a field (insert the field into the AST if it does not exist, or update its value if it does).

I have not been able to find how to do it in the documentation. Experimenting with the available methods, I have come up with the following, which works, but feels clumsy.

import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.native.JsonMethods._

object TestJson {
  implicit val formats = DefaultFormats

  def main(args: Array[String]): Unit = {
    val json = """{"foo":1, "bar":{"foo":2}}"""
    val ast = parse(json).asInstanceOf[JObject]

    println( upsertField(ast, ("foo" -> "3")) )
    println( upsertField(ast, ("foobar" -> "3")) )
  }

  def upsertField(src:JObject, fld:JField): JValue = {
    if(src \ fld._1 == JNothing){
      src ~ fld
    }
    else{
      src.replace(List(fld._1), fld._2)
    }
  }
}

I dislike it for many reasons:

  1. Having to explicitly cast the results of parse(json) to JObject
  2. The result of the upsertField function is a JValue, which I will have to recast if I want to manipulate the object further
  3. The upsertField function just feels very unelegant
  4. It does not work for fields that are not at the top level of the hierarchy

Is there a better way to transform the AST?

EDIT: as a workaround to the problem, I have managed to convert my JSON to Scala regular classes, and manipulate them with lenses (Using Lenses on Scala Regular Classes)

like image 799
Eduardo Avatar asked Jun 22 '13 19:06

Eduardo


1 Answers

There is the merge function which creates or overrides a field. You can also update fields that are not at the root level of the tree.

import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._

object mergeJson extends App {

  val json =
    """
      |{
      |  "foo":1,
      |  "bar": {
      |    "foo": 2
      |  }
      |}
      |""".stripMargin

  val ast = parse(json)

  val updated = ast merge (("foo", 3) ~ ("bar", ("fnord", 5)))

  println(pretty(updated))

  //  {
  //    "foo" : 3,
  //    "bar" : {
  //      "foo" : 2,
  //      "fnord" : 5
  //    }
  //  }

}
like image 197
Stefan Ollinger Avatar answered Oct 19 '22 06:10

Stefan Ollinger