Search code examples
scalashapelessscodec

define `Codec` for recursive data structure


I have a class looking like this,

case class Foo ( bar: Int, foos: Vector[Foo] )

to define a Codec[Foo], I tried this,

def fc = shapeless.Lazy((int32 ~~ vector(fc)).widenOpt( (Foo.apply _).tupled, Foo.unapply _ ))

But this did not work, since scodec throws StackOverflowError. What is the right way of doing this ?


Solution

  • You'll need the scodec.codecs.lazily combinator to build recursive codecs (not shapeless.lazily). For example:

    scala> def fc: Codec[Foo] = lazily((int32 :: vector(fc)).as[Foo])
    fc: scodec.Codec[Foo]
    
    scala> val res = fc.encode(Foo(1, Vector(Foo(2, Vector.empty)))).require
    res: scodec.bits.BitVector = BitVector(64 bits, 0x0000000100000002)
    
    scala> fc.decode(res)
    res2: scodec.Attempt[scodec.DecodeResult[Foo]] = Successful(DecodeResult(Foo(1,Vector(Foo(2,Vector()))),BitVector(empty)))
    

    In scodec 1.8.2 and prior, deriving this codec, instead of defining it explicitly, results in an error at compile time, due to the derivation continuing recursively forever. As of 1.8.3, this codec can be automatically derived without issue.

    For an example of a recursive tree, see this example from scodec source.