diff options
Diffstat (limited to 'src/main/scala/spray/json')
-rw-r--r-- | src/main/scala/spray/json/AdditionalFormats.scala | 104 | ||||
-rw-r--r-- | src/main/scala/spray/json/BasicFormats.scala | 129 | ||||
-rw-r--r-- | src/main/scala/spray/json/CollectionFormats.scala | 94 | ||||
-rw-r--r-- | src/main/scala/spray/json/CompactPrinter.scala | 51 | ||||
-rw-r--r-- | src/main/scala/spray/json/DefaultJsonProtocol.scala | 30 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsValue.scala | 114 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsonFormat.scala | 71 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsonParser.scala | 121 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsonPrinter.scala | 89 | ||||
-rw-r--r-- | src/main/scala/spray/json/PrettyPrinter.scala | 68 | ||||
-rw-r--r-- | src/main/scala/spray/json/ProductFormats.scala | 530 | ||||
-rw-r--r-- | src/main/scala/spray/json/StandardFormats.scala | 118 | ||||
-rw-r--r-- | src/main/scala/spray/json/package.scala | 45 |
13 files changed, 1564 insertions, 0 deletions
diff --git a/src/main/scala/spray/json/AdditionalFormats.scala b/src/main/scala/spray/json/AdditionalFormats.scala new file mode 100644 index 0000000..20fdc74 --- /dev/null +++ b/src/main/scala/spray/json/AdditionalFormats.scala @@ -0,0 +1,104 @@ +/* + * 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 + } + + /** + * 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) + } catch { + case e: Exception => Left(e) + } + } + } + +}
\ No newline at end of file diff --git a/src/main/scala/spray/json/BasicFormats.scala b/src/main/scala/spray/json/BasicFormats.scala new file mode 100644 index 0000000..55fe0bf --- /dev/null +++ b/src/main/scala/spray/json/BasicFormats.scala @@ -0,0 +1,129 @@ +/* + * 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) = JsNumber(x) + def read(value: JsValue) = value match { + case JsNumber(x) => x + case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x) + } + } + + implicit object BigIntJsonFormat extends JsonFormat[BigInt] { + def write(x: BigInt) = JsNumber(x) + def read(value: JsValue) = value match { + case JsNumber(x) => x.toBigInt + 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) {} + } + + 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) = 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/src/main/scala/spray/json/CollectionFormats.scala b/src/main/scala/spray/json/CollectionFormats.scala new file mode 100644 index 0000000..3b0d0c2 --- /dev/null +++ b/src/main/scala/spray/json/CollectionFormats.scala @@ -0,0 +1,94 @@ +/* + * 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 + +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)) + def read(value: JsValue) = value match { + case JsArray(elements) => elements.map(_.convertTo[T]) + case x => deserializationError("Expected List as JsArray, but got " + x) + } + } + + /** + * Supplies the JsonFormat for Arrays. + */ + implicit def arrayFormat[T :JsonFormat :ClassManifest] = new RootJsonFormat[Array[T]] { + def write(array: Array[T]) = JsArray(array.map(_.toJson).toList) + 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]) + } (collection.breakOut) + case x => deserializationError("Expected Map as JsObject, but got " + x) + } + } + + import collection.{immutable => imm} + + implicit def immIterableFormat[T :JsonFormat] = viaList[imm.Iterable[T], T](list => imm.Iterable(list :_*)) + implicit def immSeqFormat[T :JsonFormat] = viaList[imm.Seq[T], T](list => imm.Seq(list :_*)) + implicit def immIndexedSeqFormat[T :JsonFormat] = viaList[imm.IndexedSeq[T], T](list => imm.IndexedSeq(list :_*)) + implicit def immLinearSeqFormat[T :JsonFormat] = viaList[imm.LinearSeq[T], T](list => imm.LinearSeq(list :_*)) + implicit def immSetFormat[T :JsonFormat] = viaList[imm.Set[T], T](list => imm.Set(list :_*)) + implicit def vectorFormat[T :JsonFormat] = viaList[Vector[T], T](list => Vector(list :_*)) + + import collection._ + + implicit def iterableFormat[T :JsonFormat] = viaList[Iterable[T], T](list => Iterable(list :_*)) + implicit def seqFormat[T :JsonFormat] = viaList[Seq[T], T](list => Seq(list :_*)) + implicit def indexedSeqFormat[T :JsonFormat] = viaList[IndexedSeq[T], T](list => IndexedSeq(list :_*)) + implicit def linearSeqFormat[T :JsonFormat] = viaList[LinearSeq[T], T](list => LinearSeq(list :_*)) + implicit def setFormat[T :JsonFormat] = viaList[Set[T], T](list => Set(list :_*)) + + /** + * A JsonFormat construction helper that creates a JsonFormat for an Iterable type I from a builder function + * List => I. + */ + def viaList[I <: Iterable[T], T :JsonFormat](f: List[T] => I): RootJsonFormat[I] = new RootJsonFormat[I] { + def write(iterable: I) = JsArray(iterable.map(_.toJson).toList) + def read(value: JsValue) = value match { + case JsArray(elements) => f(elements.map(_.convertTo[T])) + case x => deserializationError("Expected Collection as JsArray, but got " + x) + } + } + +}
\ No newline at end of file diff --git a/src/main/scala/spray/json/CompactPrinter.scala b/src/main/scala/spray/json/CompactPrinter.scala new file mode 100644 index 0000000..b7d2856 --- /dev/null +++ b/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) { + x match { + case JsObject(x) => printObject(x, sb) + case JsArray(x) => printArray(x, sb) + case _ => printLeaf(x, sb) + } + } + + private def printObject(members: Map[String, JsValue], sb: StringBuilder) { + sb.append('{') + printSeq(members, sb.append(',')) { m => + printString(m._1, sb) + sb.append(':') + print(m._2, sb) + } + sb.append('}') + } + + private def printArray(elements: List[JsValue], sb: StringBuilder) { + sb.append('[') + printSeq(elements, sb.append(','))(print(_, sb)) + sb.append(']') + } +} + +object CompactPrinter extends CompactPrinter
\ No newline at end of file diff --git a/src/main/scala/spray/json/DefaultJsonProtocol.scala b/src/main/scala/spray/json/DefaultJsonProtocol.scala new file mode 100644 index 0000000..4c93184 --- /dev/null +++ b/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/src/main/scala/spray/json/JsValue.scala b/src/main/scala/spray/json/JsValue.scala new file mode 100644 index 0000000..8b25c98 --- /dev/null +++ b/src/main/scala/spray/json/JsValue.scala @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2009-2011 Mathias Doenitz + * Inspired by a similar implementation by Nathan Hamblen + * (https://github.com/n8han/Databinder-Dispatch) + * + * 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.ListMap + + +/** + * 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 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*): Seq[JsValue] = fieldNames.flatMap(fields.get) +} +object JsObject { + // we use a ListMap in order to preserve the field order + def apply(members: JsField*) = new JsObject(ListMap(members: _*)) + def apply(members: List[JsField]) = new JsObject(ListMap(members: _*)) +} + +/** + * A JSON array. + */ +case class JsArray(elements: List[JsValue]) extends JsValue +object JsArray { + def apply(elements: JsValue*) = new JsArray(elements.toList) +} + +/** + * A JSON string. + */ +case class JsString(value: String) extends JsValue + +object JsString { + def apply(value: Symbol) = new JsString(value.name) +} + +/** + * A JSON number. + */ +case class JsNumber(value: BigDecimal) extends JsValue +object JsNumber { + 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)) +} + +/** + * 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/src/main/scala/spray/json/JsonFormat.scala b/src/main/scala/spray/json/JsonFormat.scala new file mode 100644 index 0000000..c4915cc --- /dev/null +++ b/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/src/main/scala/spray/json/JsonParser.scala b/src/main/scala/spray/json/JsonParser.scala new file mode 100644 index 0000000..c01193b --- /dev/null +++ b/src/main/scala/spray/json/JsonParser.scala @@ -0,0 +1,121 @@ +/* + * 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 org.parboiled.scala._ +import org.parboiled.errors.{ErrorUtils, ParsingException} +import org.parboiled.Context +import java.lang.StringBuilder + +/** + * This JSON parser is the almost direct implementation of the JSON grammar + * presented at http://www.json.org as a parboiled PEG parser. + */ +object JsonParser extends Parser { + + // the root rule + lazy val Json = rule { WhiteSpace ~ Value ~ EOI } + + def JsonObject: Rule1[JsObject] = rule { + "{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_ :_*)) + } + + def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value ~~> ((_, _)) } + + def Value: Rule1[JsValue] = rule { + JsonString | JsonNumber | JsonObject | JsonArray | JsonTrue | JsonFalse | JsonNull + } + + def JsonString = rule { JsonStringUnwrapped ~~> (JsString(_)) } + + def JsonStringUnwrapped = rule { "\"" ~ Characters ~ "\" " ~~> (_.toString) } + + def JsonNumber = rule { group(Integer ~ optional(Frac) ~ optional(Exp)) ~> (JsNumber(_)) ~ WhiteSpace } + + def JsonArray = rule { "[ " ~ zeroOrMore(Value, separator = ", ") ~ "] " ~~> (JsArray(_)) } + + def Characters = rule { push(new StringBuilder) ~ zeroOrMore("\\" ~ EscapedChar | NormalChar) } + + def EscapedChar = rule ( + anyOf("\"\\/") ~:% withContext(appendToSb(_)(_)) + | "b" ~ appendToSb('\b') + | "f" ~ appendToSb('\f') + | "n" ~ appendToSb('\n') + | "r" ~ appendToSb('\r') + | "t" ~ appendToSb('\t') + | Unicode ~~% withContext((code, ctx) => appendToSb(code.asInstanceOf[Char])(ctx)) + ) + + def NormalChar = rule { !anyOf("\"\\") ~ ANY ~:% (withContext(appendToSb(_)(_))) } + + def Unicode = rule { "u" ~ group(HexDigit ~ HexDigit ~ HexDigit ~ HexDigit) ~> (java.lang.Integer.parseInt(_, 16)) } + + def Integer = rule { optional("-") ~ (("1" - "9") ~ Digits | Digit) } + + def Digits = rule { oneOrMore(Digit) } + + def Digit = rule { "0" - "9" } + + def HexDigit = rule { "0" - "9" | "a" - "f" | "A" - "Z" } + + def Frac = rule { "." ~ Digits } + + def Exp = rule { ignoreCase("e") ~ optional(anyOf("+-")) ~ Digits } + + def JsonTrue = rule { "true " ~ push(JsTrue) } + + def JsonFalse = rule { "false " ~ push(JsFalse) } + + def JsonNull = rule { "null " ~ push(JsNull) } + + def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf(" \n\r\t\f")) } + + // helper method for fast string building + // for maximum performance we use a somewhat unorthodox parsing technique that is a bit more verbose (and somewhat + // less readable) but reduces object allocations during the parsing run to a minimum: + // the Characters rules pushes a StringBuilder object onto the stack which is then directly fed with matched + // and unescaped characters in the sub rules (i.e. no string allocations and value stack operation required) + def appendToSb(c: Char): Context[Any] => Unit = { ctx => + ctx.getValueStack.peek.asInstanceOf[StringBuilder].append(c) + () + } + + /** + * We redefine the default string-to-rule conversion to also match trailing whitespace if the string ends with + * a blank, this keeps the rules free from most whitespace matching clutter + */ + override implicit def toRule(string: String) = { + if (string.endsWith(" ")) str(string.trim) ~ WhiteSpace + else str(string) + } + + /** + * The main parsing method. Uses a ReportingParseRunner (which only reports the first error) for simplicity. + */ + def apply(json: String): JsValue = apply(json.toCharArray) + + /** + * The main parsing method. Uses a ReportingParseRunner (which only reports the first error) for simplicity. + */ + def apply(json: Array[Char]): JsValue = { + val parsingResult = ReportingParseRunner(Json).run(json) + parsingResult.result.getOrElse { + throw new ParsingException("Invalid JSON source:\n" + ErrorUtils.printParseErrors(parsingResult)) + } + } + +}
\ No newline at end of file diff --git a/src/main/scala/spray/json/JsonPrinter.scala b/src/main/scala/spray/json/JsonPrinter.scala new file mode 100644 index 0000000..23529f3 --- /dev/null +++ b/src/main/scala/spray/json/JsonPrinter.scala @@ -0,0 +1,89 @@ +/* + * 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 + +/** + * 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: String): String = apply(x, Some(jsonpCallback)) + + def apply(x: JsValue, jsonpCallback: Option[String]): String = { + val sb = new StringBuilder + 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: StringBuilder) + + protected def printLeaf(x: JsValue, sb: StringBuilder) { + 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: StringBuilder) { + @tailrec + def printEscaped(s: String, ix: Int) { + if (ix < s.length) { + s.charAt(ix) match { + case '"' => sb.append("\\\"") + case '\\' => sb.append("\\\\") + case x if 0x20 <= x && x < 0x7F => sb.append(x) + 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 <= 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)) + } + printEscaped(s, ix + 1) + } + } + sb.append('"') + printEscaped(s, 0) + sb.append('"') + } + + protected def printSeq[A](iterable: Iterable[A], printSeparator: => Unit)(f: A => Unit) { + var first = true + iterable.foreach { a => + if (first) first = false else printSeparator + f(a) + } + } +}
\ No newline at end of file diff --git a/src/main/scala/spray/json/PrettyPrinter.scala b/src/main/scala/spray/json/PrettyPrinter.scala new file mode 100644 index 0000000..0daaf71 --- /dev/null +++ b/src/main/scala/spray/json/PrettyPrinter.scala @@ -0,0 +1,68 @@ +/* + * 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) { + print(x, sb, 0) + } + + private def print(x: JsValue, sb: StringBuilder, indent: Int) { + x match { + case JsObject(x) => printObject(x, sb, indent) + case JsArray(x) => printArray(x, sb, indent) + case _ => printLeaf(x, sb) + } + } + + private def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) { + sb.append("{\n") + printSeq(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("}") + } + + private def printArray(elements: List[JsValue], sb: StringBuilder, indent: Int) { + sb.append('[') + printSeq(elements, sb.append(", "))(print(_, sb, indent)) + sb.append(']') + } + + @tailrec + private def printIndent(sb: StringBuilder, indent: Int) { + if (indent > 0) { + sb.append(' ') + printIndent(sb, indent - 1) + } + } +} + +object PrettyPrinter extends PrettyPrinter
\ No newline at end of file diff --git a/src/main/scala/spray/json/ProductFormats.scala b/src/main/scala/spray/json/ProductFormats.scala new file mode 100644 index 0000000..5504fbd --- /dev/null +++ b/src/main/scala/spray/json/ProductFormats.scala @@ -0,0 +1,530 @@ +/* + * 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 + +/** + * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait + * (especially case classes) + */ +trait ProductFormats { + this: StandardFormats => + + def jsonFormat1[A :JF, T <: Product :ClassManifest](construct: A => T): RootJsonFormat[T] = { + val Array(a) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a) + } + def jsonFormat[A :JF, T <: Product](construct: A => T, a: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0) + ) + def read(value: JsValue) = construct( + fromField[A](value, a) + ) + } + + def jsonFormat2[A :JF, B :JF, T <: Product :ClassManifest](construct: (A, B) => T): RootJsonFormat[T] = { + val Array(a, b) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b) + } + def jsonFormat[A :JF, B :JF, T <: Product](construct: (A, B) => T, a: String, b: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1)) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b) + ) + } + + def jsonFormat3[A :JF, B :JF, C :JF, T <: Product :ClassManifest](construct: (A, B, C) => T): RootJsonFormat[T] = { + val Array(a, b, c) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c) + } + def jsonFormat[A :JF, B :JF, C :JF, T <: Product](construct: (A, B, C) => T, + a: String, b: String, c: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c) + ) + } + + def jsonFormat4[A :JF, B :JF, C :JF, D :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D) => T): RootJsonFormat[T] = { + val Array(a, b, c, d) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, T <: Product](construct: (A, B, C, D) => T, + a: String, b: String, c: String, d: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3)))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d) + ) + } + + def jsonFormat5[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product](construct: (A, B, C, D, E) => T, + a: String, b: String, c: String, d: String, e: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e) + ) + } + + def jsonFormat6[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product](construct: (A, B, C, D, E, F) => T, + a: String, b: String, c: String, d: String, e: String, f: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5)))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f) + ) + } + + def jsonFormat7[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product](construct: (A, B, C, D, E, F, G) => T, + a: String, b: String, c: String, d: String, e: String, f: String, g: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g) + ) + } + + def jsonFormat8[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H) => T, + a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7)))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h) + ) + } + + def jsonFormat9[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I) => T, a: String, b: String, c: String, d: String, e: String, f: String, + g: String, h: String, i: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i) + ) + } + + def jsonFormat10[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J) => T, a: String, b: String, c: String, d: String, e: String, + f: String, g: String, h: String, i: String, j: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9)))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j) + ) + } + + def jsonFormat11[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J, K) => T, a: String, b: String, c: String, d: String, e: String, + f: String, g: String, h: String, i: String, j: String, k: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9, + productElement2Field[K](k, p, 10))))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j), + fromField[K](value, k) + ) + } + + def jsonFormat12[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T, a: String, b: String, c: String, d: String, e: String, + f: String, g: String, h: String, i: String, j: String, k: String, l: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9, + productElement2Field[K](k, p, 10, + productElement2Field[L](l, p, 11)))))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j), + fromField[K](value, k), + fromField[L](value, l) + ) + } + + def jsonFormat13[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T, a: String, b: String, c: String, d: String, e: String, + f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9, + productElement2Field[K](k, p, 10, + productElement2Field[L](l, p, 11, + productElement2Field[M](m, p, 12))))))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j), + fromField[K](value, k), + fromField[L](value, l), + fromField[M](value, m) + ) + } + + def jsonFormat14[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T, a: String, b: String, c: String, d: String, + e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, + n: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9, + productElement2Field[K](k, p, 10, + productElement2Field[L](l, p, 11, + productElement2Field[M](m, p, 12, + productElement2Field[N](n, p, 13)))))))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j), + fromField[K](value, k), + fromField[L](value, l), + fromField[M](value, m), + fromField[N](value, n) + ) + } + + def jsonFormat15[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, O :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) + } + def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, O :JF, T <: Product] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T, a: String, b: String, c: String, d: String, + e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, n: String, + o: String): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = JsObject( + productElement2Field[A](a, p, 0, + productElement2Field[B](b, p, 1, + productElement2Field[C](c, p, 2, + productElement2Field[D](d, p, 3, + productElement2Field[E](e, p, 4, + productElement2Field[F](f, p, 5, + productElement2Field[G](g, p, 6, + productElement2Field[H](h, p, 7, + productElement2Field[I](i, p, 8, + productElement2Field[J](j, p, 9, + productElement2Field[K](k, p, 10, + productElement2Field[L](l, p, 11, + productElement2Field[M](m, p, 12, + productElement2Field[N](n, p, 13, + productElement2Field[O](o, p, 14))))))))))))))) + ) + def read(value: JsValue) = construct( + fromField[A](value, a), + fromField[B](value, b), + fromField[C](value, c), + fromField[D](value, d), + fromField[E](value, e), + fromField[F](value, f), + fromField[G](value, g), + fromField[H](value, h), + fromField[I](value, i), + fromField[J](value, j), + fromField[K](value, k), + fromField[L](value, l), + fromField[M](value, m), + fromField[N](value, n), + fromField[O](value, o) + ) + } + + // helpers + + protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField] = Nil) + (implicit writer: JsonWriter[T]): List[JsField] = { + val value = p.productElement(ix).asInstanceOf[T] + writer match { + case _: OptionFormat[_] if (value == None) => rest + case _ => (fieldName, writer.write(value)) :: rest + } + } + + private def fromField[T](value: JsValue, fieldName: String)(implicit reader: JsonReader[T]) = { + value match { + case x: JsObject => + var fieldFound = false + try { + val fieldValue = x.fields(fieldName) + fieldFound = true + reader.read(fieldValue) + } + catch { + case e: NoSuchElementException if !fieldFound => + if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T] + else deserializationError("Object is missing required member '" + fieldName + "'", e) + } + case _ => deserializationError("Object expected") + } + } + + protected def extractFieldNames(classManifest: ClassManifest[_]): Array[String] = { + val clazz = classManifest.erasure + try { + // copy methods have the form copy$default$N(), we need to sort them in order, but must account for the fact + // that lexical sorting of ...8(), ...9(), ...10() is not correct, so we extract N and sort by N.toInt + val copyDefaultMethods = clazz.getMethods.filter(_.getName.startsWith("copy$default$")).sortBy( + _.getName.drop("copy$default$".length).takeWhile(_ != '(').toInt) + val fields = clazz.getDeclaredFields.filterNot(_.getName.startsWith("$")) + if (copyDefaultMethods.length != fields.length) + sys.error("Case class " + clazz.getName + " declares additional fields") + if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType }) + sys.error("Cannot determine field order of case class " + clazz.getName) + fields.map(_.getName) + } catch { + case ex => throw new RuntimeException("Cannot automatically determine case class field names and order " + + "for '" + clazz.getName + "', please use the 'jsonFormat' overload with explicit field name specification", ex) + } + } +} + +/** + * This trait supplies an alternative rendering mode for optional case class members. + * Normally optional members that are undefined (`None`) are not rendered at all. + * By mixing in this trait into your custom JsonProtocol you can enforce the rendering of undefined members as `null`. + * (Note that this only affect JSON writing, spray-json will always read missing optional members as well as `null` + * optional members as `None`.) + */ +trait NullOptions extends ProductFormats { + this: StandardFormats => + + override protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField]) + (implicit writer: JsonWriter[T]) = { + val value = p.productElement(ix).asInstanceOf[T] + (fieldName, writer.write(value)) :: rest + } +} diff --git a/src/main/scala/spray/json/StandardFormats.scala b/src/main/scala/spray/json/StandardFormats.scala new file mode 100644 index 0000000..6a9712a --- /dev/null +++ b/src/main/scala/spray/json/StandardFormats.scala @@ -0,0 +1,118 @@ +/* + * 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] = 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]) + } + } + + 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(a :: b :: Nil) => (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(a :: b :: c :: Nil) => (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(a :: b :: c :: d :: Nil) => (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(a :: b :: c :: d :: e :: Nil) => + (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(a :: b :: c :: d :: e :: f :: Nil) => + (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._6.toJson) + def read(value: JsValue) = value match { + case JsArray(a :: b :: c :: d :: e :: f :: g :: Nil) => + (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) + } + } + } + +}
\ No newline at end of file diff --git a/src/main/scala/spray/json/package.scala b/src/main/scala/spray/json/package.scala new file mode 100644 index 0000000..5bf5714 --- /dev/null +++ b/src/main/scala/spray/json/package.scala @@ -0,0 +1,45 @@ +/* + * 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) = throw new DeserializationException(msg, cause) + 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 pimpAny[T](any: T) = new PimpedAny(any) + implicit def pimpString(string: String) = new PimpedString(string) +} + +package json { + + class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) + class SerializationException(msg: String) extends RuntimeException(msg) + + private[json] class PimpedAny[T](any: T) { + def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) + } + + private[json] class PimpedString(string: String) { + def asJson: JsValue = JsonParser(string) + } +}
\ No newline at end of file |