Scodec List Decoder example

I am using the scodec library as part of an application I am writing.

I had an issue when encoding a class of the form

case class Foo(list1: List[Foo], list2: List[Foo])

.
The initial implementation did not work because, when the list is decoded, the contents of list2 get subsumed into list1.

This is the initial non-working example:

package scodec.helloworld.example_a

import scodec._
import scodec.codecs._

import scala.language.implicitConversions

case class Bar(value: Int)

case class Foo(list1: List[Bar], list2: List[Bar])

object Foo {

  def main(args: Array[String]) : Unit = {

    implicit val barCodec : Codec[Bar] = {
      ("value" | int32).hlist
    }.as[Bar]

    implicit val fooCodec : Codec[Foo] = {
      (("list1" | list(Codec[Bar])
      :: ("list2" | list(Codec[Bar]) ) ))
    }.as[Foo]

    val bar1 = Bar(1)
    val bar2 = Bar(2)
    val bar3 = Bar(3)
    val bar4 = Bar(4)

    val aFoo = Foo(Seq(bar1, bar2).toList, Seq(bar3, bar4).toList)
    println("aFoo:       " + aFoo)

    val encodedFoo = fooCodec.encode(aFoo).require
    println("encodedFoo: " + encodedFoo)

    val decodedFoo = fooCodec.decode(encodedFoo).require.value
    println("decodedFoo: " + decodedFoo)

    assert(decodedFoo == aFoo)
  }

}

which runs as follows:

aFoo:       Foo(List(Bar(1), Bar(2)),List(Bar(3), Bar(4)))
encodedFoo: BitVector(128 bits, 0x00000001000000020000000300000004)
Exception in thread "main" java.lang.AssertionError: assertion failed
decodedFoo: Foo(List(Bar(1), Bar(2), Bar(3), Bar(4)),List())
	at scala.Predef$.assert(Predef.scala:204)
	at scodec.helloworld.example_a.Foo$.main(FooFails.scala:39)
	at scodec.helloworld.example_a.Foo.main(FooFails.scala)

I implemented a List[Bar] codec, which adds an indicator to each entry in the list stating whether it is the last entry. (This is the specification of format I’m encoding / decoding from, otherwise I would have used a ‘list length’ prefix).

The working implementation is:

package scodec.helloworld.example_b

import scodec.Attempt.{Failure, Successful}
import scodec._
import scodec.bits.BitVector
import scodec.codecs._

import scala.annotation.tailrec
import scala.language.implicitConversions

case class Bar(value: Int)

case class Foo(list1: List[Bar], list2: List[Bar])

object Foo {

  def main(args: Array[String]) : Unit = {

    implicit val barCodec : Codec[Bar] = {
      ("value" | int32).hlist
    }.as[Bar]

    implicit val barListCodec : Codec[List[Bar]] = new Codec[List[Bar]] {

      case class IsLast[A](isLast: Boolean, thing: A)

      implicit val lastBarCodec : Codec[IsLast[Bar]] = {
        ("isLast" | bool) :: ("bar" | Codec[Bar])
      }.as[IsLast[Bar]]

      override def sizeBound: SizeBound = SizeBound.unknown

      override def encode(bars: List[Bar]): Attempt[BitVector] = {
        if (bars.size == 0) {
          Failure(Err("Cannot encode zero length list"))
        } else {
          val zippedBars = bars.zipWithIndex
          val lastBars = zippedBars.map(bi => IsLast(bi._2 + 1 == bars.size, bi._1))
          Codec.encodeSeq(lastBarCodec)(lastBars)
        }
      }

      override def decode(b: BitVector): Attempt[DecodeResult[List[Bar]]] = {
        val lastBars = decode(b, List.empty)
        val bars = lastBars.map(dr => dr.value.thing)
        Successful(DecodeResult(bars, lastBars.last.remainder))
      }

      @tailrec
      private def decode(b: BitVector, lastBars: List[DecodeResult[IsLast[Bar]]]) : List[DecodeResult[IsLast[Bar]]] = {
        val lastBar = lastBarCodec.decode(b).require
        val lastBarsInterim = lastBars :+ lastBar
        if (lastBar.value.isLast) lastBarsInterim
        else decode(lastBar.remainder, lastBarsInterim)
      }
    }

    implicit val fooCodec : Codec[Foo] = {
      (("list1" | Codec[List[Bar]])
      :: ("list2" | Codec[List[Bar]]))
    }.as[Foo]

    val bar1 = Bar(1)
    val bar2 = Bar(2)
    val bar3 = Bar(3)
    val bar4 = Bar(4)

    val aFoo = Foo(Seq(bar1, bar2).toList, Seq(bar3, bar4).toList)
    println("aFoo:       " + aFoo)

    val encodedFoo = fooCodec.encode(aFoo).require
    println("encodedFoo: " + encodedFoo)

    val decodedFoo = fooCodec.decode(encodedFoo).require.value
    println("decodedFoo: " + decodedFoo)

    assert(decodedFoo == aFoo)
  }

}

which runs as follows:

aFoo:       Foo(List(Bar(1), Bar(2)),List(Bar(3), Bar(4)))
encodedFoo: BitVector(132 bits, 0x00000000c000000080000000700000004)
decodedFoo: Foo(List(Bar(1), Bar(2)),List(Bar(3), Bar(4)))