【问题标题】:Json4S creating new object with updated valueJson4S 创建具有更新值的新对象
【发布时间】:2018-08-24 21:38:09
【问题描述】:

我正在尝试更新 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 做到这一点的建议?

谢谢

【问题讨论】:

    标签: scala json4s


    【解决方案1】:

    与 Scala 中的大多数库一样,json4s 具有不变性。无法更改 JValue 中的值,但您可以创建一个带有更改的新值(如案例类副本)。

    如果您想更改每个“参考”字段,您可以使用带有模式匹配的 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)))))))))))))))
    

    编辑

    我修改了替换方法以增加对数组的支持,现在您可以通过“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"))
    

    【讨论】:

    • 谢谢,我了解不可变性,并且我需要创建一个新对象,我已经编辑了标题。我不需要更改对象中的所有 ref 字段,只需要更改 json.\("foo")(0).\("bar").\("baz") 中的特定字段。在我的实际 json 中实际上有很多 ref 对象。
    • 我更改了我的代码。现在我将“ref”mapField 应用于 foo 数组的第一个元素。我希望我可以使用替换,但我一直在寻找它的实现,它不包含计数数组。也许我可以改变它,我会在一段时间内尝试。
    • 给你一个改进的替换方法,可以根据需要工作
    • 我用这个功能创建了一个拉取请求github.com/json4s/json4s/pull/540
    猜你喜欢
    • 1970-01-01
    • 2011-08-19
    • 1970-01-01
    • 2019-10-21
    • 2021-09-05
    • 2011-01-23
    • 1970-01-01
    • 2018-04-13
    • 1970-01-01
    相关资源
    最近更新 更多