Json4S创建具有更新值的新对象

时间:2018-08-24 21:38:10

标签: scala json4s

我正在尝试更新json对象中的特定字段,其结构如下所示:

{ "foo": [
    { 
      "bar": {
        "ref": "ref to ignore", 
        "baz": { 
          "ref": "my_old_value"
 }}}]}

我正在使用Jackson将Json4S解析为该对象,并想创建一个新的json对象,并将此特定“ ref”字段的值更改为其他值。我可以显示当前的ref对象:

[scala> json.\("foo")(0).\("bar").\("baz")
res6: JObject(List((ref,JString(my_old_value))))

我能够使用transform / transformField函数生成一个新对象,我将保留转换的详细信息:

[scala> val transformed = json.\("foo")(0).\("bar").\("baz").transform { ..
transformed: JObject(List((ref,JString(my_new_value))))

我似乎无法找出创建新对象的正确方法,该方法是将他的原始“ ref”对象替换为整个json对象中已转换的“ ref”对象。我还需要注意的是,在我实际的json对象中,有很多“ ref”对象,我只需要更新位于json.\("foo")(0).\("bar").\("baz").("ref")

的特定对象即可。

我尝试同时使用replace和flatMap函数来执行此操作,但是无法使其正常工作。谁能提供一些关于如何使用Json4S的建议?

谢谢

1 个答案:

答案 0 :(得分:1)

与Scala中的大多数库一样,json4s具有不变性。无法更改JValue中的值,但是可以使用更改创建一个新值(例如case类复制)。

如果要更改每个“ ref”字段,可以使用具有模式匹配的mapField。

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

val str = """{ "foo": [
    {
      "bar": {
        "baz": {
          "ref": "my_old_value"
 }}}]}"""

val json = parse(str)

val updated = json.mapField {
  case ("foo", JArray(head :: tail)) => ("foo", JArray(head.mapField {
    case ("ref", JString("my_old_value")) => ("ref", JString("new value"))
    case otherwise => otherwise
  } :: tail))
  case otherwise => otherwise
}

println(updated)

// JObject(List((foo,JArray(List(JObject(List((bar,JObject(List((baz,JObject(List((ref,JString(new value)))))))))))))))

编辑

我修改了replace方法以增加对数组的支持,现在您可以通过“ foo []”或特定元素“ foo [index]”来修改数组中的所有元素。在您的范围内添加此隐式类。

implicit class JValueOps(underlying:JValue) {

  object ArrayIndex {
    val R = """^([^\[]+)\[(\d+)\]""".r
    def unapply(str: String): Option[(String, Int)] = str match {
      case R(name, index) => Option(name, index.toInt)
      case _ => None
    }
  }

  object ArrayAll {
    val R = """^([^\[]+)\[\]""".r
    def unapply(str: String): Option[String] = str match {
      case R(name) => Option(name)
      case _ => None
    }
  }

  def replace2(l: List[String], replacement: JValue): JValue = {

    def rep(l: List[String], in: JValue): JValue = {

      (l, in) match {

        // eg "foo[0]"
        case (ArrayIndex(name, index) :: Nil, JObject(fields)) => JObject(
          fields.map {
            case JField(`name`, JArray(array)) if array.length > index => JField(name, JArray(array.updated(index, replacement)))
            case field => field
          }
        )

        // eg "foo[0]" "bar"
        case (ArrayIndex(name, index) :: xs, JObject(fields)) => JObject(
          fields.map {
            case JField(`name`, JArray(array)) if array.length > index => JField(name, JArray(array.updated(index, rep(xs, array(index)))))
            case field => field
          }
        )

        // eg "foo[]"
        case (ArrayAll(name) :: Nil, JObject(fields)) => JObject(
          fields.map {
            case JField(`name`, JArray(array)) => JField(name, JArray(array.map(_ => replacement)))
            case field => field
          }
        )

        // eg "foo[]" "bar"
        case (ArrayAll(name) :: xs, JObject(fields)) => JObject(
          fields.map {
            case JField(`name`, JArray(array)) => JField(name, JArray(array.map( elem => rep(xs, elem))))
            case field => field
          }
        )

        // eg "foo"
        case (x :: Nil, JObject(fields)) => JObject(
          fields.map {
            case JField(`x`, value) ⇒ JField(x, replacement)
            case field ⇒ field
          }
        )

        // eg "foo" "bar"
        case (x :: xs, JObject(fields)) => JObject(
          fields.map {
            case JField(`x`, value) ⇒ JField(x, rep(xs, value))
            case field ⇒ field
          }
        )

        case _ => in

      }

    }

    rep(l, underlying)
  }

}      

那你就可以做

json.replace2("foo[0]" :: "bar" :: "baz" :: "ref" :: Nil, JString("new value"))