Search code examples
jsonscalacirce

Json.asString returns None even though Json.toString returns the correct value


Given the following case class LogMessage:

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import enumeratum.{CirceEnum, Enum, EnumEntry}
import io.circe.syntax._

sealed trait LogLevel extends EnumEntry

object LogLevel extends Enum[LogLevel] with CirceEnum[LogLevel] {
  val values = findValues

  case object Warning extends LogLevel
  case object Error   extends LogLevel
  case object Info    extends LogLevel
  case object Success extends LogLevel
}

object LogMessage {
  implicit val logMessageDecoder: Decoder[LogMessage] = deriveDecoder[LogMessage]
  implicit val logMessageEncoder: Encoder[LogMessage] = deriveEncoder[LogMessage]
}

case class LogMessage(level: LogLevel, text: String, args: List[String], date: Long)
case class MyClass[A](obj: A)(implicit encoder: Encoder[A]) {
    def message1: String = obj.asJson.toString
    def message2: Option[String] = obj.asJson.asString
}

Why does this work:

val x = MyClass(LogMessage(LogLevel.Info, "test notification", Nil, 1550218866571))

x.message1 // {\n  "level" : "Info",\n  "text" : "test notification",\n  "args" : [\n  ],\n  "date" : 1550218866571\n}

But this does not:

x.message2 // None

Here is a link to Scastie with this problem: link.


Solution

  • In circe Json has six asX methods that correspond to the six data types in JSON. For example, if a Json instance x represents a JSON boolean, x.asBoolean will return a Some containing the value as a Boolean, but if x is a JSON string, array, object, number, or null, x.asBoolean will be empty.

    You're seeing .asString return None in this case because you're calling it on a Json value that represents a JSON object, not a JSON string.

    The toString method on Json is completely different: it's the universal Scala / Java toString, which in the case of Json is implemented as .spaces2. I'm not sure what you're trying to do here, but in general I'd recommend avoiding toString—if you want to serialize an io.circe.Json value, it's better to use a printer or the printing methods that make the formatting options more explicit (e.g. noSpaces, spaces2, etc.).

    (For what it's worth, I'm not entirely happy with the naming of asString, asNull, etc. methods on Json. In general in circe "as" is used in method names for encoding or decoding, which isn't exactly what's happening in these cases, but it's close enough that I've never bothered to come up with a better alternative.)