3

Having this example,

import io.circe.generic.auto._
import io.circe.shapes._
import io.circe.parser._
import io.circe.syntax._
import shapeless._

case class A[T <: HList](name: String, params: T)

when I instance this case class with a non-empty HList, there is no problem:

scala> A("name", "a" :: HNil).asJson
res1: io.circe.Json =
{
  "name" : "name",
  "params" : [
    "a"
  ]
}

But, when this HList is only HNil, I get the follow error:

scala> A("name", HNil).asJson
<console>:29: error: could not find implicit value for parameter encoder: io.circe.Encoder[A[shapeless.HNil.type]]
   A("name", HNil).asJson
                   ^

I've read this question where they talk about case object encoder, but it doesn't work with HNil (HNil is case object) and I've seen anything about it in the documentation. FYI, I'm using circe 0.6.1

Any idea?

Community
  • 1
  • 1

2 Answers2

4

This is due to the fact that the static type of HNil is more specific than HNil (i.e. it's HNil.type). The following will work as expected:

scala> A("name", HNil: HNil).asJson.noSpaces
res0: String = {"name":"name","params":{}}

We could provide Encoder and Decoder instances for HNil.type, but in general when you construct a type with HNil and :: the static type of the HNil is HNil, and to keep things simple we decide to provide instances only for HNil.

This issue comes up in other places, including in Shapeless itself, where a search for HNil: HNil returns thirty-something occurrences.

Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • Here's an example of what the might look like: https://github.com/eed3si9n/sjson-new/blob/899686e4a3778dfbf16b745befc31a84d6c53fb3/support/scalajson/src/test/scala/sjsonnew/support/scalajson/unsafe/HList.scala#L29-L42 – Dale Wijnand Jan 12 '17 at 16:28
2

Travis' answer is valid, you can certainly encode HNil by using a type annotation. I do not have enough reputation so I can't comment his answer, but I noticed that non-empty HLists encode as JSON arrays, while an empty Hlist is encoded as an empty JSON object.

Here's a quick code sample of this behavior.

@ (1 :: 2 :: "foo" :: HNil).asJson.noSpaces 
res1: String = """[1,2,"foo"]"""
@ (HNil: HNil).asJson.noSpaces 
res12: String = "{}"
willena
  • 63
  • 7
  • Yeah, this is a kind of unfortunate inconsistency. Hlists can be encoded as either arrays or objects depending on whether they're labeled or not, but since an `HNil` is neither labeled nor unlabeled we have to choose one, and since `Unit` is encoded as `{}` (among other reasons), we go with the empty object. – Travis Brown Jan 12 '17 at 16:53