diff options
Diffstat (limited to 'shared/src/main/scala/spray/json/JsonPrinter.scala')
-rw-r--r-- | shared/src/main/scala/spray/json/JsonPrinter.scala | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/shared/src/main/scala/spray/json/JsonPrinter.scala b/shared/src/main/scala/spray/json/JsonPrinter.scala new file mode 100644 index 0000000..42cbab4 --- /dev/null +++ b/shared/src/main/scala/spray/json/JsonPrinter.scala @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2009-2011 Mathias Doenitz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package spray.json + +import annotation.tailrec +import java.lang.{StringBuilder => JStringBuilder} + +/** + * A JsonPrinter serializes a JSON AST to a String. + */ +trait JsonPrinter extends (JsValue => String) { + + def apply(x: JsValue): String = apply(x, None) + + def apply(x: JsValue, + jsonpCallback: Option[String] = None, + sb: JStringBuilder = new JStringBuilder(256)): String = { + jsonpCallback match { + case Some(callback) => + sb.append(callback).append('(') + print(x, sb) + sb.append(')') + case None => print(x, sb) + } + sb.toString + } + + def print(x: JsValue, sb: JStringBuilder): Unit + + protected def printLeaf(x: JsValue, sb: JStringBuilder): Unit = { + x match { + case JsNull => sb.append("null") + case JsTrue => sb.append("true") + case JsFalse => sb.append("false") + case JsNumber(x) => sb.append(x) + case JsString(x) => printString(x, sb) + case _ => throw new IllegalStateException + } + } + + protected def printString(s: String, sb: JStringBuilder): Unit = { + import JsonPrinter._ + @tailrec def firstToBeEncoded(ix: Int = 0): Int = + if (ix == s.length) -1 else if (requiresEncoding(s.charAt(ix))) ix else firstToBeEncoded(ix + 1) + + sb.append('"') + firstToBeEncoded() match { + case -1 => sb.append(s) + case first => + sb.append(s, 0, first) + @tailrec def append(ix: Int): Unit = + if (ix < s.length) { + s.charAt(ix) match { + case c if !requiresEncoding(c) => sb.append(c) + case '"' => sb.append("\\\"") + case '\\' => sb.append("\\\\") + case '\b' => sb.append("\\b") + case '\f' => sb.append("\\f") + case '\n' => sb.append("\\n") + case '\r' => sb.append("\\r") + case '\t' => sb.append("\\t") + case x if x <= 0xF => sb.append("\\u000").append(Integer.toHexString(x)) + case x if x <= 0xFF => sb.append("\\u00").append(Integer.toHexString(x)) + case x if x <= 0xFFF => sb.append("\\u0").append(Integer.toHexString(x)) + case x => sb.append("\\u").append(Integer.toHexString(x)) + } + append(ix + 1) + } + append(first) + } + sb.append('"') + } + + protected def printSeq[A](iterable: Iterable[A], printSeparator: => Unit)(f: A => Unit): Unit = { + var first = true + iterable.foreach { a => + if (first) first = false else printSeparator + f(a) + } + } +} + +object JsonPrinter { + def requiresEncoding(c: Char): Boolean = + // from RFC 4627 + // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + c match { + case '"' => true + case '\\' => true + case c => c < 0x20 + } +}
\ No newline at end of file |