1

I want to remove deep empty json array from my json before/during processing by circe.

Incoming JSON

{
    "config": {
        "newFiles": [{
            "type": "audio",
            "value": "welcome1.mp3"
        }],
        "oldFiles": [],
        "channel": "BC"
    }
}

or 

{
    "config": {
        "newFiles": [],
        "oldFiles": [{
            "type": "audio",
            "value": "welcome1.mp3"
        }],
        "channel": "BC"
    }
}

Resulted Json should look like

{
    "config": {
        "newFiles": [{
            "type": "audio",
            "value": "welcome1.mp3"
        }],
        "channel": "BC"
    }
}

or 

{
    "config": {
        "oldFiles": [{
            "type": "audio",
            "value": "welcome1.mp3"
        }],
        "channel": "BC"
    }
}

What i understand that this can be done before decoding config as well as during decoding config. The idea here is i want to handle only one of files (either new or old) at my case class level.

Method 1: Tried at config decoding level which works well.

case class File(`type`: String, value: String)

case class Config(files: List[File],
                  channel: String = "BC")

object Config{
  implicit final val FileDecoder: Decoder[File] = deriveDecoder[File]
  implicit val ConfigDecoder: Decoder[Config] = (h:HCursor) =>
    for {
      oldFiles <- h.get[List[File]]("oldFiles")
      files <- if (oldFiles.isEmpty) h.get[List[File]]("newFiles") else h.get[List[File]]("oldFiles")
      channel <- h.downField("channel").as[String]
    }yield{
      Config(files, channel)
    }
}

case class Inventory(config: Config)

object Inventory {
  implicit val InventoryDecoder: Decoder[Inventory] = deriveDecoder[Inventory]
}

Method 2: Tried before feeding into decoding which didn't worked out

Let me know what could be the elegant approach to handle it. PS: I have multiple similar config decoders and if i handle this at config decoding level then there will be a lot of boiler plate code.

1 Answer 1

1

I have simplified the problem a little bit again, but combining this with the previous answer should be simple.

I also took advantage of cats.data.NonEmptyList

final case class Config(files: NonEmptyList[String], channel: String = "BC")
object Config {
  implicit final val ConfigDecoder: Decoder[Config] =
    (
      (Decoder[NonEmptyList[String]].at(field = "newFiles") or Decoder[NonEmptyList[String]].at(field = "oldFiles")),
      Decoder[String].at(field = "channel")
    ).mapN(Config.apply).at(field = "config")
}

This can be used like this:

val data =
"""[
{
"config": {
  "newFiles": ["Foo", "Bar", "Baz"],
  "oldFiles": [],
  "channel": "BC"
}
},
{
"config": {
  "newFiles": [],
  "oldFiles": ["Quax"],
  "channel": "BC"
}
}
]"""

parser.decode[List[Config]](data)
// res: Either[io.circe.Error, List[Config]] =
//   Right(List(
//     Config(NonEmptyList("Foo", "Bar", "Baz"), "BC"),
//     Config(NonEmptyList("Quax"), "BC")
//   ))

Note: I am assuming that at least one of the two lists must be non-empty and give priority to the new one.


You can see the code running here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.