Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transform Json with circe

Tags:

json

scala

circe

Assuming the following json payload

val json = """{
  "choices" : [
    {
      "name" : "A"
    },
    {
      "name" : "B"
    },
    {
      "name" : "C"
    },
    {
      "name" : "D"
    }
  ],
  "domain" : "Quizz",
  "level" : "Test",
  "mandatory": true
}"""

How do I transform it to

val json = """{
  "value":"B",
  "domain" : "Quizz",
  "level" : "Test",
}"""

where the "B" is randomly selected from the available choices ?

This is what I have got so far :

val cursor = parse(json).getOrElse(Json.Null).cursor
for{
  noMandatory<- cursor.downField("mandatory").flatMap(_.delete).map(_.top)
  withEmptyValue = noMandatory.deepMerge(Json.obj("value"->Json.Null))
 }yield withEmptyValue

This drops the unused "mandatory" field and inserts an empty "value" field. Getting a random value from the array and placing it in "value" escapes me though.

-- edit

I have tried using hcursor which makes the above clearer (to me anyway)

val cursor = parse(json).getOrElse(Json.Null).hcursor

val noMandatory = cursor.downField("mandatory").delete
val withEmptyValue = noMandatory.withFocus(_.deepMerge(Json.obj("value"->Json.Null)))

(I am using circe 0.5.1 in the above examples)

like image 883
Jean Avatar asked Sep 26 '16 12:09

Jean


1 Answers

Do not mix side effects with pure code. So I didn't solve your randomisation problem.

Second, I'd recommend not removing the 'choices' field to keep things simple. Remove that field later when you feel like it.

Third, separate the getting and transformation stages for simplicity. Here you can use a case class to do most of the work.

I'm sure this might not be the idiomatic Circe solution but it's good Scala:

import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._

object ChooserApp extends App {
  val input =
    """{
  "choices" : [
    {
      "name" : "A"
    },
    {
      "name" : "B"
    },
    {
      "name" : "C"
    },
    {
      "name" : "D"
    }
  ],
  "domain" : "Quizz",
  "level" : "Test",
  "mandatory": true
}"""

  val expected =
    """{
      "choices" : [
          {
            "name" : "A"
          },
          {
            "name" : "B"
          },
          {
            "name" : "C"
          },
          {
            "name" : "D"
          }
        ],
  "value":"B",
  "domain" : "Quizz",
  "level" : "Test",
  "mandatory": true
}"""

  case class ForJson(j: Json) {
    def choices: List[String] = {
      j.asObject
        .toList
        .flatMap(_ ("choices"))
        .flatMap(_.asArray)
        .flatten
        .flatMap(_.asObject)
        .flatMap(_ ("name"))
        .flatMap(_.asString)
    }

    def chosen(a: String): Json = {
      j.asObject
        .map(_.add("value", Json.fromString(a)))
        .map(Json.fromJsonObject)
        .getOrElse(j)
    }
  }

  val expectedList = List("A", "B", "C", "D")
  val gotList = ForJson(parse(input).toOption.get).choices
  assert(gotList == expectedList, s"Expected $expectedList, got $gotList")

  val expectedJson = parse(expected).toOption.get
  val gotJson = ForJson(parse(input).toOption.get).chosen("B")
  assert(gotJson == expectedJson, s"Expected $expectedJson, got $gotJson")
}
like image 89
ScalaWilliam Avatar answered Nov 09 '22 09:11

ScalaWilliam