Search code examples
javajsonscaladecoding

Scala: decode Any[] array using circe.io


I have a Java server creating a message looking like this:

@SerializedName("message")
private String _message;

@SerializedName("args")
private Object[] _args;

Now in my Scala.js application, I want to deserialize this message using something like:

case class Notification(message: String, args: String*)

implicit val messageDecoder: Decoder[Notification] = (c: HCursor) => {
  for {
    message  <- c.downField("message").as[String]
    args     <- c.downField("args").as[List[java.lang.Object]].map(_.toString)
  } yield {
    Notification(level, message, args)
  }
}

However, Scala refuses to decode this with the error:

implicit error;
[error] !I d: Decoder[List[Object]]
[error] Decoder.importedDecoder invalid because
[error] !I exported: Exported[Decoder[List[Object]]]
[error] Decoder.decodeCanBuildFrom invalid because
[error] !I d: Decoder[Object]
[error] ??Decoder.importedDecoder invalid because
[error]   !I exported: Exported[Decoder[Object]]
[error]
[error] Decoder.decodeList invalid because
[error] !I evidence$2: Decoder[Object]
[error] ??Decoder.importedDecoder invalid because
[error]   !I exported: Exported[Decoder[Object]]
[error]       args       <- 
        c.downField("args").as[List[Object]].map(_.toString)
[error]                                           ^
[error] one error found

Any ideas on how to decode this? I only need to call map(toString) on the result.

Edit

When trying to do something like this:

args <- c.downField("args").as[Array[Any]].map(_.toString)

I get the following error:

diverging implicit expansion for type io.circe.Decoder[A]
[error] starting with value decodeString in object Decoder
[error]       args       <- 
     c.downField("args").as[Array[Any]].map(_.toString)
[error]                                           ^
[error] one error found

Edit 2

args <- c.downField("args").as[Seq[String]].map(_.toString)

Does compile, but does not succefully parse the json (Left).

Edit 3

One example of the json that is sent (in this case with integers):

{
  "message" : "{0} is smaller than {1}.",
  "args" : [
    1,
    2
  ]
}

The java server could also generate a JSON looking like this:

{
  "message" : "{0} is smaller than {1}. ({2})",
  "args" : [
    1,
    2,
    "Hello World!"
  ]
}

Solution

  • Adding it as another answer, so as to keep the history clean.

    Basically the following code simply works with the current json value and uses it string representation.

    case class Notification(message: String, args: String*)
    
    object Notification {
    
      implicit val anyDecoder : Decoder[Any] =  Decoder.instance(c => {
          c.focus match {
              case Some(x) => Right(x)
              case None => Left(DecodingFailure("Could not parse", List()))
          }
      })
    
      implicit val messageDecoder: Decoder[Notification] = Decoder.instance(c => {
        for {
          message <- c.downField("message").as[String]
          args <- c.downField("args").as[List[Any]].map(_.toString)
        } yield {
          Notification(message, args)
        }
      })
    
    }
    

    And the test case is

    val json = """
                {
                    "message" : "Hello" ,
                    "args"    : [ 2, 3.234, 4,"good", true  ]
                }
                """
    
    println(decode[Notification](json))