struggling to print out attribute names of case classes with com-lihaoyi.github.io/PPrint/

39 Views Asked by At

I really like the promise of this library for pretty printing case class instances! But I want my output to include attribute names of my case classes, and so far i am coming up short.

Here is my most recent attempt:

import fansi.Str
 object Testo extends App {

case class Person(name: String, age: Int, address: Address)
case class Address(street: String, city: String)

val person = Person("John Doe", 30, Address("123 Main St", "New York"))
val list = List(person, person)
val map = Map("foo" -> list)
val map2 = Map("foo" -> list, "bar" -> person)

val pprint2 =
  pprint.copy(
    additionalHandlers = {
      case p: Product => {
        val stringo: Seq[String] = p.productIterator.toSeq.map {
              case (name, value) => s"$name = ${pprint.apply(value)}"
          }

        val result = p.productIterator.toSeq.map(item => "x")
        pprint.Tree.Literal(stringo.mkString(""))
      }
    }
  )

pprint2.pprintln(map2)
val result: Iterator[Str] = pprint2.tokenize(map2, showFieldNames = true)

val xx = result.map(_.toString()).mkString("")
System.out.println("xx:" + xx)

}

I get a crash like this->

Connected to the target VM, address: '127.0.0.1:63150', transport: 'socket'
Exception in thread "main" scala.MatchError: Person(John Doe,30,Address(123 Main St,New York)) (of class Person)
        at Testo$$anonfun$1.$anonfun$applyOrElse$1(Test.scala:32)
        at scala.collection.immutable.Stream.map(Stream.scala:418)
        at Testo$$anonfun$1.applyOrElse(Test.scala:32)
        at scala.PartialFunction$Lifted.apply(PartialFunction.scala:228)
        at scala.PartialFunction$Lifted.apply(PartialFunction.scala:224)
        at pprint.Walker.treeify(Walker.scala:50)
        at pprint.Walker.$anonfun$treeify$3(Walker.scala:78)
        at scala.collection.Iterator$$anon$11.nextCur(Iterator.scala:486)
        at scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:492)
        at pprint.Renderer.rec(Renderer.scala:29)
        at pprint.PPrinter.tokenize(PPrinter.scala:165)
        at pprint.PPrinter.pprintln(PPrinter.scala:141)
        at Testo$.delayedEndpoint$Testo$1(Test.scala:42)
        at Testo$delayedInit$body.apply(Test.scala:15)
        at scala.Function0.apply$mcV$sp(Function0.scala:39)
        at scala.Function0.apply$mcV$sp$(Function0.scala:39)
        at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
        at scala.App.$anonfun$main$1$adapted(App.scala:80)
        at scala.collection.immutable.List.foreach(List.scala:431)
        at scala.App.main(App.scala:80)

Any ideas on how to do this, most welcome !

1

There are 1 best solutions below

1
Gastón Schabas On

The problem is not related with the library. Your code is throwing MatchError

This class implements errors which are thrown whenever an object doesn't match any pattern of a pattern matching expression.

It is saying that none of the cases you have, match against to the object you are pattern matching.

In the line

val stringo: Seq[String] = p
  .productIterator // returns a Iterator[Any]
  .toSeq // converts Iterator[Any] to Seq[Any]
  .map {
    case (name, value) => // Any doesn't match with Tuple2(x, y)
      s"$name = ${pprint.apply(value)}"
  }

As I detailed in the comments, Product.productIterator returns a value of type Iterator[Any]. When you write a case like

case (x, y) => //...

It is expecting a tuple of 2 elements. I guess you want to get the name of the element and also the value. In that case, you can use the method Product.productElementNames combined with productIterator.

case p: Product => {
  val nameAndValue = p.productElementNames.zip(p.productIterator)
  val stringo: Seq[String] = nameAndValue.toSeq.map {
    case (name, value) => s"$name = ${pprint.apply(value)}"
  }
  pprint.Tree.Literal(stringo.mkString(", "))
}

I think doing that will produce the expected output