Scala - how to print case classes like (pretty printed) tree

前端 未结 10 1825
情歌与酒
情歌与酒 2020-12-07 22:35

I\'m making a parser with Scala Combinators. It is awesome. What I end up with is a long list of entagled case classes, like: ClassDecl(Complex,List(VarDecl(Real,float

相关标签:
10条回答
  • 2020-12-07 23:04

    Here's my solution which greatly improves how http://www.lihaoyi.com/PPrint/ handles the case-classes (see https://github.com/lihaoyi/PPrint/issues/4 ).

    e.g. it prints this:

    for such a usage:

      pprint2 = pprint.copy(additionalHandlers = pprintAdditionalHandlers)
    
      case class Author(firstName: String, lastName: String)
      case class Book(isbn: String, author: Author)
      val b = Book("978-0486282114", Author("first", "last"))
      pprint2.pprintln(b)
    

    code:

    import pprint.{PPrinter, Tree, Util}
    object PPrintUtils {
      // in scala 2.13 this would be even simpler/cleaner due to added product.productElementNames
      protected def caseClassToMap(cc: Product): Map[String, Any] = {
        val fieldValues = cc.productIterator.toSet
        val fields = cc.getClass.getDeclaredFields.toSeq
          .filterNot(f => f.isSynthetic || java.lang.reflect.Modifier.isStatic(f.getModifiers))
        fields.map { f =>
          f.setAccessible(true)
          f.getName -> f.get(cc)
        }.filter { case (k, v) => fieldValues.contains(v) }
          .toMap
      }
    
      var pprint2: PPrinter = _
    
      protected def pprintAdditionalHandlers: PartialFunction[Any, Tree] = {
        case x: Product =>
          val className = x.getClass.getName
          // see source code for pprint.treeify()
          val shouldNotPrettifyCaseClass = x.productArity == 0 || (x.productArity == 2 && Util.isOperator(x.productPrefix)) || className.startsWith(pprint.tuplePrefix) || className == "scala.Some"
    
          if (shouldNotPrettifyCaseClass)
            pprint.treeify(x)
          else {
            val fieldMap = caseClassToMap(x)
            pprint.Tree.Apply(
              x.productPrefix,
              fieldMap.iterator.flatMap { case (k, v) =>
                val prettyValue: Tree = pprintAdditionalHandlers.lift(v).getOrElse(pprint2.treeify(v))
                Seq(pprint.Tree.Infix(Tree.Literal(k), "=", prettyValue))
              }
            )
          }
      }
    
      pprint2 = pprint.copy(additionalHandlers = pprintAdditionalHandlers)
    }
    
    // usage
    pprint2.println(SomeFancyObjectWithNestedCaseClasses(...))
    
    0 讨论(0)
  • 2020-12-07 23:05

    Starting Scala 2.13, case classes (which are an implementation of Product) are now provided with a productElementNames method which returns an iterator over their field's names.

    Combined with Product::productIterator which provides the values of a case class, we have a simple way to pretty print case classes without requiring reflection:

    def pprint(obj: Any, depth: Int = 0, paramName: Option[String] = None): Unit = {
    
      val indent = "  " * depth
      val prettyName = paramName.fold("")(x => s"$x: ")
      val ptype = obj match { case _: Iterable[Any] => "" case obj: Product => obj.productPrefix case _ => obj.toString }
    
      println(s"$indent$prettyName$ptype")
    
      obj match {
        case seq: Iterable[Any] =>
          seq.foreach(pprint(_, depth + 1))
        case obj: Product =>
          (obj.productIterator zip obj.productElementNames)
            .foreach { case (subObj, paramName) => pprint(subObj, depth + 1, Some(paramName)) }
        case _ =>
      }
    }
    

    which for your specific scenario:

    // sealed trait Kind
    // case object Complex extends Kind
    // case class VarDecl(a: Int, b: String)
    // case class ClassDecl(kind: Kind, decls: List[VarDecl])
    
    val data = ClassDecl(Complex, List(VarDecl(1, "abcd"), VarDecl(2, "efgh")))
    
    pprint(data)
    

    produces:

    ClassDecl
      kind: Complex
      decls: 
        VarDecl
          a: 1
          b: abcd
        VarDecl
          a: 2
          b: efgh
    
    0 讨论(0)
  • 2020-12-07 23:06
    import java.lang.reflect.Field
    ...
    
    /**
      * Pretty prints case classes with field names.
      * Handles sequences and arrays of such values.
      * Ideally, one could take the output and paste it into source code and have it compile.
      */
    def prettyPrint(a: Any): String = {
      // Recursively get all the fields; this will grab vals declared in parents of case classes.
      def getFields(cls: Class[_]): List[Field] =
        Option(cls.getSuperclass).map(getFields).getOrElse(Nil) ++
            cls.getDeclaredFields.toList.filterNot(f =>
              f.isSynthetic || java.lang.reflect.Modifier.isStatic(f.getModifiers))
      a match {
        // Make Strings look similar to their literal form.
        case s: String =>
          '"' + Seq("\n" -> "\\n", "\r" -> "\\r", "\t" -> "\\t", "\"" -> "\\\"", "\\" -> "\\\\").foldLeft(s) {
            case (acc, (c, r)) => acc.replace(c, r) } + '"'
        case xs: Seq[_] =>
          xs.map(prettyPrint).toString
        case xs: Array[_] =>
          s"Array(${xs.map(prettyPrint) mkString ", "})"
        // This covers case classes.
        case p: Product =>
          s"${p.productPrefix}(${
            (getFields(p.getClass) map { f =>
              f setAccessible true
              s"${f.getName} = ${prettyPrint(f.get(p))}"
            }) mkString ", "
          })"
        // General objects and primitives end up here.
        case q =>
          Option(q).map(_.toString).getOrElse("¡null!")
      }
    }
    
    0 讨论(0)
  • 2020-12-07 23:06

    If you use Apache Spark, you can use the following method to print your case classes :

    def prettyPrint[T <: Product : scala.reflect.runtime.universe.TypeTag](c:T) = {
      import play.api.libs.json.Json
      println(Json.prettyPrint(Json.parse(Seq(c).toDS().toJSON.head)))
    }
    

    This gives a nicely formatted JSON representation of your case class instance. Make sure sparkSession.implicits._ is imported

    example:

    case class Adress(country:String,city:String,zip:Int,street:String) 
    case class Person(name:String,age:Int,adress:Adress) 
    val person = Person("Peter",36,Adress("Switzerland","Zürich",9876,"Bahnhofstrasse 69"))
    
    prettyPrint(person)
    

    gives :

    {
      "name" : "Peter",
      "age" : 36,
      "adress" : {
        "country" : "Switzerland",
        "city" : "Zürich",
        "zip" : 9876,
        "street" : "Bahnhofstrasse 69"
      }
    }
    
    0 讨论(0)
提交回复
热议问题