'Deep remove specific empty json array in circe scala

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.



Solution 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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Luis Miguel Mejía Suárez