From c40e5aa808f39d42f796068da7d1b31936b0ec3c Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:21:06 +0100 Subject: [PATCH 01/14] Added alternative product formats --- README.markdown | 19 + src/main/scala/spray/json/ProductFormat.scala | 251 ++++++ .../scala/spray/json/ProductFormats.scala | 4 +- .../scala/spray/json/ProductFormats2.scala | 718 ++++++++++++++++++ 4 files changed, 990 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/spray/json/ProductFormat.scala create mode 100644 src/main/scala/spray/json/ProductFormats2.scala diff --git a/README.markdown b/README.markdown index 51806172..275be70f 100644 --- a/README.markdown +++ b/README.markdown @@ -154,6 +154,25 @@ object MyJsonProtocol extends DefaultJsonProtocol { } ``` +### Alternative format for Case Classes + +As of version 1.2.6, there is an alternative method to create formats for case classes. Instead of `jsonFormatN` use +`formatN`. Apart from this, the new formats are a drop-in replacement for the original ones. + +```scala +case class Color(name: String, red: Int, green: Int, blue: Int) + +object MyJsonProtocol extends DefaultJsonProtocol { + implicit val colorFormat = format4(Color) +} + +The new method has several advantages: + +* Use default values defined on case-classes when fields are missing from json +* Omit empty arrays and objects when serializing fields with default values +* Allows property renaming +* Override formats per field +* Allow advanced customizations #### NullOptions diff --git a/src/main/scala/spray/json/ProductFormat.scala b/src/main/scala/spray/json/ProductFormat.scala new file mode 100644 index 00000000..bc160f6e --- /dev/null +++ b/src/main/scala/spray/json/ProductFormat.scala @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2011 Ruud Diterwich, BusyMachines + * + * 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.reflect.{ParameterizedType, Modifier} +import scala.reflect.ClassTag +import scala.reflect.classTag +import scala.collection.mutable +import scala.language.existentials + +/** + * A field format provides a more generic way to translate a field value to json and visa versa. + * It allows for field renaming, multi-field mappings etc. + */ +trait ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) : List[JsField] + def read(field: ProductField, obj: JsObject) : F + def withJsonName(jsonName: String) = this + def withJsonFormat(format: JsonFormat[F]) = this + def withDefault(default: Option[() => Any]) = this +} + +/** + * Implicitly converts a JsonFormat to a ProductFieldFormat. This implicit + * makes the new product formats drop-in compatible with the original ones. + */ +object ProductFieldFormat { + implicit def of[F](implicit jsonFormat: JsonFormat[F]) = + DefaultProductFieldFormat[F](None, None, jsonFormat) +} + +/** + * Do not serialize the field to and from json. Should only be used when the + * field has a default value. Instead of using this class directly, one can also + * use ProductFormat#excludeField. + */ +class NullProductFieldFormat[F] extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = rest + def read(field: ProductField, obj: JsObject) : F = + field.default.getOrElse(throw new IllegalStateException(s"Field ${field.name} should have a default value")).apply().asInstanceOf[F] +} + +object NullProductFieldFormat extends NullProductFieldFormat[Any] + +/** + * A normal field format that will serialize a product field one to one to a json field. + */ +case class DefaultProductFieldFormat[F](jsonName: Option[String], default: Option[() => Any], jsonFormat: JsonFormat[F]) extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = + if (field.isOption && value == None) rest + else if (field.isSeq && field.default.isDefined && value == Seq.empty) rest + else if (field.isMap && field.default.isDefined && value == Map.empty) rest + else jsonName.getOrElse(field.name) -> jsonFormat.write(value) :: rest + + def read(field: ProductField, obj: JsObject) = + obj.fields.get(jsonName.getOrElse(field.name)) match { + case Some(value) => jsonFormat.read(value) + case None => default.orElse(field.default) match { + case Some(defarg) => defarg().asInstanceOf[F] + case None => + if (field.isOption) None.asInstanceOf[F] + else deserializationError("Object is missing required member '" + field.name + "'") + } + } + override def withJsonName(jsonName: String) = this.copy(jsonName = Some(jsonName)) + override def withDefault(default: Option[() => Any]) = this.copy(default = default) + override def withJsonFormat(format: JsonFormat[F]) = this.copy(jsonFormat = format) +} + +class EmbeddedProductFieldFormat[F](originalFormat: ProductFieldFormat[F]) extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = + originalFormat.write(field, value, Nil).map(_._2).collect{case JsObject(fields) => fields}.flatten ++ rest + def read(field: ProductField, obj: JsObject) : F = + originalFormat match { + case DefaultProductFieldFormat(jsonName, default, jsonFormat) => + jsonFormat.read(obj) + case _ => throw new Exception(s"Can't embed a non-default product field ${field.name}") + } +} + +/** + * Represents a case-class field. + */ +case class ProductField( + name: String, + default: Option[() => Any] = None, + isOption: Boolean = false, + isSeq: Boolean = false, + isMap: Boolean = false, + fieldType: Class[_], + genericParameterTypes: Array[Class[_]], + format: ProductFieldFormat[_]) + +/** + * Base class for product formats, mainly used as json formats for case-classes. + */ +abstract class ProductFormat[P :ClassTag] extends RootJsonFormat[P] { outer => + + /** + * Discovered fields of the product class. + */ + val fields: Array[ProductField] + protected val delegate: ProductFormat[P] + + def write(p: P) = delegate.write(fields, p) + def read(value: JsValue) = delegate.read(fields, value) + protected def write(fields: Seq[ProductField], p: P) : JsValue + protected def read(fields: Seq[ProductField], value: JsValue) : P + + /** + * Returns a new format that overrides the json name for given fields (identified by name). + */ + def withJsonNames(jsonNames: (String, String)*) = decorate( + fields.map(f => jsonNames.find(_._1 == f.name).map(s => f.copy(format = f.format.withJsonName(s._2))).getOrElse(f))) + + /** + * Returns a new format that overrides the json formats for given fields (identified by name). + */ + def withJsonFormats(jsonFormats: (String, JsonFormat[_])*) = decorate( + fields.map(f => jsonFormats.find(_._1 == f.name).map(s => f.copy(format = f.format.asInstanceOf[ProductFieldFormat[Any]].withJsonFormat(s._2.asInstanceOf[JsonFormat[Any]]))).getOrElse(f))) + + /** + * Returns a new format that overrides the default value for given fields (identified by name). + */ + def withDefaults(defaults: (String, () => Any)*) = decorate( + fields.map(f => f.copy(default = defaults.find(_._1 == f.name).map(_._2).orElse(f.default)))) + + /** + * Returns a new format that overrides the field formats for given fields (identified by name). + */ + def withFieldFormats(formats: (String, ProductFieldFormat[_])*) = decorate( + fields.map(f => f.copy(format = formats.find(_._1 == f.name).map(_._2).getOrElse(f.format)))) + + /** + * Returns a new format with transformed product fields. + */ + def mapFields(cp: ProductField => ProductField) = decorate( + fields.map(cp)) + + /** + * Returns a new format that excludes given fields when serializing to json. + */ + def excludeFields(fields: String*) = + withFieldFormats(fields.map(_ -> NullProductFieldFormat):_*) + + /** + * Returns a new format that embeds nested fields in the root json object. + */ + def embed(fields: String*) = mapFields { + case field if !fields.contains(field.name) => field + case field => field.copy(format = new EmbeddedProductFieldFormat[Any](field.format.asInstanceOf[ProductFieldFormat[Any]])) + } + + private def decorate(_fields: Array[ProductField]) = new ProductFormat[P] { + val fields = _fields + val delegate = outer.delegate + def write(fields: Seq[ProductField], p: P) : JsValue = throw new IllegalStateException + def read(fields: Seq[ProductField], value: JsValue) : P = throw new IllegalStateException + } +} + +abstract private[spray] class ProductFormatImpl[P <: Product :ClassTag, F0 :ProductFieldFormat, F1 :ProductFieldFormat, F2 :ProductFieldFormat,F3 :ProductFieldFormat,F4 :ProductFieldFormat,F5 :ProductFieldFormat,F6 :ProductFieldFormat,F7 :ProductFieldFormat,F8 :ProductFieldFormat,F9 :ProductFieldFormat,F10 :ProductFieldFormat, F11 :ProductFieldFormat, F12 :ProductFieldFormat, F13 :ProductFieldFormat, F14 :ProductFieldFormat, F15 :ProductFieldFormat, F16 :ProductFieldFormat, F17 :ProductFieldFormat, F18 :ProductFieldFormat, F19 :ProductFieldFormat, F20 :ProductFieldFormat, F21 :ProductFieldFormat] extends ProductFormat[P] { + + protected val delegate = this + + protected def write[F :ProductFieldFormat](field: ProductField, p: P, fieldIndex: Int, rest: List[JsField]): List[JsField] = + field.format.asInstanceOf[ProductFieldFormat[Any]].write(field, p.productElement(fieldIndex), rest) + + protected def read[F :ProductFieldFormat](field: ProductField, value: JsValue) : F = { + value match { + case obj: JsObject => field.format.read(field, obj).asInstanceOf[F] + case _ => deserializationError("Object expected") + } + } + + protected def jsObject(fields: Iterable[JsField]): JsObject = { + JsObject(fields.toMap match { + case map if map.size == fields.size => map + case map => + val builder = mutable.Map[String, JsValue]() + for ((name, value) <- fields) { + (builder.get(name), value) match { + case (Some(JsObject(oldFields)), JsObject(fields)) => + builder += (name -> jsObject(oldFields ++ fields)) + case _ => builder += (name -> value) + } + } + builder.toMap + }) + } + + private def fmt[F](implicit f: ProductFieldFormat[F]) = f.asInstanceOf[ProductFieldFormat[Any]] + + val fields = { + val formats = Array(fmt[F0], fmt[F1], + fmt[F2], fmt[F3], fmt[F4], fmt[F5], fmt[F6], + fmt[F7], fmt[F8], fmt[F9], fmt[F10], fmt[F11], + fmt[F12], fmt[F13], fmt[F14], fmt[F15], fmt[F16], + fmt[F17], fmt[F18], fmt[F19], fmt[F20], fmt[F21]) + + val runtimeClass = classTag[P].runtimeClass + try { + // Need companion class for default arguments. + lazy val companionClass = Class.forName(runtimeClass.getName + "$") + lazy val moduleField = + try { companionClass.getField("MODULE$") } + catch { case e : Throwable => throw new RuntimeException("Can't deserialize default arguments of nested case classes", e) } + lazy val companionObj = moduleField.get(null) + // 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 = runtimeClass.getMethods.filter(_.getName.startsWith("copy$default$")).sortBy( + _.getName.drop("copy$default$".length).takeWhile(_ != '(').toInt) + val fields = runtimeClass.getDeclaredFields.filterNot(f => f.getName.startsWith("$") || Modifier.isTransient(f.getModifiers) || Modifier.isStatic(f.getModifiers)) + if (copyDefaultMethods.length != fields.length) + sys.error("Case class " + runtimeClass.getName + " declares additional fields") + val applyDefaultMethods = copyDefaultMethods.map { method => + try { + val defmeth = companionClass.getMethod("apply" + method.getName.drop("copy".size)) + Some(() => defmeth.invoke(companionObj))} + catch { case e : Throwable => None } + } + if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType }) + sys.error("Cannot determine field order of case class " + runtimeClass.getName) + fields.zip(applyDefaultMethods).zipWithIndex.map { case ((f, m), index) => + val typeArgs: Array[Class[_]] = f.getGenericType match { + case pType: ParameterizedType => pType.getActualTypeArguments.collect { + case argClass: Class[_] => argClass + } + case _ => Array.empty + } + ProductField(f.getName, default = m, classOf[Option[_]].isAssignableFrom(f.getType), classOf[Seq[_]].isAssignableFrom(f.getType), classOf[Map[_, _]].isAssignableFrom(f.getType), f.getType, typeArgs, format = formats(index)) + } + } catch { + case ex : Throwable => throw new RuntimeException("Cannot automatically determine case class field names and order " + + "for '" + runtimeClass.getName + "', please use the 'jsonFormat' overload with explicit field name specification", ex) + } + } +} diff --git a/src/main/scala/spray/json/ProductFormats.scala b/src/main/scala/spray/json/ProductFormats.scala index ad572213..d45c7fab 100644 --- a/src/main/scala/spray/json/ProductFormats.scala +++ b/src/main/scala/spray/json/ProductFormats.scala @@ -22,7 +22,7 @@ import java.lang.reflect.Modifier * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait * (especially case classes) */ -trait ProductFormats extends ProductFormatsInstances { +trait ProductFormats extends ProductFormatsInstances with ProductFormats2 { this: StandardFormats => // helpers @@ -70,7 +70,7 @@ trait ProductFormats extends ProductFormatsInstances { 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 " + + case ex: Throwable => 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) } } diff --git a/src/main/scala/spray/json/ProductFormats2.scala b/src/main/scala/spray/json/ProductFormats2.scala new file mode 100644 index 00000000..ba113f90 --- /dev/null +++ b/src/main/scala/spray/json/ProductFormats2.scala @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2011 Ruud Diterwich, BusyMachines + * + * 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 ProductFormats2 { + type Fields = Seq[ProductField] + type JFmt[F] = ProductFieldFormat[F] + private[spray] trait FNull + private[spray] implicit val fNull = new NullProductFieldFormat[FNull] + + def format1[P <: Product :ClassTag, F0 :JFmt](construct: (F0) => P) = new ProductFormatImpl[P, F0, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + Nil) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value) + ) + } + + def format2[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt](construct: (F0, F1) => P) = new ProductFormatImpl[P, F0, F1, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + Nil)) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value) + ) + } + + def format3[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt](construct: (F0, F1, F2) => P) = new ProductFormatImpl[P, F0, F1, F2, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + Nil))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value) + ) + } + + def format4[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt](construct: (F0, F1, F2, F3) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + Nil)))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value) + ) + } + + def format5[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt](construct: (F0, F1, F2, F3, F4) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + Nil))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value) + ) + } + + def format6[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt](construct: (F0, F1, F2, F3, F4, F5) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + Nil)))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value) + ) + } + + def format7[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + Nil))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value) + ) + } + + def format8[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + Nil)))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value) + ) + } + + def format9[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + Nil))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value) + ) + } + + def format10[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt, F9 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + Nil)))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value) + ) + } + + def format11[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt, F9 :JFmt, F10 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + Nil))))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value) + ) + } + + def format12[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt,F11 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + Nil)))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value) + ) + } + + def format13[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + Nil))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value) + ) + } + + def format14[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + Nil)))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value) + ) + } + + def format15[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + Nil))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value) + ) + } + + def format16[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + Nil)))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value) + ) + } + + def format17[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + Nil))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value) + ) + } + + def format18[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + Nil)))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value) + ) + } + + def format19[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + Nil))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value) + ) + } + + def format20[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + Nil)))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value) + ) + } + + def format21[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt, F20 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + write[F20](fields(20), p, 20, + Nil))))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value), + read[F20](fields(20), value) + ) + } + + def format22[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt, F20 :JFmt, F21 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + write[F20](fields(20), p, 20, + write[F21](fields(21), p, 21, + Nil)))))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value), + read[F20](fields(20), value), + read[F21](fields(21), value) + ) + } +} From f985f24be24e4a7bf1cf67572ffa7c221ca6a6ed Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:31:14 +0100 Subject: [PATCH 02/14] Added tests for alternative product format --- .../spray/json/ProductFormats2Spec.scala | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/test/scala/spray/json/ProductFormats2Spec.scala diff --git a/src/test/scala/spray/json/ProductFormats2Spec.scala b/src/test/scala/spray/json/ProductFormats2Spec.scala new file mode 100644 index 00000000..8405d4d6 --- /dev/null +++ b/src/test/scala/spray/json/ProductFormats2Spec.scala @@ -0,0 +1,121 @@ +package spray.json + +import org.specs2.mutable.Specification + +object ProductFormatTestsFixture { + + case class Box(size: Double, things: List[Thing]) + case class Thing(name: String, properties: Map[String, String] = Map.empty) +} + +class ProductFormats2Spec extends Specification { + import ProductFormatTestsFixture._ + import DefaultJsonProtocol._ + + { + implicit val thingFormat = format2(Thing) + implicit val boxFormat = format2(Box) + + "A case class" should { + "be correctly serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"name" : "egg", "properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + } + + "Default values" should { + "be correctly deserialized from json" in { + val json = """{"name" : "egg"}""" + val thing = Thing("egg") + assert(json.asJson.convertTo[Thing] === thing) + assert(json.asJson.convertTo[Thing].properties == Map.empty) + } + } + + "Empty collections with a default value" should { + "not be serialized to json" in { + val thing = Thing("egg") + val json = """{"name" : "egg"}""" + assert(thing.toJson === json.asJson) + } + } + + "Empty collections without a default value" should { + "be serialized to json" in { + val thing = Box(10, Nil) + val json = """{"size" : 10, "things" : [] }""" + assert(thing.toJson === json.asJson) + } + } + } + + { + implicit val thingFormat = format2(Thing).withJsonNames("name" -> "description") + implicit val boxFormat = format2(Box) + + "A renamed field" should { + "be correctly serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"description" : "egg", "properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + "be correctly serialized in a nested object" in { + val box = Box(10, Thing("egg", Map("color" -> "red")) :: Nil) + val json = """{"size":10, "things":[{"description" : "egg", "properties" : {"color" : "red"}}]}""" + assert(box.toJson === json.asJson) + assert(json.asJson.convertTo[Box] === box) + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("properties") + + "An excluded field" should { + "not be serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"name":"egg"}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === Thing("egg")) + } + "not be deserialized from json" in { + val json = """{"name":"egg", "properties" : {"color" : "red"}}""" + val thing = Thing("egg") + assert(json.asJson.convertTo[Thing] === thing) + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("name") + + "An excluded field without a default" should { + "not be deserialized from json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + json.asJson.convertTo[Thing] must throwA[IllegalStateException] + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("name") + .withDefaults("name" -> (() => "ball")) + + "An excluded field with an explicit default" should { + "not be serialized to json" in { + val thing = Thing("ball", Map("color" -> "red")) + val json = """{"properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + } + } +} From e27307166c80e817b4425b645a990b7bd087947c Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:32:52 +0100 Subject: [PATCH 03/14] typo in readme --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 275be70f..e41e6379 100644 --- a/README.markdown +++ b/README.markdown @@ -165,6 +165,7 @@ case class Color(name: String, red: Int, green: Int, blue: Int) object MyJsonProtocol extends DefaultJsonProtocol { implicit val colorFormat = format4(Color) } +``` The new method has several advantages: From 6934de82c50bfb43b29ce9f8af7d5f0547f97ed8 Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Tue, 11 Mar 2014 22:03:59 +0100 Subject: [PATCH 04/14] preserve field order in product formats --- src/main/scala/spray/json/ProductFormat.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/spray/json/ProductFormat.scala b/src/main/scala/spray/json/ProductFormat.scala index bc160f6e..51beeba1 100644 --- a/src/main/scala/spray/json/ProductFormat.scala +++ b/src/main/scala/spray/json/ProductFormat.scala @@ -19,6 +19,7 @@ import java.lang.reflect.{ParameterizedType, Modifier} import scala.reflect.ClassTag import scala.reflect.classTag import scala.collection.mutable +import scala.collection.immutable.ListMap import scala.language.existentials /** @@ -186,15 +187,15 @@ abstract private[spray] class ProductFormatImpl[P <: Product :ClassTag, F0 :Prod } } - protected def jsObject(fields: Iterable[JsField]): JsObject = { - JsObject(fields.toMap match { + protected def jsObject(fields: Seq[JsField]): JsObject = { + JsObject(ListMap(fields:_*) match { case map if map.size == fields.size => map case map => - val builder = mutable.Map[String, JsValue]() + val builder = mutable.ListMap[String, JsValue]() for ((name, value) <- fields) { (builder.get(name), value) match { - case (Some(JsObject(oldFields)), JsObject(fields)) => - builder += (name -> jsObject(oldFields ++ fields)) + case (Some(JsObject(oldFields)), JsObject(newFields)) => + builder += (name -> jsObject((oldFields ++ newFields).toSeq)) case _ => builder += (name -> value) } } From 12d83eb4b4b7c749cafe34f4a557c5ce153e4cdf Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 15 Sep 2014 16:48:00 -0400 Subject: [PATCH 05/14] Add jsonFormat0 for fieldless case classes Closes #41 --- .../scala/spray/json/ProductFormats.scala | 9 +++++++++ .../scala/spray/json/ProductFormatsSpec.scala | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/scala/spray/json/ProductFormats.scala b/src/main/scala/spray/json/ProductFormats.scala index 971c7a63..5f5a9f4e 100644 --- a/src/main/scala/spray/json/ProductFormats.scala +++ b/src/main/scala/spray/json/ProductFormats.scala @@ -26,6 +26,15 @@ import scala.util.control.NonFatal trait ProductFormats extends ProductFormatsInstances { this: StandardFormats => + def jsonFormat0[T](construct: () => T): RootJsonFormat[T] = + new RootJsonFormat[T] { + def write(p: T) = JsObject() + def read(value: JsValue) = value match { + case JsObject(_) => construct() + case _ => throw new DeserializationException("Object expected") + } + } + // helpers protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField] = Nil) diff --git a/src/test/scala/spray/json/ProductFormatsSpec.scala b/src/test/scala/spray/json/ProductFormatsSpec.scala index 8e3390d9..c4bb4898 100644 --- a/src/test/scala/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/spray/json/ProductFormatsSpec.scala @@ -20,6 +20,7 @@ import org.specs2.mutable._ class ProductFormatsSpec extends Specification { + case class Test0() case class Test2(a: Int, b: Option[Double]) case class Test3[A, B](as: List[A], bs: List[B]) case class TestTransient(a: Int, b: Option[Double]) { @@ -30,6 +31,7 @@ class ProductFormatsSpec extends Specification { trait TestProtocol { this: DefaultJsonProtocol => + implicit val test0Format = jsonFormat0(Test0) implicit val test2Format = jsonFormat2(Test2) implicit def test3Format[A: JsonFormat, B: JsonFormat] = jsonFormat2(Test3.apply[A, B]) implicit def testTransientFormat = jsonFormat2(TestTransient) @@ -162,4 +164,21 @@ class ProductFormatsSpec extends Specification { } } + "A JsonFormat created with `jsonFormat`, for a case class with 0 elements," should { + import TestProtocol1._ + val obj = Test0() + val json = JsObject() + "convert to a respective JsObject" in { + obj.toJson mustEqual json + } + "convert a JsObject to the respective case class instance" in { + json.convertTo[Test0] mustEqual obj + } + "ignore additional members during deserialization" in { + JsObject("a" -> JsNumber(42)).convertTo[Test0] mustEqual obj + } + "throw a DeserializationException if the JsValue is not a JsObject" in ( + JsNull.convertTo[Test0] must throwA(new DeserializationException("Object expected")) + ) + } } From 37af0d4ca639ccea44efcb22d10a1e07abc4c04b Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 19 Sep 2014 15:13:01 +0200 Subject: [PATCH 06/14] Switch JsArray(List) to JsArray(Vector), make parser produce JsObject(HashMap) rather than JsObject(ListMap) --- .../scala/spray/json/CollectionFormats.scala | 35 +++++---- .../scala/spray/json/CompactPrinter.scala | 2 +- src/main/scala/spray/json/JsValue.scala | 12 ++- src/main/scala/spray/json/JsonParser.scala | 4 +- src/main/scala/spray/json/PrettyPrinter.scala | 2 +- .../scala/spray/json/StandardFormats.scala | 12 +-- .../scala/spray/json/PrettyPrinterSpec.scala | 74 +++++++++---------- src/test/scala/spray/json/ReadmeSpec.scala | 2 +- .../scala/spray/json/RoundTripSpecs.scala | 4 +- 9 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/main/scala/spray/json/CollectionFormats.scala b/src/main/scala/spray/json/CollectionFormats.scala index 3b0d0c26..24ab1dd0 100644 --- a/src/main/scala/spray/json/CollectionFormats.scala +++ b/src/main/scala/spray/json/CollectionFormats.scala @@ -23,9 +23,9 @@ 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]) + def write(list: List[T]) = JsArray(list.map(_.toJson).toVector) + def read(value: JsValue): List[T] = value match { + case JsArray(elements) => elements.map(_.convertTo[T])(collection.breakOut) case x => deserializationError("Expected List as JsArray, but got " + x) } } @@ -34,7 +34,7 @@ trait CollectionFormats { * 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 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) @@ -64,31 +64,30 @@ trait CollectionFormats { 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 :_*)) + 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] = 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 :_*)) + 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 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 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) } } - } \ No newline at end of file diff --git a/src/main/scala/spray/json/CompactPrinter.scala b/src/main/scala/spray/json/CompactPrinter.scala index eca616f1..a51583d3 100644 --- a/src/main/scala/spray/json/CompactPrinter.scala +++ b/src/main/scala/spray/json/CompactPrinter.scala @@ -41,7 +41,7 @@ trait CompactPrinter extends JsonPrinter { sb.append('}') } - protected def printArray(elements: List[JsValue], sb: StringBuilder) { + protected def printArray(elements: Seq[JsValue], sb: StringBuilder) { sb.append('[') printSeq(elements, sb.append(','))(print(_, sb)) sb.append(']') diff --git a/src/main/scala/spray/json/JsValue.scala b/src/main/scala/spray/json/JsValue.scala index 7dfb06f7..bc2889d9 100644 --- a/src/main/scala/spray/json/JsValue.scala +++ b/src/main/scala/spray/json/JsValue.scala @@ -18,7 +18,7 @@ package spray.json -import collection.immutable.ListMap +import collection.immutable /** * The general type of a JSON AST node. @@ -49,20 +49,18 @@ sealed abstract class JsValue { */ 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) + def getFields(fieldNames: String*): immutable.Seq[JsValue] = fieldNames.flatMap(fields.get)(collection.breakOut) } 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: _*)) + def apply(members: JsField*) = new JsObject(Map(members: _*)) } /** * A JSON array. */ -case class JsArray(elements: List[JsValue]) extends JsValue +case class JsArray(elements: Vector[JsValue]) extends JsValue object JsArray { - def apply(elements: JsValue*) = new JsArray(elements.toList) + def apply(elements: JsValue*) = new JsArray(elements.toVector) } /** diff --git a/src/main/scala/spray/json/JsonParser.scala b/src/main/scala/spray/json/JsonParser.scala index 36aa3388..19718a7e 100644 --- a/src/main/scala/spray/json/JsonParser.scala +++ b/src/main/scala/spray/json/JsonParser.scala @@ -72,7 +72,7 @@ class JsonParser(input: ParserInput) { // http://tools.ietf.org/html/rfc4627#section-2.2 private def `object`(): Unit = { ws() - var map = ListMap.empty[String, JsValue] + var map = Map.empty[String, JsValue] @tailrec def members(): Unit = { `string`() require(':') @@ -91,7 +91,7 @@ class JsonParser(input: ParserInput) { // http://tools.ietf.org/html/rfc4627#section-2.3 private def `array`(): Unit = { ws() - var list = List.newBuilder[JsValue] + var list = Vector.newBuilder[JsValue] @tailrec def values(): Unit = { `value`() list += jsValue diff --git a/src/main/scala/spray/json/PrettyPrinter.scala b/src/main/scala/spray/json/PrettyPrinter.scala index 5a2f142b..57cf35e7 100644 --- a/src/main/scala/spray/json/PrettyPrinter.scala +++ b/src/main/scala/spray/json/PrettyPrinter.scala @@ -50,7 +50,7 @@ trait PrettyPrinter extends JsonPrinter { sb.append("}") } - protected def printArray(elements: List[JsValue], sb: StringBuilder, indent: Int) { + protected def printArray(elements: Seq[JsValue], sb: StringBuilder, indent: Int) { sb.append('[') printSeq(elements, sb.append(", "))(print(_, sb, indent)) sb.append(']') diff --git a/src/main/scala/spray/json/StandardFormats.scala b/src/main/scala/spray/json/StandardFormats.scala index d71dcae4..154077a5 100644 --- a/src/main/scala/spray/json/StandardFormats.scala +++ b/src/main/scala/spray/json/StandardFormats.scala @@ -61,7 +61,7 @@ trait StandardFormats { 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 JsArray(Seq(a, b)) => (a.convertTo[A], b.convertTo[B]) case x => deserializationError("Expected Tuple2 as JsArray, but got " + x) } } @@ -69,7 +69,7 @@ trait StandardFormats { 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 JsArray(Seq(a, b, c)) => (a.convertTo[A], b.convertTo[B], c.convertTo[C]) case x => deserializationError("Expected Tuple3 as JsArray, but got " + x) } } @@ -77,7 +77,7 @@ trait StandardFormats { 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 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) } } @@ -86,7 +86,7 @@ trait StandardFormats { 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) => + 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) } @@ -97,7 +97,7 @@ trait StandardFormats { 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) => + 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) } @@ -108,7 +108,7 @@ trait StandardFormats { 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(a :: b :: c :: d :: e :: f :: g :: Nil) => + 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/src/test/scala/spray/json/PrettyPrinterSpec.scala b/src/test/scala/spray/json/PrettyPrinterSpec.scala index 27137a88..6354ef0b 100644 --- a/src/test/scala/spray/json/PrettyPrinterSpec.scala +++ b/src/test/scala/spray/json/PrettyPrinterSpec.scala @@ -16,49 +16,49 @@ package spray.json +import scala.collection.immutable.ListMap import org.specs2.mutable._ class PrettyPrinterSpec extends Specification { "The PrettyPrinter" should { "print a more complicated JsObject nicely aligned" in { - PrettyPrinter { - JsonParser { - """|{ - | "simpleKey" : "some value", - | "key with spaces": null, - | "zero": 0, - | "number": -1.2323424E-5, - | "Boolean yes":true, - | "Boolean no": false, - | "Unic\u00f8de" : "Long string with newline\nescape", - | "key with \"quotes\"" : "string", - | "sub object" : { - | "sub key": 26.5, - | "a": "b", - | "array": [1, 2, { "yes":1, "no":0 }, ["a", "b", null], false] - | } - |}""".stripMargin - } - } mustEqual { - """|{ - | "simpleKey": "some value", - | "key with spaces": null, - | "zero": 0, - | "number": -0.000012323424, - | "Boolean yes": true, - | "Boolean no": false, - | "Unic\u00f8de": "Long string with newline\nescape", - | "key with \"quotes\"": "string", - | "sub object": { - | "sub key": 26.5, - | "a": "b", - | "array": [1, 2, { - | "yes": 1, - | "no": 0 - | }, ["a", "b", null], false] - | } - |}""".stripMargin + val JsObject(fields) = JsonParser { + """{ + | "Boolean no": false, + | "Boolean yes":true, + | "Unic\u00f8de" : "Long string with newline\nescape", + | "key with \"quotes\"" : "string", + | "key with spaces": null, + | "number": -1.2323424E-5, + | "simpleKey" : "some value", + | "sub object" : { + | "sub key": 26.5, + | "a": "b", + | "array": [1, 2, { "yes":1, "no":0 }, ["a", "b", null], false] + | }, + | "zero": 0 + |}""".stripMargin + } + PrettyPrinter(JsObject(ListMap(fields.toSeq.sortBy(_._1):_*))) mustEqual { + """{ + | "Boolean no": false, + | "Boolean yes": true, + | "Unic\u00f8de": "Long string with newline\nescape", + | "key with \"quotes\"": "string", + | "key with spaces": null, + | "number": -0.000012323424, + | "simpleKey": "some value", + | "sub object": { + | "sub key": 26.5, + | "a": "b", + | "array": [1, 2, { + | "yes": 1, + | "no": 0 + | }, ["a", "b", null], false] + | }, + | "zero": 0 + |}""".stripMargin } } } diff --git a/src/test/scala/spray/json/ReadmeSpec.scala b/src/test/scala/spray/json/ReadmeSpec.scala index 51a1ec50..306b6568 100644 --- a/src/test/scala/spray/json/ReadmeSpec.scala +++ b/src/test/scala/spray/json/ReadmeSpec.scala @@ -60,7 +60,7 @@ class ReadmeSpec extends Specification { JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) def read(value: JsValue) = value match { - case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => + case JsArray(Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue))) => new Color(name, red.toInt, green.toInt, blue.toInt) case _ => deserializationError("Color expected") } diff --git a/src/test/scala/spray/json/RoundTripSpecs.scala b/src/test/scala/spray/json/RoundTripSpecs.scala index d9e16c10..7609a53b 100644 --- a/src/test/scala/spray/json/RoundTripSpecs.scala +++ b/src/test/scala/spray/json/RoundTripSpecs.scala @@ -20,7 +20,7 @@ object JsValueGenerators { for { n <- choose(0, 15) els <- Gen.containerOfN[List, JsValue](n, genValue(depth - 1)) - } yield JsArray(els) + } yield JsArray(els.toVector) def genField(depth: Int): Gen[(String, JsValue)] = for { key <- parseableString @@ -32,7 +32,7 @@ object JsValueGenerators { for { n <- choose(0, 15) fields <- Gen.containerOfN[List, (String, JsValue)](n, genField(depth - 1)) - } yield JsObject(fields) + } yield JsObject(fields: _*) def genValue(depth: Int): Gen[JsValue] = oneOf( From f33e846b95d8e9b20a8aeb4e2ca74fd2f34206fc Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 19 Sep 2014 15:25:35 +0200 Subject: [PATCH 07/14] Improve JsonPrinter to enable printing to custom StringBuilder --- src/main/scala/spray/json/JsonPrinter.scala | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/scala/spray/json/JsonPrinter.scala b/src/main/scala/spray/json/JsonPrinter.scala index d255e034..258fc5aa 100644 --- a/src/main/scala/spray/json/JsonPrinter.scala +++ b/src/main/scala/spray/json/JsonPrinter.scala @@ -17,7 +17,7 @@ package spray.json import annotation.tailrec -import java.lang.StringBuilder +import java.lang.{StringBuilder => JStringBuilder} /** * A JsonPrinter serializes a JSON AST to a String. @@ -26,24 +26,22 @@ 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 + def apply(x: JsValue, + jsonpCallback: Option[String] = None, + sb: JStringBuilder = new JStringBuilder(256)): String = { jsonpCallback match { - case Some(callback) => { + case Some(callback) => sb.append(callback).append('(') print(x, sb) - sb.append(')'); - } + sb.append(')') case None => print(x, sb) } sb.toString } - def print(x: JsValue, sb: StringBuilder) + def print(x: JsValue, sb: JStringBuilder) - protected def printLeaf(x: JsValue, sb: StringBuilder) { + protected def printLeaf(x: JsValue, sb: JStringBuilder) { x match { case JsNull => sb.append("null") case JsTrue => sb.append("true") @@ -54,7 +52,7 @@ trait JsonPrinter extends (JsValue => String) { } } - protected def printString(s: String, sb: StringBuilder) { + protected def printString(s: String, sb: JStringBuilder) { 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) From a6aaf5164ae8b9dda62327c56349de9d97e7721c Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 19 Sep 2014 16:42:58 +0200 Subject: [PATCH 08/14] Fix small problem in JsonParser error reporting --- src/main/scala/spray/json/JsonParser.scala | 11 +++++------ src/test/scala/spray/json/JsonParserSpec.scala | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/scala/spray/json/JsonParser.scala b/src/main/scala/spray/json/JsonParser.scala index 19718a7e..8e7a83eb 100644 --- a/src/main/scala/spray/json/JsonParser.scala +++ b/src/main/scala/spray/json/JsonParser.scala @@ -20,7 +20,6 @@ import java.lang.{StringBuilder => JStringBuilder} import java.nio.{CharBuffer, ByteBuffer} import java.nio.charset.Charset import scala.annotation.{switch, tailrec} -import scala.collection.immutable.ListMap /** * Fast, no-dependency parser for JSON as defined by http://tools.ietf.org/html/rfc4627. @@ -29,7 +28,7 @@ object JsonParser { def apply(input: ParserInput): JsValue = new JsonParser(input).parseJsValue() class ParsingException(val summary: String, val detail: String = "") - extends RuntimeException(if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail) + extends RuntimeException(if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ":" + detail) } class JsonParser(input: ParserInput) { @@ -192,7 +191,7 @@ class JsonParser(input: ParserInput) { } val detail = { val sanitizedText = text.map(c ⇒ if (Character.isISOControl(c)) '?' else c) - s"\n$sanitizedText\n${" " * col}^\n" + s"\n$sanitizedText\n${" " * (col-1)}^\n" } throw new ParsingException(summary, detail) } @@ -236,11 +235,11 @@ object ParserInput { @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, sb.toString) + case '\n' | EOI => Line(lineNr, index - lineStartIx + 1, sb.toString) case c => sb.append(c); rec(ix + 1, lineStartIx, lineNr) } val savedCursor = _cursor - _cursor = 0 + _cursor = -1 val line = rec(ix = 0, lineStartIx = 0, lineNr = 1) _cursor = savedCursor line @@ -250,7 +249,7 @@ object ParserInput { private val UTF8 = Charset.forName("UTF-8") /** - * ParserInput reading directly off a byte array which is assumed to contain the UTF-8 encoded represenation + * 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 DefaultParserInput { diff --git a/src/test/scala/spray/json/JsonParserSpec.scala b/src/test/scala/spray/json/JsonParserSpec.scala index 608898f1..4968eee0 100644 --- a/src/test/scala/spray/json/JsonParserSpec.scala +++ b/src/test/scala/spray/json/JsonParserSpec.scala @@ -73,6 +73,22 @@ class JsonParserSpec extends Specification { _.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size } === List.fill(20)(100) } - } + "produce proper error messages" in { + def errorMessage(input: String) = + try JsonParser(input) catch { case e: JsonParser.ParsingException => e.getMessage } + + errorMessage("""[null, 1.23 {"key":true } ]""") === + """Unexpected character '{' at input index 12 (line 1, position 13), expected ']': + |[null, 1.23 {"key":true } ] + | ^ + |""".stripMargin + + errorMessage("""[null, 1.23, { key":true } ]""") === + """Unexpected character 'k' at input index 16 (line 1, position 17), expected '"': + |[null, 1.23, { key":true } ] + | ^ + |""".stripMargin + } + } } \ No newline at end of file From cddb9a25571af94d4a01df9fec710148cb6ea088 Mon Sep 17 00:00:00 2001 From: Mathias Date: Mon, 22 Sep 2014 11:00:55 +0200 Subject: [PATCH 09/14] Prepare for release 1.3.0 --- CHANGELOG | 11 +++++++++++ README.markdown | 18 ++++++------------ build.sbt | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7a8029b3..e5bc9c4a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,14 @@ +Version 1.3.0 (2014-09-22) +-------------------------- +- Upgraded to Scala 2.11.2, dropped support for Scala 2.9 +- Switched to fast, hand-written parser (#86, #108) +- Removed dependency on parboiled +- Changed parser to produce JsObject(HashMap) rather than JsObject(ListMap) +- Switched JsArray(List) to JsArray(Vector) +- Improved JsonPrinter to support printing to custom StringBuilder +- Added support for parameter-less case classes (#41) + + Version 1.2.6 (2014-04-10) -------------------------- - Improved deserialization error message with name of malformed field (#62) diff --git a/README.markdown b/README.markdown index ab0d0362..4df02604 100644 --- a/README.markdown +++ b/README.markdown @@ -3,9 +3,10 @@ _spray-json_ is a lightweight, clean and efficient [JSON] implementation in Scal It sports the following features: * A simple immutable model of the JSON language elements -* An efficient JSON PEG parser (implemented with [parboiled][]) +* An efficient JSON parser * Choice of either compact or pretty JSON-to-string printing * Type-class based (de)serialization of custom objects (no reflection, no intrusion) +* No external dependencies _spray-json_ allows you to convert between * String JSON documents @@ -19,20 +20,14 @@ as depicted in this diagram: ### Installation _spray-json_ is available from the [repo.spray.io] repository. -The latest release is `1.2.6` and is built against Scala 2.9.3, Scala 2.10.4 and Scala 2.11.0-RC4. +The latest release is `1.3.0` and is built against Scala 2.10.4 and Scala 2.11.2. If you use SBT you can include _spray-json_ in your project with ```scala -resolvers += "spray" at "http://repo.spray.io/" - -libraryDependencies += "io.spray" %% "spray-json" % "1.2.6" +libraryDependencies += "io.spray" %% "spray-json" % "1.3.0" ``` -_spray-json_ has only one dependency: the parsing library [parboiled][] -(which is also a dependency of _spray-http_, so if you use _spray-json_ together with other modules of the *spray* -suite you are not incurring any additional dependency). - ### Usage _spray-json_ is really easy to use. @@ -40,7 +35,7 @@ Just bring all relevant elements in scope with ```scala import spray.json._ -import DefaultJsonProtocol._ // !!! IMPORTANT, else `convertTo` and `toJson` won't work correctly +import DefaultJsonProtocol._ // if you don't supply your own Protocol (see below) ``` and do one or more of the following: @@ -92,7 +87,7 @@ protocol need to be "mece" (mutually exclusive, collectively exhaustive), i.e. t together need to span all types required by the application. This may sound more complicated than it is. -_spray-json_ comes with a `DefaultJsonProtocol`, which already covers all of Scalas value types as well as the most +_spray-json_ comes with a `DefaultJsonProtocol`, which already covers all of Scala's value types as well as the most important reference and collection types. As long as your code uses nothing more than these you only need the `DefaultJsonProtocol`. Here are the types already taken care of by the `DefaultJsonProtocol`: @@ -289,7 +284,6 @@ _spray-json_ project under the project’s open source license. [JSON]: http://json.org - [parboiled]: http://parboiled.org [repo.spray.io]: http://repo.spray.io [SJSON]: https://github.com/debasishg/sjson [Databinder-Dispatch]: https://github.com/n8han/Databinder-Dispatch diff --git a/build.sbt b/build.sbt index 8ec4857f..8607bffe 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ name := "spray-json" -version := "1.3.0-SNAPSHOT" +version := "1.3.0" organization := "io.spray" From 493a11f79f05e2bfc69a78e803459ee2ad14870b Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:21:06 +0100 Subject: [PATCH 10/14] Added alternative product formats --- README.markdown | 19 + src/main/scala/spray/json/ProductFormat.scala | 251 ++++++ .../scala/spray/json/ProductFormats.scala | 2 +- .../scala/spray/json/ProductFormats2.scala | 718 ++++++++++++++++++ 4 files changed, 989 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/spray/json/ProductFormat.scala create mode 100644 src/main/scala/spray/json/ProductFormats2.scala diff --git a/README.markdown b/README.markdown index 4df02604..d6d718c7 100644 --- a/README.markdown +++ b/README.markdown @@ -153,6 +153,25 @@ object MyJsonProtocol extends DefaultJsonProtocol { } ``` +### Alternative format for Case Classes + +As of version 1.2.6, there is an alternative method to create formats for case classes. Instead of `jsonFormatN` use +`formatN`. Apart from this, the new formats are a drop-in replacement for the original ones. + +```scala +case class Color(name: String, red: Int, green: Int, blue: Int) + +object MyJsonProtocol extends DefaultJsonProtocol { + implicit val colorFormat = format4(Color) +} + +The new method has several advantages: + +* Use default values defined on case-classes when fields are missing from json +* Omit empty arrays and objects when serializing fields with default values +* Allows property renaming +* Override formats per field +* Allow advanced customizations #### NullOptions diff --git a/src/main/scala/spray/json/ProductFormat.scala b/src/main/scala/spray/json/ProductFormat.scala new file mode 100644 index 00000000..bc160f6e --- /dev/null +++ b/src/main/scala/spray/json/ProductFormat.scala @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2011 Ruud Diterwich, BusyMachines + * + * 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.reflect.{ParameterizedType, Modifier} +import scala.reflect.ClassTag +import scala.reflect.classTag +import scala.collection.mutable +import scala.language.existentials + +/** + * A field format provides a more generic way to translate a field value to json and visa versa. + * It allows for field renaming, multi-field mappings etc. + */ +trait ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) : List[JsField] + def read(field: ProductField, obj: JsObject) : F + def withJsonName(jsonName: String) = this + def withJsonFormat(format: JsonFormat[F]) = this + def withDefault(default: Option[() => Any]) = this +} + +/** + * Implicitly converts a JsonFormat to a ProductFieldFormat. This implicit + * makes the new product formats drop-in compatible with the original ones. + */ +object ProductFieldFormat { + implicit def of[F](implicit jsonFormat: JsonFormat[F]) = + DefaultProductFieldFormat[F](None, None, jsonFormat) +} + +/** + * Do not serialize the field to and from json. Should only be used when the + * field has a default value. Instead of using this class directly, one can also + * use ProductFormat#excludeField. + */ +class NullProductFieldFormat[F] extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = rest + def read(field: ProductField, obj: JsObject) : F = + field.default.getOrElse(throw new IllegalStateException(s"Field ${field.name} should have a default value")).apply().asInstanceOf[F] +} + +object NullProductFieldFormat extends NullProductFieldFormat[Any] + +/** + * A normal field format that will serialize a product field one to one to a json field. + */ +case class DefaultProductFieldFormat[F](jsonName: Option[String], default: Option[() => Any], jsonFormat: JsonFormat[F]) extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = + if (field.isOption && value == None) rest + else if (field.isSeq && field.default.isDefined && value == Seq.empty) rest + else if (field.isMap && field.default.isDefined && value == Map.empty) rest + else jsonName.getOrElse(field.name) -> jsonFormat.write(value) :: rest + + def read(field: ProductField, obj: JsObject) = + obj.fields.get(jsonName.getOrElse(field.name)) match { + case Some(value) => jsonFormat.read(value) + case None => default.orElse(field.default) match { + case Some(defarg) => defarg().asInstanceOf[F] + case None => + if (field.isOption) None.asInstanceOf[F] + else deserializationError("Object is missing required member '" + field.name + "'") + } + } + override def withJsonName(jsonName: String) = this.copy(jsonName = Some(jsonName)) + override def withDefault(default: Option[() => Any]) = this.copy(default = default) + override def withJsonFormat(format: JsonFormat[F]) = this.copy(jsonFormat = format) +} + +class EmbeddedProductFieldFormat[F](originalFormat: ProductFieldFormat[F]) extends ProductFieldFormat[F] { + def write(field: ProductField, value: F, rest: List[JsField]) = + originalFormat.write(field, value, Nil).map(_._2).collect{case JsObject(fields) => fields}.flatten ++ rest + def read(field: ProductField, obj: JsObject) : F = + originalFormat match { + case DefaultProductFieldFormat(jsonName, default, jsonFormat) => + jsonFormat.read(obj) + case _ => throw new Exception(s"Can't embed a non-default product field ${field.name}") + } +} + +/** + * Represents a case-class field. + */ +case class ProductField( + name: String, + default: Option[() => Any] = None, + isOption: Boolean = false, + isSeq: Boolean = false, + isMap: Boolean = false, + fieldType: Class[_], + genericParameterTypes: Array[Class[_]], + format: ProductFieldFormat[_]) + +/** + * Base class for product formats, mainly used as json formats for case-classes. + */ +abstract class ProductFormat[P :ClassTag] extends RootJsonFormat[P] { outer => + + /** + * Discovered fields of the product class. + */ + val fields: Array[ProductField] + protected val delegate: ProductFormat[P] + + def write(p: P) = delegate.write(fields, p) + def read(value: JsValue) = delegate.read(fields, value) + protected def write(fields: Seq[ProductField], p: P) : JsValue + protected def read(fields: Seq[ProductField], value: JsValue) : P + + /** + * Returns a new format that overrides the json name for given fields (identified by name). + */ + def withJsonNames(jsonNames: (String, String)*) = decorate( + fields.map(f => jsonNames.find(_._1 == f.name).map(s => f.copy(format = f.format.withJsonName(s._2))).getOrElse(f))) + + /** + * Returns a new format that overrides the json formats for given fields (identified by name). + */ + def withJsonFormats(jsonFormats: (String, JsonFormat[_])*) = decorate( + fields.map(f => jsonFormats.find(_._1 == f.name).map(s => f.copy(format = f.format.asInstanceOf[ProductFieldFormat[Any]].withJsonFormat(s._2.asInstanceOf[JsonFormat[Any]]))).getOrElse(f))) + + /** + * Returns a new format that overrides the default value for given fields (identified by name). + */ + def withDefaults(defaults: (String, () => Any)*) = decorate( + fields.map(f => f.copy(default = defaults.find(_._1 == f.name).map(_._2).orElse(f.default)))) + + /** + * Returns a new format that overrides the field formats for given fields (identified by name). + */ + def withFieldFormats(formats: (String, ProductFieldFormat[_])*) = decorate( + fields.map(f => f.copy(format = formats.find(_._1 == f.name).map(_._2).getOrElse(f.format)))) + + /** + * Returns a new format with transformed product fields. + */ + def mapFields(cp: ProductField => ProductField) = decorate( + fields.map(cp)) + + /** + * Returns a new format that excludes given fields when serializing to json. + */ + def excludeFields(fields: String*) = + withFieldFormats(fields.map(_ -> NullProductFieldFormat):_*) + + /** + * Returns a new format that embeds nested fields in the root json object. + */ + def embed(fields: String*) = mapFields { + case field if !fields.contains(field.name) => field + case field => field.copy(format = new EmbeddedProductFieldFormat[Any](field.format.asInstanceOf[ProductFieldFormat[Any]])) + } + + private def decorate(_fields: Array[ProductField]) = new ProductFormat[P] { + val fields = _fields + val delegate = outer.delegate + def write(fields: Seq[ProductField], p: P) : JsValue = throw new IllegalStateException + def read(fields: Seq[ProductField], value: JsValue) : P = throw new IllegalStateException + } +} + +abstract private[spray] class ProductFormatImpl[P <: Product :ClassTag, F0 :ProductFieldFormat, F1 :ProductFieldFormat, F2 :ProductFieldFormat,F3 :ProductFieldFormat,F4 :ProductFieldFormat,F5 :ProductFieldFormat,F6 :ProductFieldFormat,F7 :ProductFieldFormat,F8 :ProductFieldFormat,F9 :ProductFieldFormat,F10 :ProductFieldFormat, F11 :ProductFieldFormat, F12 :ProductFieldFormat, F13 :ProductFieldFormat, F14 :ProductFieldFormat, F15 :ProductFieldFormat, F16 :ProductFieldFormat, F17 :ProductFieldFormat, F18 :ProductFieldFormat, F19 :ProductFieldFormat, F20 :ProductFieldFormat, F21 :ProductFieldFormat] extends ProductFormat[P] { + + protected val delegate = this + + protected def write[F :ProductFieldFormat](field: ProductField, p: P, fieldIndex: Int, rest: List[JsField]): List[JsField] = + field.format.asInstanceOf[ProductFieldFormat[Any]].write(field, p.productElement(fieldIndex), rest) + + protected def read[F :ProductFieldFormat](field: ProductField, value: JsValue) : F = { + value match { + case obj: JsObject => field.format.read(field, obj).asInstanceOf[F] + case _ => deserializationError("Object expected") + } + } + + protected def jsObject(fields: Iterable[JsField]): JsObject = { + JsObject(fields.toMap match { + case map if map.size == fields.size => map + case map => + val builder = mutable.Map[String, JsValue]() + for ((name, value) <- fields) { + (builder.get(name), value) match { + case (Some(JsObject(oldFields)), JsObject(fields)) => + builder += (name -> jsObject(oldFields ++ fields)) + case _ => builder += (name -> value) + } + } + builder.toMap + }) + } + + private def fmt[F](implicit f: ProductFieldFormat[F]) = f.asInstanceOf[ProductFieldFormat[Any]] + + val fields = { + val formats = Array(fmt[F0], fmt[F1], + fmt[F2], fmt[F3], fmt[F4], fmt[F5], fmt[F6], + fmt[F7], fmt[F8], fmt[F9], fmt[F10], fmt[F11], + fmt[F12], fmt[F13], fmt[F14], fmt[F15], fmt[F16], + fmt[F17], fmt[F18], fmt[F19], fmt[F20], fmt[F21]) + + val runtimeClass = classTag[P].runtimeClass + try { + // Need companion class for default arguments. + lazy val companionClass = Class.forName(runtimeClass.getName + "$") + lazy val moduleField = + try { companionClass.getField("MODULE$") } + catch { case e : Throwable => throw new RuntimeException("Can't deserialize default arguments of nested case classes", e) } + lazy val companionObj = moduleField.get(null) + // 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 = runtimeClass.getMethods.filter(_.getName.startsWith("copy$default$")).sortBy( + _.getName.drop("copy$default$".length).takeWhile(_ != '(').toInt) + val fields = runtimeClass.getDeclaredFields.filterNot(f => f.getName.startsWith("$") || Modifier.isTransient(f.getModifiers) || Modifier.isStatic(f.getModifiers)) + if (copyDefaultMethods.length != fields.length) + sys.error("Case class " + runtimeClass.getName + " declares additional fields") + val applyDefaultMethods = copyDefaultMethods.map { method => + try { + val defmeth = companionClass.getMethod("apply" + method.getName.drop("copy".size)) + Some(() => defmeth.invoke(companionObj))} + catch { case e : Throwable => None } + } + if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType }) + sys.error("Cannot determine field order of case class " + runtimeClass.getName) + fields.zip(applyDefaultMethods).zipWithIndex.map { case ((f, m), index) => + val typeArgs: Array[Class[_]] = f.getGenericType match { + case pType: ParameterizedType => pType.getActualTypeArguments.collect { + case argClass: Class[_] => argClass + } + case _ => Array.empty + } + ProductField(f.getName, default = m, classOf[Option[_]].isAssignableFrom(f.getType), classOf[Seq[_]].isAssignableFrom(f.getType), classOf[Map[_, _]].isAssignableFrom(f.getType), f.getType, typeArgs, format = formats(index)) + } + } catch { + case ex : Throwable => throw new RuntimeException("Cannot automatically determine case class field names and order " + + "for '" + runtimeClass.getName + "', please use the 'jsonFormat' overload with explicit field name specification", ex) + } + } +} diff --git a/src/main/scala/spray/json/ProductFormats.scala b/src/main/scala/spray/json/ProductFormats.scala index 5f5a9f4e..119b9219 100644 --- a/src/main/scala/spray/json/ProductFormats.scala +++ b/src/main/scala/spray/json/ProductFormats.scala @@ -23,7 +23,7 @@ import scala.util.control.NonFatal * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait * (especially case classes) */ -trait ProductFormats extends ProductFormatsInstances { +trait ProductFormats extends ProductFormatsInstances with ProductFormats2 { this: StandardFormats => def jsonFormat0[T](construct: () => T): RootJsonFormat[T] = diff --git a/src/main/scala/spray/json/ProductFormats2.scala b/src/main/scala/spray/json/ProductFormats2.scala new file mode 100644 index 00000000..ba113f90 --- /dev/null +++ b/src/main/scala/spray/json/ProductFormats2.scala @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2011 Ruud Diterwich, BusyMachines + * + * 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 ProductFormats2 { + type Fields = Seq[ProductField] + type JFmt[F] = ProductFieldFormat[F] + private[spray] trait FNull + private[spray] implicit val fNull = new NullProductFieldFormat[FNull] + + def format1[P <: Product :ClassTag, F0 :JFmt](construct: (F0) => P) = new ProductFormatImpl[P, F0, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + Nil) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value) + ) + } + + def format2[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt](construct: (F0, F1) => P) = new ProductFormatImpl[P, F0, F1, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + Nil)) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value) + ) + } + + def format3[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt](construct: (F0, F1, F2) => P) = new ProductFormatImpl[P, F0, F1, F2, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + Nil))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value) + ) + } + + def format4[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt](construct: (F0, F1, F2, F3) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + Nil)))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value) + ) + } + + def format5[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt](construct: (F0, F1, F2, F3, F4) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + Nil))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value) + ) + } + + def format6[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt](construct: (F0, F1, F2, F3, F4, F5) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + Nil)))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value) + ) + } + + def format7[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + Nil))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value) + ) + } + + def format8[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + Nil)))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value) + ) + } + + def format9[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + Nil))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value) + ) + } + + def format10[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt, F9 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + Nil)))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value) + ) + } + + def format11[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt, F3 :JFmt, F4 :JFmt, F5 :JFmt, F6 :JFmt, F7 :JFmt, F8 :JFmt, F9 :JFmt, F10 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + Nil))))))))))) + ) + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value) + ) + } + + def format12[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt,F11 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + Nil)))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value) + ) + } + + def format13[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + Nil))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value) + ) + } + + def format14[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, FNull, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + Nil)))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value) + ) + } + + def format15[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, FNull, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + Nil))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value) + ) + } + + def format16[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, FNull, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + Nil)))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value) + ) + } + + def format17[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, FNull, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + Nil))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value) + ) + } + + def format18[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, FNull, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + Nil)))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value) + ) + } + + def format19[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, FNull, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + Nil))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value) + ) + } + + def format20[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, FNull, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + Nil)))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value) + ) + } + + def format21[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt, F20 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, FNull] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + write[F20](fields(20), p, 20, + Nil))))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value), + read[F20](fields(20), value) + ) + } + + def format22[P <: Product :ClassTag, F0 :JFmt, F1 :JFmt, F2 :JFmt,F3 :JFmt,F4 :JFmt,F5 :JFmt,F6 :JFmt,F7 :JFmt,F8 :JFmt,F9 :JFmt,F10 :JFmt, F11 :JFmt, F12 :JFmt, F13 :JFmt, F14 :JFmt, F15 :JFmt, F16 :JFmt, F17 :JFmt, F18 :JFmt, F19 :JFmt, F20 :JFmt, F21 :JFmt](construct: (F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21) => P) = new ProductFormatImpl[P, F0, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, F18, F19, F20, F21] { + def write(fields: Fields, p: P) = jsObject( + write[F0](fields(0), p, 0, + write[F1](fields(1), p, 1, + write[F2](fields(2), p, 2, + write[F3](fields(3), p, 3, + write[F4](fields(4), p, 4, + write[F5](fields(5), p, 5, + write[F6](fields(6), p, 6, + write[F7](fields(7), p, 7, + write[F8](fields(8), p, 8, + write[F9](fields(9), p, 9, + write[F10](fields(10), p, 10, + write[F11](fields(11), p, 11, + write[F12](fields(12), p, 12, + write[F13](fields(13), p, 13, + write[F14](fields(14), p, 14, + write[F15](fields(15), p, 15, + write[F16](fields(16), p, 16, + write[F17](fields(17), p, 17, + write[F18](fields(18), p, 18, + write[F19](fields(19), p, 19, + write[F20](fields(20), p, 20, + write[F21](fields(21), p, 21, + Nil)))))))))))))))))))))) + ) + + def read(fields: Fields, value: JsValue) = construct( + read[F0](fields(0), value), + read[F1](fields(1), value), + read[F2](fields(2), value), + read[F3](fields(3), value), + read[F4](fields(4), value), + read[F5](fields(5), value), + read[F6](fields(6), value), + read[F7](fields(7), value), + read[F8](fields(8), value), + read[F9](fields(9), value), + read[F10](fields(10), value), + read[F11](fields(11), value), + read[F12](fields(12), value), + read[F13](fields(13), value), + read[F14](fields(14), value), + read[F15](fields(15), value), + read[F16](fields(16), value), + read[F17](fields(17), value), + read[F18](fields(18), value), + read[F19](fields(19), value), + read[F20](fields(20), value), + read[F21](fields(21), value) + ) + } +} From 74f13dbcadad97fef5efa6df8ffdd97599450694 Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:31:14 +0100 Subject: [PATCH 11/14] Added tests for alternative product format --- .../spray/json/ProductFormats2Spec.scala | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/test/scala/spray/json/ProductFormats2Spec.scala diff --git a/src/test/scala/spray/json/ProductFormats2Spec.scala b/src/test/scala/spray/json/ProductFormats2Spec.scala new file mode 100644 index 00000000..8405d4d6 --- /dev/null +++ b/src/test/scala/spray/json/ProductFormats2Spec.scala @@ -0,0 +1,121 @@ +package spray.json + +import org.specs2.mutable.Specification + +object ProductFormatTestsFixture { + + case class Box(size: Double, things: List[Thing]) + case class Thing(name: String, properties: Map[String, String] = Map.empty) +} + +class ProductFormats2Spec extends Specification { + import ProductFormatTestsFixture._ + import DefaultJsonProtocol._ + + { + implicit val thingFormat = format2(Thing) + implicit val boxFormat = format2(Box) + + "A case class" should { + "be correctly serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"name" : "egg", "properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + } + + "Default values" should { + "be correctly deserialized from json" in { + val json = """{"name" : "egg"}""" + val thing = Thing("egg") + assert(json.asJson.convertTo[Thing] === thing) + assert(json.asJson.convertTo[Thing].properties == Map.empty) + } + } + + "Empty collections with a default value" should { + "not be serialized to json" in { + val thing = Thing("egg") + val json = """{"name" : "egg"}""" + assert(thing.toJson === json.asJson) + } + } + + "Empty collections without a default value" should { + "be serialized to json" in { + val thing = Box(10, Nil) + val json = """{"size" : 10, "things" : [] }""" + assert(thing.toJson === json.asJson) + } + } + } + + { + implicit val thingFormat = format2(Thing).withJsonNames("name" -> "description") + implicit val boxFormat = format2(Box) + + "A renamed field" should { + "be correctly serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"description" : "egg", "properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + "be correctly serialized in a nested object" in { + val box = Box(10, Thing("egg", Map("color" -> "red")) :: Nil) + val json = """{"size":10, "things":[{"description" : "egg", "properties" : {"color" : "red"}}]}""" + assert(box.toJson === json.asJson) + assert(json.asJson.convertTo[Box] === box) + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("properties") + + "An excluded field" should { + "not be serialized to json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"name":"egg"}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === Thing("egg")) + } + "not be deserialized from json" in { + val json = """{"name":"egg", "properties" : {"color" : "red"}}""" + val thing = Thing("egg") + assert(json.asJson.convertTo[Thing] === thing) + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("name") + + "An excluded field without a default" should { + "not be deserialized from json" in { + val thing = Thing("egg", Map("color" -> "red")) + val json = """{"properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + json.asJson.convertTo[Thing] must throwA[IllegalStateException] + } + } + } + + { + implicit val thingFormat = format2(Thing) + .excludeFields("name") + .withDefaults("name" -> (() => "ball")) + + "An excluded field with an explicit default" should { + "not be serialized to json" in { + val thing = Thing("ball", Map("color" -> "red")) + val json = """{"properties" : {"color" : "red"}}""" + assert(thing.toJson === json.asJson) + assert(json.asJson.convertTo[Thing] === thing) + } + } + } +} From ac5f073af27ef817dbc09a6ee835fe0e192b18fe Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sun, 9 Mar 2014 18:32:52 +0100 Subject: [PATCH 12/14] typo in readme --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index d6d718c7..31cb78b6 100644 --- a/README.markdown +++ b/README.markdown @@ -164,6 +164,7 @@ case class Color(name: String, red: Int, green: Int, blue: Int) object MyJsonProtocol extends DefaultJsonProtocol { implicit val colorFormat = format4(Color) } +``` The new method has several advantages: From 373d5e6a193d3bfbee4b9c55e5e3cd6df95dbe0a Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Tue, 11 Mar 2014 22:03:59 +0100 Subject: [PATCH 13/14] preserve field order in product formats --- src/main/scala/spray/json/ProductFormat.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/scala/spray/json/ProductFormat.scala b/src/main/scala/spray/json/ProductFormat.scala index bc160f6e..51beeba1 100644 --- a/src/main/scala/spray/json/ProductFormat.scala +++ b/src/main/scala/spray/json/ProductFormat.scala @@ -19,6 +19,7 @@ import java.lang.reflect.{ParameterizedType, Modifier} import scala.reflect.ClassTag import scala.reflect.classTag import scala.collection.mutable +import scala.collection.immutable.ListMap import scala.language.existentials /** @@ -186,15 +187,15 @@ abstract private[spray] class ProductFormatImpl[P <: Product :ClassTag, F0 :Prod } } - protected def jsObject(fields: Iterable[JsField]): JsObject = { - JsObject(fields.toMap match { + protected def jsObject(fields: Seq[JsField]): JsObject = { + JsObject(ListMap(fields:_*) match { case map if map.size == fields.size => map case map => - val builder = mutable.Map[String, JsValue]() + val builder = mutable.ListMap[String, JsValue]() for ((name, value) <- fields) { (builder.get(name), value) match { - case (Some(JsObject(oldFields)), JsObject(fields)) => - builder += (name -> jsObject(oldFields ++ fields)) + case (Some(JsObject(oldFields)), JsObject(newFields)) => + builder += (name -> jsObject((oldFields ++ newFields).toSeq)) case _ => builder += (name -> value) } } From cc186e0543ca3ab2975a7cbeeebf277891b39cb6 Mon Sep 17 00:00:00 2001 From: Ruud Diterwich Date: Sat, 27 Sep 2014 14:48:31 +0200 Subject: [PATCH 14/14] Fixed tests for ProductFormats2 --- .../spray/json/ProductFormats2Spec.scala | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/scala/spray/json/ProductFormats2Spec.scala b/src/test/scala/spray/json/ProductFormats2Spec.scala index 8405d4d6..aac89e37 100644 --- a/src/test/scala/spray/json/ProductFormats2Spec.scala +++ b/src/test/scala/spray/json/ProductFormats2Spec.scala @@ -1,6 +1,6 @@ package spray.json -import org.specs2.mutable.Specification +import org.specs2.mutable._ object ProductFormatTestsFixture { @@ -20,8 +20,8 @@ class ProductFormats2Spec extends Specification { "be correctly serialized to json" in { val thing = Thing("egg", Map("color" -> "red")) val json = """{"name" : "egg", "properties" : {"color" : "red"}}""" - assert(thing.toJson === json.asJson) - assert(json.asJson.convertTo[Thing] === thing) + thing.toJson mustEqual json.parseJson + json.parseJson.convertTo[Thing] mustEqual thing } } @@ -29,8 +29,8 @@ class ProductFormats2Spec extends Specification { "be correctly deserialized from json" in { val json = """{"name" : "egg"}""" val thing = Thing("egg") - assert(json.asJson.convertTo[Thing] === thing) - assert(json.asJson.convertTo[Thing].properties == Map.empty) + json.parseJson.convertTo[Thing] mustEqual thing + json.parseJson.convertTo[Thing].properties mustEqual Map.empty } } @@ -38,7 +38,7 @@ class ProductFormats2Spec extends Specification { "not be serialized to json" in { val thing = Thing("egg") val json = """{"name" : "egg"}""" - assert(thing.toJson === json.asJson) + thing.toJson mustEqual json.parseJson } } @@ -46,7 +46,7 @@ class ProductFormats2Spec extends Specification { "be serialized to json" in { val thing = Box(10, Nil) val json = """{"size" : 10, "things" : [] }""" - assert(thing.toJson === json.asJson) + thing.toJson mustEqual json.parseJson } } } @@ -59,14 +59,14 @@ class ProductFormats2Spec extends Specification { "be correctly serialized to json" in { val thing = Thing("egg", Map("color" -> "red")) val json = """{"description" : "egg", "properties" : {"color" : "red"}}""" - assert(thing.toJson === json.asJson) - assert(json.asJson.convertTo[Thing] === thing) + thing.toJson mustEqual json.parseJson + json.parseJson.convertTo[Thing] mustEqual thing } "be correctly serialized in a nested object" in { val box = Box(10, Thing("egg", Map("color" -> "red")) :: Nil) val json = """{"size":10, "things":[{"description" : "egg", "properties" : {"color" : "red"}}]}""" - assert(box.toJson === json.asJson) - assert(json.asJson.convertTo[Box] === box) + box.toJson mustEqual json.parseJson + json.parseJson.convertTo[Box] mustEqual box } } } @@ -79,13 +79,13 @@ class ProductFormats2Spec extends Specification { "not be serialized to json" in { val thing = Thing("egg", Map("color" -> "red")) val json = """{"name":"egg"}""" - assert(thing.toJson === json.asJson) - assert(json.asJson.convertTo[Thing] === Thing("egg")) + thing.toJson mustEqual json.parseJson + json.parseJson.convertTo[Thing] mustEqual Thing("egg") } "not be deserialized from json" in { val json = """{"name":"egg", "properties" : {"color" : "red"}}""" val thing = Thing("egg") - assert(json.asJson.convertTo[Thing] === thing) + json.parseJson.convertTo[Thing] mustEqual thing } } } @@ -98,8 +98,8 @@ class ProductFormats2Spec extends Specification { "not be deserialized from json" in { val thing = Thing("egg", Map("color" -> "red")) val json = """{"properties" : {"color" : "red"}}""" - assert(thing.toJson === json.asJson) - json.asJson.convertTo[Thing] must throwA[IllegalStateException] + assert(thing.toJson === json.parseJson) + json.parseJson.convertTo[Thing] must throwA[IllegalStateException] } } } @@ -113,8 +113,8 @@ class ProductFormats2Spec extends Specification { "not be serialized to json" in { val thing = Thing("ball", Map("color" -> "red")) val json = """{"properties" : {"color" : "red"}}""" - assert(thing.toJson === json.asJson) - assert(json.asJson.convertTo[Thing] === thing) + thing.toJson mustEqual json.parseJson + json.parseJson.convertTo[Thing] mustEqual thing } } }