summaryrefslogtreecommitdiff
path: root/shared/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'shared/src/main')
-rw-r--r--shared/src/main/scala/spray/json/AdditionalFormats.scala117
-rw-r--r--shared/src/main/scala/spray/json/BasicFormats.scala139
-rw-r--r--shared/src/main/scala/spray/json/CollectionFormats.scala95
-rw-r--r--shared/src/main/scala/spray/json/CompactPrinter.scala51
-rw-r--r--shared/src/main/scala/spray/json/DefaultJsonProtocol.scala30
-rw-r--r--shared/src/main/scala/spray/json/JsValue.scala125
-rw-r--r--shared/src/main/scala/spray/json/JsonFormat.scala71
-rw-r--r--shared/src/main/scala/spray/json/JsonParser.scala379
-rw-r--r--shared/src/main/scala/spray/json/JsonParserSettings.scala56
-rw-r--r--shared/src/main/scala/spray/json/JsonPrinter.scala106
-rw-r--r--shared/src/main/scala/spray/json/PrettyPrinter.scala71
-rw-r--r--shared/src/main/scala/spray/json/SortedPrinter.scala25
-rw-r--r--shared/src/main/scala/spray/json/StandardFormats.scala120
-rw-r--r--shared/src/main/scala/spray/json/package.scala66
14 files changed, 1451 insertions, 0 deletions
diff --git a/shared/src/main/scala/spray/json/AdditionalFormats.scala b/shared/src/main/scala/spray/json/AdditionalFormats.scala
new file mode 100644
index 0000000..fbabb0b
--- /dev/null
+++ b/shared/src/main/scala/spray/json/AdditionalFormats.scala
@@ -0,0 +1,117 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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
+
+/**
+ * Provides additional JsonFormats and helpers
+ */
+trait AdditionalFormats {
+
+ implicit object JsValueFormat extends JsonFormat[JsValue] {
+ def write(value: JsValue) = value
+ def read(value: JsValue) = value
+ }
+
+ implicit object RootJsObjectFormat extends RootJsonFormat[JsObject] {
+ def write(value: JsObject) = value
+ def read(value: JsValue) = value.asJsObject
+ }
+
+ implicit object RootJsArrayFormat extends RootJsonFormat[JsArray] {
+ def write(value: JsArray) = value
+ def read(value: JsValue) = value match {
+ case x: JsArray => x
+ case _ => deserializationError("JSON array expected")
+ }
+ }
+
+ /**
+ * Constructs a JsonFormat from its two parts, JsonReader and JsonWriter.
+ */
+ def jsonFormat[T](reader: JsonReader[T], writer: JsonWriter[T]) = new JsonFormat[T] {
+ def write(obj: T) = writer.write(obj)
+ def read(json: JsValue) = reader.read(json)
+ }
+
+ /**
+ * Constructs a RootJsonFormat from its two parts, RootJsonReader and RootJsonWriter.
+ */
+ def rootJsonFormat[T](reader: RootJsonReader[T], writer: RootJsonWriter[T]) =
+ rootFormat(jsonFormat(reader, writer))
+
+ /**
+ * Turns a JsonWriter into a JsonFormat that throws an UnsupportedOperationException for reads.
+ */
+ def lift[T](writer :JsonWriter[T]) = new JsonFormat[T] {
+ def write(obj: T): JsValue = writer.write(obj)
+ def read(value: JsValue) =
+ throw new UnsupportedOperationException("JsonReader implementation missing")
+ }
+
+ /**
+ * Turns a RootJsonWriter into a RootJsonFormat that throws an UnsupportedOperationException for reads.
+ */
+ def lift[T](writer :RootJsonWriter[T]): RootJsonFormat[T] =
+ rootFormat(lift(writer :JsonWriter[T]))
+
+ /**
+ * Turns a JsonReader into a JsonFormat that throws an UnsupportedOperationException for writes.
+ */
+ def lift[T <: AnyRef](reader :JsonReader[T]) = new JsonFormat[T] {
+ def write(obj: T): JsValue =
+ throw new UnsupportedOperationException("No JsonWriter[" + obj.getClass + "] available")
+ def read(value: JsValue) = reader.read(value)
+ }
+
+ /**
+ * Turns a RootJsonReader into a RootJsonFormat that throws an UnsupportedOperationException for writes.
+ */
+ def lift[T <: AnyRef](reader :RootJsonReader[T]): RootJsonFormat[T] =
+ rootFormat(lift(reader :JsonReader[T]))
+
+ /**
+ * Lazy wrapper around serialization. Useful when you want to serialize (mutually) recursive structures.
+ */
+ def lazyFormat[T](format: => JsonFormat[T]) = new JsonFormat[T] {
+ lazy val delegate = format;
+ def write(x: T) = delegate.write(x);
+ def read(value: JsValue) = delegate.read(value);
+ }
+
+ /**
+ * Explicitly turns a JsonFormat into a RootJsonFormat.
+ */
+ def rootFormat[T](format: JsonFormat[T]) = new RootJsonFormat[T] {
+ def write(obj: T) = format.write(obj)
+ def read(json: JsValue) = format.read(json)
+ }
+
+ /**
+ * Wraps an existing JsonReader with Exception protection.
+ */
+ def safeReader[A :JsonReader] = new JsonReader[Either[Exception, A]] {
+ def read(json: JsValue) = {
+ try {
+ Right(json.convertTo[A])
+ } catch {
+ case e: Exception => Left(e)
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/shared/src/main/scala/spray/json/BasicFormats.scala b/shared/src/main/scala/spray/json/BasicFormats.scala
new file mode 100644
index 0000000..2e6342f
--- /dev/null
+++ b/shared/src/main/scala/spray/json/BasicFormats.scala
@@ -0,0 +1,139 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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
+
+/**
+ * Provides the JsonFormats for the most important Scala types.
+ */
+trait BasicFormats {
+
+ implicit object IntJsonFormat extends JsonFormat[Int] {
+ def write(x: Int) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.intValue
+ case x => deserializationError("Expected Int as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object LongJsonFormat extends JsonFormat[Long] {
+ def write(x: Long) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.longValue
+ case x => deserializationError("Expected Long as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object FloatJsonFormat extends JsonFormat[Float] {
+ def write(x: Float) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.floatValue
+ case JsNull => Float.NaN
+ case x => deserializationError("Expected Float as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object DoubleJsonFormat extends JsonFormat[Double] {
+ def write(x: Double) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.doubleValue
+ case JsNull => Double.NaN
+ case x => deserializationError("Expected Double as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object ByteJsonFormat extends JsonFormat[Byte] {
+ def write(x: Byte) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.byteValue
+ case x => deserializationError("Expected Byte as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object ShortJsonFormat extends JsonFormat[Short] {
+ def write(x: Short) = JsNumber(x)
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.shortValue
+ case x => deserializationError("Expected Short as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object BigDecimalJsonFormat extends JsonFormat[BigDecimal] {
+ def write(x: BigDecimal) = {
+ require(x ne null)
+ JsNumber(x)
+ }
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x
+ case JsString(x) => BigDecimal(x)
+ case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object BigIntJsonFormat extends JsonFormat[BigInt] {
+ def write(x: BigInt) = {
+ require(x ne null)
+ JsNumber(x)
+ }
+ def read(value: JsValue) = value match {
+ case JsNumber(x) => x.toBigInt
+ case JsString(x) => BigInt(x)
+ case x => deserializationError("Expected BigInt as JsNumber, but got " + x)
+ }
+ }
+
+ implicit object UnitJsonFormat extends JsonFormat[Unit] {
+ def write(x: Unit) = JsNumber(1)
+ def read(value: JsValue): Unit = {}
+ }
+
+ implicit object BooleanJsonFormat extends JsonFormat[Boolean] {
+ def write(x: Boolean) = JsBoolean(x)
+ def read(value: JsValue) = value match {
+ case JsTrue => true
+ case JsFalse => false
+ case x => deserializationError("Expected JsBoolean, but got " + x)
+ }
+ }
+
+ implicit object CharJsonFormat extends JsonFormat[Char] {
+ def write(x: Char) = JsString(String.valueOf(x))
+ def read(value: JsValue) = value match {
+ case JsString(x) if x.length == 1 => x.charAt(0)
+ case x => deserializationError("Expected Char as single-character JsString, but got " + x)
+ }
+ }
+
+ implicit object StringJsonFormat extends JsonFormat[String] {
+ def write(x: String) = {
+ require(x ne null)
+ JsString(x)
+ }
+ def read(value: JsValue) = value match {
+ case JsString(x) => x
+ case x => deserializationError("Expected String as JsString, but got " + x)
+ }
+ }
+
+ implicit object SymbolJsonFormat extends JsonFormat[Symbol] {
+ def write(x: Symbol) = JsString(x.name)
+ def read(value: JsValue) = value match {
+ case JsString(x) => Symbol(x)
+ case x => deserializationError("Expected Symbol as JsString, but got " + x)
+ }
+ }
+}
diff --git a/shared/src/main/scala/spray/json/CollectionFormats.scala b/shared/src/main/scala/spray/json/CollectionFormats.scala
new file mode 100644
index 0000000..ef94297
--- /dev/null
+++ b/shared/src/main/scala/spray/json/CollectionFormats.scala
@@ -0,0 +1,95 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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 scala.reflect.ClassTag
+
+trait CollectionFormats {
+
+ /**
+ * Supplies the JsonFormat for Lists.
+ */
+ implicit def listFormat[T :JsonFormat] = new RootJsonFormat[List[T]] {
+ def write(list: List[T]) = JsArray(list.map(_.toJson).toVector)
+ def read(value: JsValue): List[T] = value match {
+ case JsArray(elements) => elements.toIterator.map(_.convertTo[T]).toList
+ case x => deserializationError("Expected List as JsArray, but got " + x)
+ }
+ }
+
+ /**
+ * Supplies the JsonFormat for Arrays.
+ */
+ implicit def arrayFormat[T :JsonFormat :ClassTag] = new RootJsonFormat[Array[T]] {
+ def write(array: Array[T]) = JsArray(array.map(_.toJson).toVector)
+ def read(value: JsValue) = value match {
+ case JsArray(elements) => elements.map(_.convertTo[T]).toArray[T]
+ case x => deserializationError("Expected Array as JsArray, but got " + x)
+ }
+ }
+
+ /**
+ * Supplies the JsonFormat for Maps. The implicitly available JsonFormat for the key type K must
+ * always write JsStrings, otherwise a [[spray.json.SerializationException]] will be thrown.
+ */
+ implicit def mapFormat[K :JsonFormat, V :JsonFormat] = new RootJsonFormat[Map[K, V]] {
+ def write(m: Map[K, V]) = JsObject {
+ m.map { field =>
+ field._1.toJson match {
+ case JsString(x) => x -> field._2.toJson
+ case x => throw new SerializationException("Map key must be formatted as JsString, not '" + x + "'")
+ }
+ }
+ }
+ def read(value: JsValue) = value match {
+ case x: JsObject => x.fields.map { field =>
+ (JsString(field._1).convertTo[K], field._2.convertTo[V])
+ }
+ case x => deserializationError("Expected Map as JsObject, but got " + x)
+ }
+ }
+
+ import collection.{immutable => imm}
+
+ implicit def immIterableFormat[T :JsonFormat] = viaSeq[imm.Iterable[T], T](seq => imm.Iterable(seq :_*))
+ implicit def immSeqFormat[T :JsonFormat] = viaSeq[imm.Seq[T], T](seq => imm.Seq(seq :_*))
+ implicit def immIndexedSeqFormat[T :JsonFormat] = viaSeq[imm.IndexedSeq[T], T](seq => imm.IndexedSeq(seq :_*))
+ implicit def immLinearSeqFormat[T :JsonFormat] = viaSeq[imm.LinearSeq[T], T](seq => imm.LinearSeq(seq :_*))
+ implicit def immSetFormat[T :JsonFormat] = viaSeq[imm.Set[T], T](seq => imm.Set(seq :_*))
+ implicit def vectorFormat[T :JsonFormat] = viaSeq[Vector[T], T](seq => Vector(seq :_*))
+
+ import collection._
+
+ implicit def iterableFormat[T :JsonFormat] = viaSeq[Iterable[T], T](seq => Iterable(seq :_*))
+ implicit def seqFormat[T :JsonFormat] = viaSeq[Seq[T], T](seq => Seq(seq :_*))
+ implicit def indexedSeqFormat[T :JsonFormat] = viaSeq[IndexedSeq[T], T](seq => IndexedSeq(seq :_*))
+ implicit def linearSeqFormat[T :JsonFormat] = viaSeq[LinearSeq[T], T](seq => LinearSeq(seq :_*))
+ implicit def setFormat[T :JsonFormat] = viaSeq[Set[T], T](seq => Set(seq :_*))
+
+ /**
+ * A JsonFormat construction helper that creates a JsonFormat for an Iterable type I from a builder function
+ * List => I.
+ */
+ def viaSeq[I <: Iterable[T], T :JsonFormat](f: imm.Seq[T] => I): RootJsonFormat[I] = new RootJsonFormat[I] {
+ def write(iterable: I) = JsArray(iterable.map(_.toJson).toVector)
+ def read(value: JsValue) = value match {
+ case JsArray(elements) => f(elements.map(_.convertTo[T]))
+ case x => deserializationError("Expected Collection as JsArray, but got " + x)
+ }
+ }
+}
diff --git a/shared/src/main/scala/spray/json/CompactPrinter.scala b/shared/src/main/scala/spray/json/CompactPrinter.scala
new file mode 100644
index 0000000..7260c2f
--- /dev/null
+++ b/shared/src/main/scala/spray/json/CompactPrinter.scala
@@ -0,0 +1,51 @@
+/*
+ * 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 java.lang.StringBuilder
+
+/**
+ * A JsonPrinter that produces compact JSON source without any superfluous whitespace.
+ */
+trait CompactPrinter extends JsonPrinter {
+
+ def print(x: JsValue, sb: StringBuilder): Unit = {
+ x match {
+ case JsObject(x) => printObject(x, sb)
+ case JsArray(x) => printArray(x, sb)
+ case _ => printLeaf(x, sb)
+ }
+ }
+
+ protected def printObject(members: Map[String, JsValue], sb: StringBuilder): Unit = {
+ sb.append('{')
+ printSeq(members, sb.append(',')) { m =>
+ printString(m._1, sb)
+ sb.append(':')
+ print(m._2, sb)
+ }
+ sb.append('}')
+ }
+
+ protected def printArray(elements: Seq[JsValue], sb: StringBuilder): Unit = {
+ sb.append('[')
+ printSeq(elements, sb.append(','))(print(_, sb))
+ sb.append(']')
+ }
+}
+
+object CompactPrinter extends CompactPrinter \ No newline at end of file
diff --git a/shared/src/main/scala/spray/json/DefaultJsonProtocol.scala b/shared/src/main/scala/spray/json/DefaultJsonProtocol.scala
new file mode 100644
index 0000000..4c93184
--- /dev/null
+++ b/shared/src/main/scala/spray/json/DefaultJsonProtocol.scala
@@ -0,0 +1,30 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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
+
+/**
+ * Provides all the predefined JsonFormats.
+ */
+trait DefaultJsonProtocol
+ extends BasicFormats
+ with StandardFormats
+ with CollectionFormats
+ with ProductFormats
+ with AdditionalFormats
+
+object DefaultJsonProtocol extends DefaultJsonProtocol
diff --git a/shared/src/main/scala/spray/json/JsValue.scala b/shared/src/main/scala/spray/json/JsValue.scala
new file mode 100644
index 0000000..9ed94da
--- /dev/null
+++ b/shared/src/main/scala/spray/json/JsValue.scala
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2009-2011 Mathias Doenitz
+ * Inspired by a similar implementation by Nathan Hamblen
+ * (https://github.com/dispatch/classic)
+ *
+ * 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 collection.immutable
+import scala.collection.immutable.TreeMap
+
+/**
+ * The general type of a JSON AST node.
+ */
+sealed abstract class JsValue {
+ override def toString = compactPrint
+ def toString(printer: (JsValue => String)) = printer(this)
+ def compactPrint = CompactPrinter(this)
+ def prettyPrint = PrettyPrinter(this)
+ def sortedPrint = SortedPrinter(this)
+ def convertTo[T :JsonReader]: T = jsonReader[T].read(this)
+
+ /**
+ * Returns `this` if this JsValue is a JsObject, otherwise throws a DeserializationException with the given error msg.
+ */
+ def asJsObject(errorMsg: String = "JSON object expected"): JsObject = deserializationError(errorMsg)
+
+ /**
+ * Returns `this` if this JsValue is a JsObject, otherwise throws a DeserializationException.
+ */
+ def asJsObject: JsObject = asJsObject()
+
+ @deprecated("Superceded by 'convertTo'", "1.1.0")
+ def fromJson[T :JsonReader]: T = convertTo
+}
+
+/**
+ * A JSON object.
+ */
+case class JsObject(fields: Map[String, JsValue]) extends JsValue {
+ override def asJsObject(errorMsg: String) = this
+ def getFields(fieldNames: String*): immutable.Seq[JsValue] = fieldNames.toIterator.flatMap(fields.get).toList
+}
+object JsObject {
+ val empty = JsObject(TreeMap.empty[String, JsValue])
+ def apply(members: JsField*): JsObject = new JsObject(TreeMap(members: _*))
+ @deprecated("Use JsObject(JsValue*) instead", "1.3.0")
+ def apply(members: List[JsField]): JsObject = apply(members: _*)
+}
+
+/**
+ * A JSON array.
+ */
+case class JsArray(elements: Vector[JsValue]) extends JsValue {
+ @deprecated("Use JsArray(Vector[JsValue]) instead", "1.3.0")
+ def this(elements: List[JsValue]) = this(elements.toVector)
+}
+object JsArray {
+ val empty = JsArray(Vector.empty)
+ def apply(elements: JsValue*) = new JsArray(elements.toVector)
+ @deprecated("Use JsArray(Vector[JsValue]) instead", "1.3.0")
+ def apply(elements: List[JsValue]) = new JsArray(elements.toVector)
+}
+
+/**
+ * A JSON string.
+ */
+case class JsString(value: String) extends JsValue
+
+object JsString {
+ val empty = JsString("")
+ def apply(value: Symbol) = new JsString(value.name)
+}
+
+/**
+ * A JSON number.
+ */
+case class JsNumber(value: BigDecimal) extends JsValue
+object JsNumber {
+ val zero: JsNumber = apply(0)
+ def apply(n: Int) = new JsNumber(BigDecimal(n))
+ def apply(n: Long) = new JsNumber(BigDecimal(n))
+ def apply(n: Double) = n match {
+ case n if n.isNaN => JsNull
+ case n if n.isInfinity => JsNull
+ case _ => new JsNumber(BigDecimal(n))
+ }
+ def apply(n: BigInt) = new JsNumber(BigDecimal(n))
+ def apply(n: String) = new JsNumber(BigDecimal(n))
+ def apply(n: Array[Char]) = new JsNumber(BigDecimal(n))
+}
+
+/**
+ * JSON Booleans.
+ */
+sealed abstract class JsBoolean extends JsValue {
+ def value: Boolean
+}
+object JsBoolean {
+ def apply(x: Boolean): JsBoolean = if (x) JsTrue else JsFalse
+ def unapply(x: JsBoolean): Option[Boolean] = Some(x.value)
+}
+case object JsTrue extends JsBoolean {
+ def value = true
+}
+case object JsFalse extends JsBoolean {
+ def value = false
+}
+
+/**
+ * The representation for JSON null.
+ */
+case object JsNull extends JsValue
diff --git a/shared/src/main/scala/spray/json/JsonFormat.scala b/shared/src/main/scala/spray/json/JsonFormat.scala
new file mode 100644
index 0000000..c4915cc
--- /dev/null
+++ b/shared/src/main/scala/spray/json/JsonFormat.scala
@@ -0,0 +1,71 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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.implicitNotFound
+
+/**
+ * Provides the JSON deserialization for type T.
+ */
+@implicitNotFound(msg = "Cannot find JsonReader or JsonFormat type class for ${T}")
+trait JsonReader[T] {
+ def read(json: JsValue): T
+}
+
+object JsonReader {
+ implicit def func2Reader[T](f: JsValue => T): JsonReader[T] = new JsonReader[T] {
+ def read(json: JsValue) = f(json)
+ }
+}
+
+/**
+ * Provides the JSON serialization for type T.
+ */
+@implicitNotFound(msg = "Cannot find JsonWriter or JsonFormat type class for ${T}")
+trait JsonWriter[T] {
+ def write(obj: T): JsValue
+}
+
+object JsonWriter {
+ implicit def func2Writer[T](f: T => JsValue): JsonWriter[T] = new JsonWriter[T] {
+ def write(obj: T) = f(obj)
+ }
+}
+
+/**
+ * Provides the JSON deserialization and serialization for type T.
+ */
+trait JsonFormat[T] extends JsonReader[T] with JsonWriter[T]
+
+/**
+ * A special JsonReader capable of reading a legal JSON root object, i.e. either a JSON array or a JSON object.
+ */
+@implicitNotFound(msg = "Cannot find RootJsonReader or RootJsonFormat type class for ${T}")
+trait RootJsonReader[T] extends JsonReader[T]
+
+/**
+ * A special JsonWriter capable of writing a legal JSON root object, i.e. either a JSON array or a JSON object.
+ */
+@implicitNotFound(msg = "Cannot find RootJsonWriter or RootJsonFormat type class for ${T}")
+trait RootJsonWriter[T] extends JsonWriter[T]
+
+/**
+ * A special JsonFormat signaling that the format produces a legal JSON root object, i.e. either a JSON array
+ * or a JSON object.
+ */
+trait RootJsonFormat[T] extends JsonFormat[T] with RootJsonReader[T] with RootJsonWriter[T] \ No newline at end of file
diff --git a/shared/src/main/scala/spray/json/JsonParser.scala b/shared/src/main/scala/spray/json/JsonParser.scala
new file mode 100644
index 0000000..7dfae0a
--- /dev/null
+++ b/shared/src/main/scala/spray/json/JsonParser.scala
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2014 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 scala.annotation.{switch, tailrec}
+import java.lang.{StringBuilder => JStringBuilder}
+import java.nio.{ByteBuffer, CharBuffer}
+import java.nio.charset.Charset
+
+import scala.collection.immutable.TreeMap
+
+/**
+ * Fast, no-dependency parser for JSON as defined by http://tools.ietf.org/html/rfc4627.
+ */
+object JsonParser {
+ def apply(input: ParserInput): JsValue = new JsonParser(input).parseJsValue()
+ def apply(input: ParserInput, settings: JsonParserSettings): JsValue = new JsonParser(input, settings).parseJsValue()
+
+ class ParsingException(val summary: String, val detail: String = "")
+ extends RuntimeException(if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ":" + detail)
+}
+
+class JsonParser(input: ParserInput, settings: JsonParserSettings = JsonParserSettings.default) {
+ def this(input: ParserInput) = this(input, JsonParserSettings.default)
+
+ import JsonParser.ParsingException
+
+ private[this] val sb = new JStringBuilder
+ private[this] var cursorChar: Char = input.nextChar()
+ private[this] var jsValue: JsValue = _
+
+ def parseJsValue(): JsValue =
+ parseJsValue(false)
+
+ def parseJsValue(allowTrailingInput: Boolean): JsValue = {
+ ws()
+ `value`(settings.maxDepth)
+ if (!allowTrailingInput)
+ require(EOI)
+ jsValue
+ }
+
+ ////////////////////// GRAMMAR ////////////////////////
+
+ private final val EOI = '\uFFFF' // compile-time constant
+
+ // http://tools.ietf.org/html/rfc4627#section-2.1
+ private def `value`(remainingNesting: Int): Unit =
+ if (remainingNesting == 0)
+ throw new ParsingException(
+ "JSON input nested too deeply",
+ s"JSON input was nested more deeply than the configured limit of maxNesting = ${settings.maxDepth}"
+ )
+ else {
+ val mark = input.cursor
+ def simpleValue(matched: Boolean, value: JsValue) = if (matched) jsValue = value else fail("JSON Value", mark)
+ (cursorChar: @switch) match {
+ case 'f' => simpleValue(`false`(), JsFalse)
+ case 'n' => simpleValue(`null`(), JsNull)
+ case 't' => simpleValue(`true`(), JsTrue)
+ case '{' => advance(); `object`(remainingNesting)
+ case '[' => advance(); `array`(remainingNesting)
+ case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-' => `number`()
+ case '"' => `string`(); jsValue = if (sb.length == 0) JsString.empty else JsString(sb.toString)
+ case _ => fail("JSON Value")
+ }
+ }
+
+ private def `false`() = advance() && ch('a') && ch('l') && ch('s') && ws('e')
+ private def `null`() = advance() && ch('u') && ch('l') && ws('l')
+ private def `true`() = advance() && ch('r') && ch('u') && ws('e')
+
+ // http://tools.ietf.org/html/rfc4627#section-2.2
+ private def `object`(remainingNesting: Int): Unit = {
+ ws()
+ jsValue = if (cursorChar != '}') {
+ @tailrec def members(map: Map[String, JsValue]): Map[String, JsValue] = {
+ `string`()
+ require(':')
+ ws()
+ val key = sb.toString
+ `value`(remainingNesting - 1)
+ val nextMap = map.updated(key, jsValue)
+ if (ws(',')) members(nextMap) else nextMap
+ }
+ val map = members(TreeMap.empty[String, JsValue])
+ require('}')
+ JsObject(map)
+ } else {
+ advance()
+ JsObject.empty
+ }
+ ws()
+ }
+
+ // http://tools.ietf.org/html/rfc4627#section-2.3
+ private def `array`(remainingNesting: Int): Unit = {
+ ws()
+ jsValue = if (cursorChar != ']') {
+ val list = Vector.newBuilder[JsValue]
+ @tailrec def values(): Unit = {
+ `value`(remainingNesting - 1)
+ list += jsValue
+ if (ws(',')) values()
+ }
+ values()
+ require(']')
+ JsArray(list.result())
+ } else {
+ advance()
+ JsArray.empty
+ }
+ ws()
+ }
+
+ // http://tools.ietf.org/html/rfc4627#section-2.4
+ private def `number`() = {
+ val start = input.cursor
+ val startChar = cursorChar
+ ch('-')
+ `int`()
+ `frac`()
+ `exp`()
+ val numberLength = input.cursor - start
+
+ jsValue =
+ if (startChar == '0' && input.cursor - start == 1) JsNumber.zero
+ else if (numberLength <= settings.maxNumberCharacters) JsNumber(input.sliceCharArray(start, input.cursor))
+ else {
+ val numberSnippet = new String(input.sliceCharArray(start, math.min(input.cursor, start + 20)))
+ throw new ParsingException("Number too long",
+ s"The number starting with '$numberSnippet' had " +
+ s"$numberLength characters which is more than the allowed limit maxNumberCharacters = ${settings.maxNumberCharacters}. If this is legit input " +
+ s"consider increasing the limit."
+ )
+ }
+ ws()
+ }
+
+ private def `int`(): Unit = if (!ch('0')) oneOrMoreDigits()
+ private def `frac`(): Unit = if (ch('.')) oneOrMoreDigits()
+ private def `exp`(): Unit = if (ch('e') || ch('E')) { ch('-') || ch('+'); oneOrMoreDigits() }
+
+ private def oneOrMoreDigits(): Unit = if (DIGIT()) zeroOrMoreDigits() else fail("DIGIT")
+ @tailrec private def zeroOrMoreDigits(): Unit = if (DIGIT()) zeroOrMoreDigits()
+
+ private def DIGIT(): Boolean = cursorChar >= '0' && cursorChar <= '9' && advance()
+
+ // http://tools.ietf.org/html/rfc4627#section-2.5
+ private def `string`(): Unit = {
+ if (cursorChar == '"') cursorChar = input.nextUtf8Char() else fail("'\"'")
+ sb.setLength(0)
+ while (`char`()) cursorChar = input.nextUtf8Char()
+ require('"')
+ ws()
+ }
+
+ private def `char`() =
+ // simple bloom-filter that quick-matches the most frequent case of characters that are ok to append
+ // (it doesn't match control chars, EOI, '"', '?', '\', 'b' and certain higher, non-ASCII chars)
+ if (((1L << cursorChar) & ((31 - cursorChar) >> 31) & 0x7ffffffbefffffffL) != 0L) appendSB(cursorChar)
+ else cursorChar match {
+ case '"' | EOI => false
+ case '\\' => advance(); `escaped`()
+ case c => (c >= ' ') && appendSB(c)
+ }
+
+ private def `escaped`() = {
+ def hexValue(c: Char): Int =
+ if ('0' <= c && c <= '9') c - '0'
+ else if ('a' <= c && c <= 'f') c - 87
+ else if ('A' <= c && c <= 'F') c - 55
+ else fail("hex digit")
+ def unicode() = {
+ var value = hexValue(cursorChar)
+ advance()
+ value = (value << 4) + hexValue(cursorChar)
+ advance()
+ value = (value << 4) + hexValue(cursorChar)
+ advance()
+ value = (value << 4) + hexValue(cursorChar)
+ appendSB(value.toChar)
+ }
+ (cursorChar: @switch) match {
+ case '"' | '/' | '\\' => appendSB(cursorChar)
+ case 'b' => appendSB('\b')
+ case 'f' => appendSB('\f')
+ case 'n' => appendSB('\n')
+ case 'r' => appendSB('\r')
+ case 't' => appendSB('\t')
+ case 'u' => advance(); unicode()
+ case _ => fail("JSON escape sequence")
+ }
+ }
+
+ @tailrec private def ws(): Unit =
+ // fast test whether cursorChar is one of " \n\r\t"
+ if (((1L << cursorChar) & ((cursorChar - 64) >> 31) & 0x100002600L) != 0L) { advance(); ws() }
+
+ ////////////////////////// HELPERS //////////////////////////
+
+ private def ch(c: Char): Boolean = if (cursorChar == c) { advance(); true } else false
+ private def ws(c: Char): Boolean = if (ch(c)) { ws(); true } else false
+ private def advance(): Boolean = { cursorChar = input.nextChar(); true }
+ private def appendSB(c: Char): Boolean = { sb.append(c); true }
+ private def require(c: Char): Unit = if (!ch(c)) fail(s"'$c'")
+
+ private def fail(target: String, cursor: Int = input.cursor, errorChar: Char = cursorChar): Nothing = {
+ val ParserInput.Line(lineNr, col, text) = input.getLine(cursor)
+ val summary = {
+ val unexpected =
+ if (errorChar != EOI) {
+ val c = if (Character.isISOControl(errorChar)) "\\u%04x" format errorChar.toInt else errorChar.toString
+ s"character '$c'"
+ } else "end-of-input"
+ val expected = if (target != "'\uFFFF'") target else "end-of-input"
+ s"Unexpected $unexpected at input index $cursor (line $lineNr, position $col), expected $expected"
+ }
+ val detail = {
+ val sanitizedText = text.map(c => if (Character.isISOControl(c)) '?' else c)
+ s"\n$sanitizedText\n${" " * (col-1)}^\n"
+ }
+ throw new ParsingException(summary, detail)
+ }
+}
+
+trait ParserInput {
+ /**
+ * Advance the cursor and get the next char.
+ * Since the char is required to be a 7-Bit ASCII char no decoding is required.
+ */
+ def nextChar(): Char
+
+ /**
+ * Advance the cursor and get the next char, which could potentially be outside
+ * of the 7-Bit ASCII range. Therefore decoding might be required.
+ */
+ def nextUtf8Char(): Char
+
+ def cursor: Int
+ def length: Int
+ def sliceString(start: Int, end: Int): String
+ def sliceCharArray(start: Int, end: Int): Array[Char]
+ def getLine(index: Int): ParserInput.Line
+}
+
+object ParserInput {
+ private final val EOI = '\uFFFF' // compile-time constant
+ private final val ErrorChar = '\uFFFD' // compile-time constant, universal UTF-8 replacement character '�'
+
+ implicit def apply(string: String): StringBasedParserInput = new StringBasedParserInput(string)
+ implicit def apply(chars: Array[Char]): CharArrayBasedParserInput = new CharArrayBasedParserInput(chars)
+ implicit def apply(bytes: Array[Byte]): ByteArrayBasedParserInput = new ByteArrayBasedParserInput(bytes)
+
+ case class Line(lineNr: Int, column: Int, text: String)
+
+ abstract class DefaultParserInput extends ParserInput {
+ protected var _cursor: Int = -1
+ def cursor = _cursor
+ def getLine(index: Int): Line = {
+ val sb = new java.lang.StringBuilder
+ @tailrec def rec(ix: Int, lineStartIx: Int, lineNr: Int): Line =
+ nextUtf8Char() match {
+ case '\n' if index > ix => sb.setLength(0); rec(ix + 1, ix + 1, lineNr + 1)
+ case '\n' | EOI => Line(lineNr, index - lineStartIx + 1, sb.toString)
+ case c => sb.append(c); rec(ix + 1, lineStartIx, lineNr)
+ }
+ val savedCursor = _cursor
+ _cursor = -1
+ val line = rec(ix = 0, lineStartIx = 0, lineNr = 1)
+ _cursor = savedCursor
+ line
+ }
+ }
+
+ private val UTF8 = Charset.forName("UTF-8")
+
+ /**
+ * ParserInput that allows to create a ParserInput from any randomly accessible indexed byte storage.
+ */
+ abstract class IndexedBytesParserInput extends DefaultParserInput {
+ def length: Int
+ protected def byteAt(offset: Int): Byte
+
+ private val byteBuffer = ByteBuffer.allocate(4)
+ private val charBuffer = CharBuffer.allocate(2)
+ private val decoder = UTF8.newDecoder()
+ def nextChar() = {
+ _cursor += 1
+ if (_cursor < length) (byteAt(_cursor) & 0xFF).toChar else EOI
+ }
+ def nextUtf8Char() = {
+ @tailrec def decode(byte: Byte, remainingBytes: Int): Char = {
+ byteBuffer.put(byte)
+ if (remainingBytes > 0) {
+ _cursor += 1
+ if (_cursor < length) decode(byteAt(_cursor), remainingBytes - 1) else ErrorChar
+ } else {
+ byteBuffer.flip()
+ val coderResult = decoder.decode(byteBuffer, charBuffer, false)
+ charBuffer.flip()
+ val result = if (coderResult.isUnderflow & charBuffer.hasRemaining) charBuffer.get() else ErrorChar
+ byteBuffer.clear()
+ if (!charBuffer.hasRemaining) charBuffer.clear()
+ result
+ }
+ }
+
+ if (charBuffer.position() > 0) {
+ val result = charBuffer.get()
+ charBuffer.clear()
+ result
+ } else {
+ _cursor += 1
+ if (_cursor < length) {
+ val byte = byteAt(_cursor)
+ if (byte >= 0) byte.toChar // 7-Bit ASCII
+ else if ((byte & 0xE0) == 0xC0) decode(byte, 1) // 2-byte UTF-8 sequence
+ else if ((byte & 0xF0) == 0xE0) decode(byte, 2) // 3-byte UTF-8 sequence
+ else if ((byte & 0xF8) == 0xF0) decode(byte, 3) // 4-byte UTF-8 sequence
+ else ErrorChar
+ } else EOI
+ }
+ }
+ }
+
+ /**
+ * ParserInput reading directly off a byte array which is assumed to contain the UTF-8 encoded representation
+ * of the JSON input, without requiring a separate decoding step.
+ */
+ class ByteArrayBasedParserInput(bytes: Array[Byte]) extends IndexedBytesParserInput {
+ protected def byteAt(offset: Int): Byte = bytes(offset)
+ def length: Int = bytes.length
+
+ def sliceString(start: Int, end: Int) = new String(bytes, start, end - start, UTF8)
+ def sliceCharArray(start: Int, end: Int) =
+ UTF8.decode(ByteBuffer.wrap(java.util.Arrays.copyOfRange(bytes, start, end))).array()
+ }
+
+ class StringBasedParserInput(string: String) extends DefaultParserInput {
+ def nextChar(): Char = {
+ _cursor += 1
+ if (_cursor < string.length) string.charAt(_cursor) else EOI
+ }
+ def nextUtf8Char() = nextChar()
+ def length = string.length
+ def sliceString(start: Int, end: Int) = string.substring(start, end)
+ def sliceCharArray(start: Int, end: Int) = {
+ val chars = new Array[Char](end - start)
+ string.getChars(start, end, chars, 0)
+ chars
+ }
+ }
+
+ class CharArrayBasedParserInput(chars: Array[Char]) extends DefaultParserInput {
+ def nextChar(): Char = {
+ _cursor += 1
+ if (_cursor < chars.length) chars(_cursor) else EOI
+ }
+ def nextUtf8Char() = nextChar()
+ def length = chars.length
+ def sliceString(start: Int, end: Int) = new String(chars, start, end - start)
+ def sliceCharArray(start: Int, end: Int) = java.util.Arrays.copyOfRange(chars, start, end)
+ }
+} \ No newline at end of file
diff --git a/shared/src/main/scala/spray/json/JsonParserSettings.scala b/shared/src/main/scala/spray/json/JsonParserSettings.scala
new file mode 100644
index 0000000..8c76b0a
--- /dev/null
+++ b/shared/src/main/scala/spray/json/JsonParserSettings.scala
@@ -0,0 +1,56 @@
+package spray.json
+
+/**
+ * Allows to customize settings for the JSON parser.
+ *
+ * Use it like this:
+ *
+ * ```
+ * val customSettings =
+ * JsonParserSettings.default
+ * .withMaxDepth(100)
+ * .withMaxNumberCharacters(20)
+ *
+ * JsonParser(jsonString, customSettings)
+ * // or
+ * jsonString.parseJson(customSettings)
+ * ```
+ */
+sealed trait JsonParserSettings {
+ /**
+ * The JsonParser uses recursive decent parsing that keeps intermediate values on the stack. To prevent
+ * StackOverflowExceptions a limit is enforced on the depth of the parsed JSON structure.
+ *
+ * As a guideline we tested that one level of depth needs about 300 bytes of stack space.
+ *
+ * The default is a depth of 1000.
+ */
+ def maxDepth: Int
+
+ /**
+ * Returns a copy of this settings object with the `maxDepth` setting changed to the new value.
+ */
+ def withMaxDepth(newValue: Int): JsonParserSettings
+
+ /**
+ * The maximum number of characters the parser should support for numbers. This is restricted because creating
+ * `BigDecimal`s with high precision can be very slow (approx. quadratic runtime per amount of characters).
+ */
+ def maxNumberCharacters: Int
+
+ /**
+ * Returns a copy of this settings object with the `maxNumberCharacters` setting changed to the new value.
+ */
+ def withMaxNumberCharacters(newValue: Int): JsonParserSettings
+}
+object JsonParserSettings {
+ val default: JsonParserSettings = SettingsImpl()
+
+ private case class SettingsImpl(
+ maxDepth: Int = 1000,
+ maxNumberCharacters: Int = 100
+ ) extends JsonParserSettings {
+ override def withMaxDepth(newValue: Int): JsonParserSettings = copy(maxDepth = newValue)
+ override def withMaxNumberCharacters(newValue: Int): JsonParserSettings = copy(maxNumberCharacters = newValue)
+ }
+} \ No newline at end of file
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
diff --git a/shared/src/main/scala/spray/json/PrettyPrinter.scala b/shared/src/main/scala/spray/json/PrettyPrinter.scala
new file mode 100644
index 0000000..7526dab
--- /dev/null
+++ b/shared/src/main/scala/spray/json/PrettyPrinter.scala
@@ -0,0 +1,71 @@
+/*
+ * 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 java.lang.StringBuilder
+import annotation.tailrec
+
+/**
+ * A JsonPrinter that produces a nicely readable JSON source.
+ */
+trait PrettyPrinter extends JsonPrinter {
+ val Indent = 2
+
+ def print(x: JsValue, sb: StringBuilder): Unit = {
+ print(x, sb, 0)
+ }
+
+ protected def print(x: JsValue, sb: StringBuilder, indent: Int): Unit = {
+ x match {
+ case JsObject(x) => printObject(x, sb, indent)
+ case JsArray(x) => printArray(x, sb, indent)
+ case _ => printLeaf(x, sb)
+ }
+ }
+
+ protected def organiseMembers(members: Map[String, JsValue]): Seq[(String, JsValue)] = members.toSeq
+
+ protected def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int): Unit = {
+ sb.append("{\n")
+ printSeq(organiseMembers(members), sb.append(",\n")) { m =>
+ printIndent(sb, indent + Indent)
+ printString(m._1, sb)
+ sb.append(": ")
+ print(m._2, sb, indent + Indent)
+ }
+ sb.append('\n')
+ printIndent(sb, indent)
+ sb.append("}")
+ }
+
+ protected def printArray(elements: Seq[JsValue], sb: StringBuilder, indent: Int): Unit = {
+ sb.append('[')
+ printSeq(elements, sb.append(", "))(print(_, sb, indent))
+ sb.append(']')
+ }
+
+ protected def printIndent(sb: StringBuilder, indent: Int): Unit = {
+ @tailrec def rec(indent: Int): Unit =
+ if (indent > 0) {
+ sb.append(' ')
+ rec(indent - 1)
+ }
+ rec(indent)
+ }
+}
+
+object PrettyPrinter extends PrettyPrinter \ No newline at end of file
diff --git a/shared/src/main/scala/spray/json/SortedPrinter.scala b/shared/src/main/scala/spray/json/SortedPrinter.scala
new file mode 100644
index 0000000..28db225
--- /dev/null
+++ b/shared/src/main/scala/spray/json/SortedPrinter.scala
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 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
+
+trait SortedPrinter extends PrettyPrinter {
+
+ override protected def organiseMembers(members: Map[String, JsValue]): Seq[(String, JsValue)] =
+ members.toSeq.sortBy(_._1)
+}
+
+object SortedPrinter extends SortedPrinter
diff --git a/shared/src/main/scala/spray/json/StandardFormats.scala b/shared/src/main/scala/spray/json/StandardFormats.scala
new file mode 100644
index 0000000..e59de64
--- /dev/null
+++ b/shared/src/main/scala/spray/json/StandardFormats.scala
@@ -0,0 +1,120 @@
+/*
+ * Original implementation (C) 2009-2011 Debasish Ghosh
+ * Adapted and extended in 2011 by 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 scala.{Left, Right}
+
+/**
+ * Provides the JsonFormats for the non-collection standard types.
+ */
+trait StandardFormats {
+ this: AdditionalFormats =>
+
+ private[json] type JF[T] = JsonFormat[T] // simple alias for reduced verbosity
+
+ implicit def optionFormat[T :JF]: JF[Option[T]] = new OptionFormat[T]
+
+ class OptionFormat[T :JF] extends JF[Option[T]] {
+ def write(option: Option[T]) = option match {
+ case Some(x) => x.toJson
+ case None => JsNull
+ }
+ def read(value: JsValue) = value match {
+ case JsNull => None
+ case x => Some(x.convertTo[T])
+ }
+ // allows reading the JSON as a Some (useful in container formats)
+ def readSome(value: JsValue) = Some(value.convertTo[T])
+ }
+
+ implicit def eitherFormat[A :JF, B :JF] = new JF[Either[A, B]] {
+ def write(either: Either[A, B]) = either match {
+ case Right(a) => a.toJson
+ case Left(b) => b.toJson
+ }
+ def read(value: JsValue) = (value.convertTo(safeReader[A]), value.convertTo(safeReader[B])) match {
+ case (Right(a), _: Left[_, _]) => Left(a)
+ case (_: Left[_, _], Right(b)) => Right(b)
+ case (_: Right[_, _], _: Right[_, _]) => deserializationError("Ambiguous Either value: can be read as both, Left and Right, values")
+ case (Left(ea), Left(eb)) => deserializationError("Could not read Either value:\n" + ea + "---------- and ----------\n" + eb)
+ }
+ }
+
+ implicit def tuple1Format[A :JF] = new JF[Tuple1[A]] {
+ def write(t: Tuple1[A]) = t._1.toJson
+ def read(value: JsValue) = Tuple1(value.convertTo[A])
+ }
+
+ implicit def tuple2Format[A :JF, B :JF] = new RootJsonFormat[(A, B)] {
+ def write(t: (A, B)) = JsArray(t._1.toJson, t._2.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b)) => (a.convertTo[A], b.convertTo[B])
+ case x => deserializationError("Expected Tuple2 as JsArray, but got " + x)
+ }
+ }
+
+ implicit def tuple3Format[A :JF, B :JF, C :JF] = new RootJsonFormat[(A, B, C)] {
+ def write(t: (A, B, C)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b, c)) => (a.convertTo[A], b.convertTo[B], c.convertTo[C])
+ case x => deserializationError("Expected Tuple3 as JsArray, but got " + x)
+ }
+ }
+
+ implicit def tuple4Format[A :JF, B :JF, C :JF, D :JF] = new RootJsonFormat[(A, B, C, D)] {
+ def write(t: (A, B, C, D)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b, c, d)) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D])
+ case x => deserializationError("Expected Tuple4 as JsArray, but got " + x)
+ }
+ }
+
+ implicit def tuple5Format[A :JF, B :JF, C :JF, D :JF, E :JF] = {
+ new RootJsonFormat[(A, B, C, D, E)] {
+ def write(t: (A, B, C, D, E)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b, c, d, e)) =>
+ (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E])
+ case x => deserializationError("Expected Tuple5 as JsArray, but got " + x)
+ }
+ }
+ }
+
+ implicit def tuple6Format[A :JF, B :JF, C :JF, D :JF, E :JF, F: JF] = {
+ new RootJsonFormat[(A, B, C, D, E, F)] {
+ def write(t: (A, B, C, D, E, F)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson, t._6.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b, c, d, e, f)) =>
+ (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F])
+ case x => deserializationError("Expected Tuple6 as JsArray, but got " + x)
+ }
+ }
+ }
+
+ implicit def tuple7Format[A :JF, B :JF, C :JF, D :JF, E :JF, F: JF, G: JF] = {
+ new RootJsonFormat[(A, B, C, D, E, F, G)] {
+ def write(t: (A, B, C, D, E, F, G)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson, t._6.toJson, t._7.toJson)
+ def read(value: JsValue) = value match {
+ case JsArray(Seq(a, b, c, d, e, f, g)) =>
+ (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F], g.convertTo[G])
+ case x => deserializationError("Expected Tuple7 as JsArray, but got " + x)
+ }
+ }
+ }
+
+}
diff --git a/shared/src/main/scala/spray/json/package.scala b/shared/src/main/scala/spray/json/package.scala
new file mode 100644
index 0000000..a8a42f0
--- /dev/null
+++ b/shared/src/main/scala/spray/json/package.scala
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+package object json {
+
+ type JsField = (String, JsValue)
+
+ def deserializationError(msg: String, cause: Throwable = null, fieldNames: List[String] = Nil) = throw new DeserializationException(msg, cause, fieldNames)
+ def serializationError(msg: String) = throw new SerializationException(msg)
+
+ def jsonReader[T](implicit reader: JsonReader[T]) = reader
+ def jsonWriter[T](implicit writer: JsonWriter[T]) = writer
+
+ implicit def enrichAny[T](any: T) = new RichAny(any)
+ implicit def enrichString(string: String) = new RichString(string)
+
+ @deprecated("use enrichAny", "1.3.4")
+ def pimpAny[T](any: T) = new PimpedAny(any)
+ @deprecated("use enrichString", "1.3.4")
+ def pimpString(string: String) = new PimpedString(string)
+}
+
+package json {
+
+ case class DeserializationException(msg: String, cause: Throwable = null, fieldNames: List[String] = Nil) extends RuntimeException(msg, cause)
+ class SerializationException(msg: String) extends RuntimeException(msg)
+
+ private[json] class RichAny[T](any: T) {
+ def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any)
+ }
+
+ private[json] class RichString(string: String) {
+ @deprecated("deprecated in favor of parseJson", "1.2.6")
+ def asJson: JsValue = parseJson
+ def parseJson: JsValue = JsonParser(string)
+ def parseJson(settings: JsonParserSettings): JsValue = JsonParser(string, settings)
+ }
+
+ @deprecated("use RichAny", "1.3.4")
+ private[json] class PimpedAny[T](any: T) {
+ def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any)
+ }
+
+ @deprecated("use RichString", "1.3.4")
+ private[json] class PimpedString(string: String) {
+ @deprecated("deprecated in favor of parseJson", "1.2.6")
+ def asJson: JsValue = parseJson
+ def parseJson: JsValue = JsonParser(string)
+ }
+
+}