0

I'm in the process of choosing a good Scala JSON library, and the consensus seems to be that lift-json is currently the best choice.

After playing with it (version 2.5.1) for a spell, I've been able to do most of the things I needed fairly easily, except for one: modifying an existing JValue.

Say that I have the following instance of JValue:

val john = ("id"   -> 1) ~
           ("name" -> "Foo") ~
           ("nested" ->
             ("id" -> 2) ~
             ("name" -> "Bar"))

I'd like to change the parent element's name from Foo to foo. I thought the transform method was the way to go:

john transform {
    case JField("name", _) => JField("name", "foo")
})

But this changes both the parent and nested element's name fields to foo - which, in retrospect, really should not have been a surprise.

I've looked at the documentation and code and could not find a way to choose a specific field with a key of name. Did I miss something?

The other solution (and this one works) seems to be merging two JValue objects, but it seems a bit verbose:

john merge JObject(JField("name", "foo") :: Nil)

Is there a built-in, more readable way of achieving the same result? I probably could write an implicit conversion from JField to JObject, but it seems odd that lift-json doesn't already have such a mechanism. If I had to bet, it'd be on my not having found it rather than on it not existing.

EDIT: I feel a bit silly now

john transform {
    case JField("name", "Foo") => JField("name", "foo")
})

Not the most optimal solution in the world, but perfectly readable and concise.

Nicolas Rinaudo
  • 6,068
  • 28
  • 41

2 Answers2

0

This is ugly as it uses a mutable variable and it also uses json4s (which uses lift-json under the hood), but it works:

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

object JsonTesting {
  def main(args: Array[String]) {
    val john = ("id"   -> 1) ~
           ("name" -> "Foo") ~
           ("nested" ->
             ("id" -> 2) ~
             ("name" -> "Bar"))

    var changed = false
    val john2 = john transformField {
      case JField("name", _) if !changed => 
        changed = true
        JField("name", "foo")
    }
  }
}

I could not find a more clean way to tell it to either only traverse one level of depth or to be aware of depth for each node checked so I would only make the switch on the first level of depth.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
0

Use the replace method of JValue like this:

john.replace(List("name"), "foo")

Implementation of this 'replace' method is a disaster and the name replace suggests (to me) a state modification which isn't true but it does the thing

JObject(List((id,JInt(1)), (name,JString(foo)), (nested,JObject(List((id,JInt(2)), (name,JString(Bar)))))))

mjaskowski
  • 1,479
  • 1
  • 12
  • 16