com.rojoma.json.v3.io.PrettyJsonWriter.scala Maven / Gradle / Ivy
The newest version!
package com.rojoma.json.v3
package io
import scala.{collection => sc}
import java.io.Writer
import ast._
import extensions.StringWriter
private[io] case class PrettyContext(output: Writer, leftMargin: List[String], indentSize: Int, availableSpace: Int) {
def indented = shifted(indentSize)
def shifted(n: Int) = copy(leftMargin = (" " * n) :: leftMargin, availableSpace = availableSpace - n)
def printMargin() = for(s <- leftMargin) output.write(s)
}
/** An object that will write [[com.rojoma.json.v3.ast.JValue]]s in a human-friendly
* indented format.
*
* The writer will try to keep as much data on a single line as
* possible. As a result, arrays and objects have two printing
* modes (selected automatically), "compact" and "indented".
*
* In the "compact" mode, an array is stored on a single line:
* {{{
* [ elem1, elem2, ... ]
* }}}
* Similarly for objects:
* {{{
* { "key1" : value1, "key2 : value2, ... }
* }}}
*
* In the "indented" mode, the delimiters appear on lines by
* themselves, with each element formatted on a separate line:
* {{{
* [
* elem1,
* elem2,
* ...
* ]
* }}}
* For objects, if a key/value pair does not fit on a single line,
* the value is printed on a line of its own, indented further:
* {{{
* {
* "key" :
* value
* }
* }}}
*
* Empty arrays and objects are always represented compactly,
* as `[]` or `{}` respectively.
*
* This does many small writes, so it is probably a good idea to wrap
* the `Writer` in a `BufferedWriter`. */
class PrettyJsonWriter private (context: PrettyContext) extends JsonWriter {
def this(output: Writer, indentation: Int = 2, leftMargin: Int = 0, targetWidth: Int = 78) =
this(PrettyContext(output, List(" " * leftMargin), indentation, targetWidth))
private def output = context.output
private def size(atom: JAtom) = atom match {
case JNull => "null".length
case JBoolean(x) => x.toString.length
case JString(s) => StringWriter.stringWriter.toString(s).length
case n: JNumber => n.toString.length
}
private def willFitIn(elem: JValue, space: Int): Option[Int] = elem match {
case atom: JAtom => Some(size(atom))
case JArray(elems) =>
if(elems.isEmpty) {
Some("[]".length)
} else {
var remaining = space - "[ ]".length
val it = elems.iterator
var didOne = false
while(it.hasNext) {
if(didOne) remaining -= ", ".length
else didOne = true
willFitIn(it.next(), remaining) match {
case None => return None
case Some(size) => remaining -= size
}
}
if(remaining >= 0) Some(space - remaining)
else None
}
case JObject(fields) =>
if(fields.isEmpty) {
Some("{}".length)
} else {
var remaining = space - "{ }".length
val it = fields.iterator
var didOne = false
while(it.hasNext) {
if(didOne) remaining -= ", ".length
else didOne = true
remaining -= " : ".length
val (k, v) = it.next()
willFitIn(JString(k), remaining) match {
case None => return None
case Some(size) => remaining -= size
}
willFitIn(v, remaining) match {
case None => return None
case Some(size) => remaining -= size
}
}
if(remaining >= 0) Some(space - remaining)
else None
}
}
private def writeCompactly(jobject: JValue) {
jobject match {
case JArray(elements) =>
writeArrayCompactly(elements)
case JObject(fields) =>
writeObjectCompactly(fields)
case other: JAtom =>
write(other)
}
}
protected def writeArray(elements: sc.Seq[JValue]) {
if(willFitIn(JArray(elements), context.availableSpace).isDefined) {
writeArrayCompactly(elements)
} else {
output.write("[\n")
val newContext = context.indented
val newWriter = new PrettyJsonWriter(newContext)
newContext.printMargin()
var didOne = false
for(elem <- elements) {
if(didOne) { output.write(",\n"); newContext.printMargin() }
else didOne = true
newWriter.write(elem)
}
output.write("\n")
context.printMargin()
output.write("]")
}
}
protected def writeObject(fields: sc.Map[String, JValue]) {
if(willFitIn(JObject(fields), context.availableSpace).isDefined) {
writeObjectCompactly(fields)
} else {
output.write("{\n")
val newContext = context.indented
newContext.printMargin()
var didOne = false
for((k, v) <- fields) {
if(didOne) { output.write(",\n"); newContext.printMargin() }
else didOne = true
val key = StringWriter.stringWriter.toString(k)
output.write(key)
val spaceForValue = context.availableSpace - key.length - " : ".length
if(willFitIn(v, spaceForValue).isDefined) {
output.write(" : ")
val newerContext = newContext.shifted(key.length + " : ".length)
val newerWriter = new PrettyJsonWriter(newerContext)
newerWriter.write(v)
} else {
output.write(" :\n")
val newerContext = newContext.indented
val newerWriter = new PrettyJsonWriter(newerContext)
newerContext.printMargin()
newerWriter.write(v)
}
}
output.write("\n")
context.printMargin()
output.write("}")
}
}
private def writeArrayCompactly(elements: sc.Seq[JValue]) {
if(elements.isEmpty) {
output.write("[]")
} else {
output.write("[ ")
var didOne = false
for(element <- elements) {
if(didOne) output.write(", ")
else didOne = true
writeCompactly(element)
}
output.write(" ]")
}
}
private def writeObjectCompactly(fields: sc.Map[String, JValue]) {
if(fields.isEmpty) {
output.write("{}")
} else {
output.write("{ ")
var didOne = false
for((k, v) <- fields) {
if(didOne) output.write(", ")
else didOne = true
StringWriter.stringWriter.toWriter(output, k)
output.write(" : ")
writeCompactly(v)
}
output.write(" }")
}
}
protected def writeBoolean(b: Boolean) {
output.write(if(b) "true" else "false")
}
protected def writeNull() {
output.write("null")
}
protected def writeNumber(x: JNumber) {
x.toWriter(output)
}
protected def writeString(s: String) {
StringWriter.stringWriter.toWriter(output, s)
}
}
object PrettyJsonWriter {
/** Utility function for writing a single datum to a `Writer`.
* @throws `IOException` if a low-level IO exception occurs.
* @see [[com.rojoma.json.v3.io.PrettyJsonWriter]] */
@throws(classOf[java.io.IOException])
def toWriter(w: Writer, datum: JValue) = new PrettyJsonWriter(w).write(datum)
/** Utility function for writing a single datum to a `String`.
* @return The encoded JSON object.
* @see [[com.rojoma.json.v3.io.PrettyJsonWriter]] */
def toString(datum: JValue) = {
val w = new java.io.StringWriter
toWriter(w, datum)
w.toString
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy