Scala play json - обновить все значения одним и тем же ключом - PullRequest
1 голос
/ 28 марта 2020

Допустим, у меня есть JsValue в форме:

{
  "businessDetails" : {
    "name" : "Business",
    "phoneNumber" : "+44 0808 157 0192"
  },
  "employees" : [
    {
      "name" : "Employee 1",
      "phoneNumber" : "07700 900 982"
    },
    {
      "name" : "Employee 2",
      "phoneNumber" : "+44(0)151 999 2458"
    }
  ]
}

Мне было интересно, есть ли способ обновить все значения, принадлежащие ключу с определенным именем внутри JsValue независимо от его сложности? В идеале я хотел бы сопоставить каждый номер телефона, чтобы убедиться, что (0) удаляется, если он есть. Я сталкивался с play- json -zipper updateAll, но у меня возникают нерешенные проблемы с зависимостями при добавлении библиотеки в мой sbt проект. Любая помощь, либо добавление библиотеки play-json-zipper, либо ее реализация в обычном play-json, будет принята с благодарностью. Спасибо!

1 Ответ

1 голос
/ 28 марта 2020

Из того, что я вижу на странице проекта play-json-zipper, вы можете забыть добавить преобразователь resolvers += "mandubian maven bintray" at "http://dl.bintray.com/mandubian/maven"

Если это не поможет, и вы хотели бы приступить к пользовательской реализации: play-json не предоставляет API сворачивания или обхода через JsValue из коробки, поэтому он может быть рекурсивно реализован следующим образом:

/**
 * JSON path from the root. Int - index in array, String - field
 */
type JsPath = Seq[Either[Int,String]]
type JsEntry = (JsPath, JsValue)
type JsTraverse = PartialFunction[JsEntry, JsValue]

implicit class JsPathOps(underlying: JsPath) {
  def isEndsWith(field: String): Boolean = underlying.lastOption.contains(Right(field))
  def isEndsWith(index: Int): Boolean = underlying.lastOption.contains(Left(index))
  def /(field: String): JsPath = underlying :+ Right(field)
  def /(index: Int): JsPath = underlying :+ Left(index)
}

implicit class JsValueOps(underlying: JsValue) {
  /**
   * Traverse underlying json based on given partially defined function `f` only on scalar values, like:
   * null, string or number.
   *
   * @param f function
   * @return updated json
   */
  def traverse(f: JsTraverse): JsValue = {
    def traverseRec(prefix: JsPath, value: JsValue): JsValue = {
      val lifted: JsValue => JsValue = value => f.lift(prefix -> value).getOrElse(value)
      value match {
        case JsNull => lifted(JsNull)
        case boolean: JsBoolean => lifted(boolean)
        case number: JsNumber => lifted(number)
        case string: JsString => lifted(string)
        case array: JsArray =>
          val updatedArray = array.value.zipWithIndex.map {
            case (arrayValue, index) => traverseRec(prefix / index, arrayValue)
          }
          JsArray(updatedArray)

        case `object`: JsObject =>
          val updatedFields = `object`.fieldSet.toSeq.map {
            case (field, fieldValue) => field -> traverseRec(prefix / field, fieldValue)
          }
          JsObject(updatedFields)
      }
    }
    traverseRec(Nil, underlying)
  }
}

, который можно использовать следующим образом:

val json =
  s"""
     |{
     |  "businessDetails" : {
     |    "name" : "Business",
     |    "phoneNumber" : "+44(0) 0808 157 0192"
     |  },
     |  "employees" : [
     |    {
     |      "name" : "Employee 1",
     |      "phoneNumber" : "07700 900 982"
     |    },
     |    {
     |      "name" : "Employee 2",
     |      "phoneNumber" : "+44(0)151 999 2458"
     |    }
     |  ]
     |}
     |""".stripMargin

val updated = Json.parse(json).traverse {
  case (path, JsString(phone)) if path.isEndsWith("phoneNumber") => JsString(phone.replace("(0)", ""))
}

println(Json.prettyPrint(updated))

, который даст желаемый результат:

{
  "businessDetails" : {
    "name" : "Business",
    "phoneNumber" : "+44 0808 157 0192"
  },
  "employees" : [ {
    "name" : "Employee 1",
    "phoneNumber" : "07700 900 982"
  }, {
    "name" : "Employee 2",
    "phoneNumber" : "+44151 999 2458"
  } ]
}

Надеюсь, это поможет!

...