Play JSON transformer for renaming (pruning) field in map

I am struggeling now for about 3 days to transform the following JSON:

{
	"invoiceState": {
		"userId": "832eac3d-89d2-428c-9007-688b3e590533",
		"invoices": {
			"407613d7-7dc0-4cd6-8d62-41eea7067ca0": {
				"id": "407613d7-7dc0-4cd6-8d62-41eea7067ca4",
				"externalId": "I-000000-000000",
				"eventId": "013bdbbe-6749-41e8-8175-07f7e0e2089c",
				"date": "1970-01-01T01:00+01:00[Europe/Vienna]",
			},
			"8d16b2f8-ced0-4b41-8b6b-b275709ba88a": {
				"id": "407613d7-7dc0-4cd6-8d62-41eea7067ca4",
				"externalId": "I-000000-000000",
				"eventId": "013bdbbe-6749-41e8-8175-07f7e0e2089c",
				"date": "1970-01-01T01:00+01:00[Europe/Vienna]",
			}
		}
	}
}

into this one:

{
	"invoiceState": {
		"userId": "832eac3d-89d2-428c-9007-688b3e590533",
		"invoices": {
			"407613d7-7dc0-4cd6-8d62-41eea7067ca0": {
				"id": "407613d7-7dc0-4cd6-8d62-41eea7067ca4",
				"externalId": "I-000000-000000",
				"eventId": "013bdbbe-6749-41e8-8175-07f7e0e2089c",
				"orderDate": "1970-01-01T01:00+01:00[Europe/Vienna]",
			},
			"8d16b2f8-ced0-4b41-8b6b-b275709ba88a": {
				"id": "407613d7-7dc0-4cd6-8d62-41eea7067ca4",
				"externalId": "I-000000-000000",
				"eventId": "013bdbbe-6749-41e8-8175-07f7e0e2089c",
				"orderDate": "1970-01-01T01:00+01:00[Europe/Vienna]",
			}
		}
	}
}

As you can see, the only thing I want to achieve is rename a field in a Map, in this case its renming date to orderDate.

No matter what I try, I always end up with 1 of 2 possible outcomes: Either the date is not renamed or the outermost userId field is not present in the final JSON.

The closest I have come is using code similar to this:

      (JsPath \ "invoiceState" \ "invoices").json
      .update(
        Reads
          .map(
            (JsPath \ "orderDate").json
              .copyFrom((JsPath \ "date").json.pick)
          )
          .map(JsObject(_))
      )
      .andThen(
        (JsPath \ "invoiceState" \ "invoices").json
          .pickBranch(
            Reads
              .map(
                (JsPath \ "date").json.prune
              )
              .map(JsObject(_))
          )
      )

I would really really appreciate some help.

Hi @leozilla,

As far that i know the update function returns a new Reads rigth ? Then you need do something like that

val myNewReads: Reads[MyJson] = Reads { jsValue => 
  val fieldToRenameValue = (jsValue \ "date").get
  val jsValueWithOutDate =  (jsValue \ "date").pick
  (jsvalueWithOutDate \ "orderDate").json.put(fieldToRenameValue)
}
yourJson.transform(myNewReads)

I hope this can help you.

I ended up with the following solution which works for me but I guess there should be a simpler way, or at least there should be something similar to foldKeyReads in play already:

  private val invoicesPath: JsPath = JsPath \ "invoiceState" \ "invoices"

  private[customer] val invoiceDateRenamedMigration = {
    invoicesPath.json
      .update(
        Reads
          .map(
            (JsPath \ "orderDate").json
              .copyFrom((JsPath \ "date").json.pick)
          )
          .map(JsObject(_))
      )
      .andThen(
        invoicesPath.foldKeyReads(JsPath.json.pickBranch)(keyPath => (keyPath \ "date").json.prune)
      )
  }

  implicit class RichJsPath(path: JsPath) {

    def foldKeyReads(z: Reads[JsObject])(keyReads: JsPath => Reads[JsObject]): Reads[JsObject] =
      path.read[JsObject].flatMap { obj =>
        obj.keys.foldLeft(z) {
          case (acc, key) => acc.andThen(keyReads(path \ key))
        }
      }
  }