Last active
January 17, 2024 14:02
-
-
Save carymrobbins/7b8ed52cd6ea186dbdf8 to your computer and use it in GitHub Desktop.
Pretty print Scala case classes and other data structures.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Pretty prints a Scala value similar to its source represention. | |
* Particularly useful for case classes. | |
* @param a - The value to pretty print. | |
* @param indentSize - Number of spaces for each indent. | |
* @param maxElementWidth - Largest element size before wrapping. | |
* @param depth - Initial depth to pretty print indents. | |
* @return | |
*/ | |
private def prettyPrint(a: Any, indentSize: Int = 2, maxElementWidth: Int = 30, depth: Int = 0): String = { | |
val indent = " " * depth * indentSize | |
val fieldIndent = indent + (" " * indentSize) | |
val thisDepth = prettyPrint(_: Any, indentSize, maxElementWidth, depth) | |
val nextDepth = prettyPrint(_: Any, indentSize, maxElementWidth, depth + 1) | |
a match { | |
// Make Strings look similar to their literal form. | |
case s: String => | |
val replaceMap = Seq( | |
"\n" -> "\\n", | |
"\r" -> "\\r", | |
"\t" -> "\\t", | |
"\"" -> "\\\"" | |
) | |
'"' + replaceMap.foldLeft(s) { case (acc, (c, r)) => acc.replace(c, r) } + '"' | |
// For an empty Seq just use its normal String representation. | |
case xs: Seq[_] if xs.isEmpty => xs.toString() | |
case xs: Seq[_] => | |
// If the Seq is not too long, pretty print on one line. | |
val resultOneLine = xs.map(nextDepth).toString() | |
if (resultOneLine.length <= maxElementWidth) return resultOneLine | |
// Otherwise, build it with newlines and proper field indents. | |
val result = xs.map(x => s"\n$fieldIndent${nextDepth(x)}").toString() | |
result.substring(0, result.length - 1) + "\n" + indent + ")" | |
// Product should cover case classes. | |
case p: Product => | |
val prefix = p.productPrefix | |
// We'll use reflection to get the constructor arg names and values. | |
val cls = p.getClass | |
val fields = cls.getDeclaredFields.filterNot(_.isSynthetic).map(_.getName) | |
val values = p.productIterator.toSeq | |
// If we weren't able to match up fields/values, fall back to toString. | |
if (fields.length != values.length) return p.toString | |
fields.zip(values).toList match { | |
// If there are no fields, just use the normal String representation. | |
case Nil => p.toString | |
// If there is just one field, let's just print it as a wrapper. | |
case (_, value) :: Nil => s"$prefix(${thisDepth(value)})" | |
// If there is more than one field, build up the field names and values. | |
case kvps => | |
val prettyFields = kvps.map { case (k, v) => s"$fieldIndent$k = ${nextDepth(v)}" } | |
// If the result is not too long, pretty print on one line. | |
val resultOneLine = s"$prefix(${prettyFields.mkString(", ")})" | |
if (resultOneLine.length <= maxElementWidth) return resultOneLine | |
// Otherwise, build it with newlines and proper field indents. | |
s"$prefix(\n${prettyFields.mkString(",\n")}\n$indent)" | |
} | |
// If we haven't specialized this type, just use its toString. | |
case _ => a.toString | |
} | |
} |
Very easy to use with REPL. Thank you!
Just a heads up - since this uses reflection, it's not going to work if you're compiling with ScalaJS.
thank you
thank you
Good Job, you should add
case opt: Some[_] => "Some(" + prettyPrint(opt.get) + ")"
Added Option and Map support:
https://gist.github.com/myDisconnect/1f7046b23e18b4b43dd3c5932d0db7dc
thank you!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good job. Thank you.