Json4S создает новый объект с обновленным значением - PullRequest
0 голосов
/ 25 августа 2018

Я пытаюсь обновить определенное поле в объекте json, структура выглядит следующим образом:

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

Я анализирую этот объект с помощью Json4S, используя Джексона, и хотел бы создать новый jsonобъект со значением этого конкретного поля "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' был заменен на преобразованный объект 'ref' в общем объекте json.Я также должен отметить, что в моем реальном json-объекте есть много объектов 'ref', мне нужно только обновить конкретный объект, расположенный по адресу json.\("foo")(0).\("bar").\("baz").("ref")

Я пробовал использовать функции replace и flatMap длясделать это, но не могу заставить его работать должным образом.Может кто-нибудь дать совет, как это сделать с Json4S?

Спасибо

1 Ответ

0 голосов
/ 25 августа 2018

Как и большинство библиотек в Scala, json4s работает с неизменяемостью. Невозможно изменить значение внутри JValue, но вы можете создать новое с изменениями (например, копирование классов дел).

Если вы хотите изменить каждое поле «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)))))))))))))))

EDIT

Я изменил метод замены, чтобы добавить поддержку массивов, теперь вы можете изменять все элементы в массиве через "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"))
...