/*
* Copyright (C) 2011 Mathias Doenitz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cc.spray.json
/**
* Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait
* (especially case classes)
*/
trait ProductFormats {
this: StandardFormats =>
def jsonFormat1[A :JF, T <: Product :ClassManifest](construct: A => T): RootJsonFormat[T] = {
val Array(a) = extractFieldNames(classManifest[T])
jsonFormat(construct, a)
}
def jsonFormat[A :JF, T <: Product](construct: A => T, a: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0)
)
def read(value: JsValue) = construct(
fromField[A](value, a)
)
}
def jsonFormat2[A :JF, B :JF, T <: Product :ClassManifest](construct: (A, B) => T): RootJsonFormat[T] = {
val Array(a, b) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b)
}
def jsonFormat[A :JF, B :JF, T <: Product](construct: (A, B) => T, a: String, b: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b)
)
}
def jsonFormat3[A :JF, B :JF, C :JF, T <: Product :ClassManifest](construct: (A, B, C) => T): RootJsonFormat[T] = {
val Array(a, b, c) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c)
}
def jsonFormat[A :JF, B :JF, C :JF, T <: Product](construct: (A, B, C) => T,
a: String, b: String, c: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2)))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c)
)
}
def jsonFormat4[A :JF, B :JF, C :JF, D :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D) => T): RootJsonFormat[T] = {
val Array(a, b, c, d) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, T <: Product](construct: (A, B, C, D) => T,
a: String, b: String, c: String, d: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d)
)
}
def jsonFormat5[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product](construct: (A, B, C, D, E) => T,
a: String, b: String, c: String, d: String, e: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4)))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e)
)
}
def jsonFormat6[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product](construct: (A, B, C, D, E, F) => T,
a: String, b: String, c: String, d: String, e: String, f: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f)
)
}
def jsonFormat7[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product](construct: (A, B, C, D, E, F, G) => T,
a: String, b: String, c: String, d: String, e: String, f: String, g: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6)))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g)
)
}
def jsonFormat8[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H) => T,
a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h)
)
}
def jsonFormat9[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I) => T, a: String, b: String, c: String, d: String, e: String, f: String,
g: String, h: String, i: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8)))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i)
)
}
def jsonFormat10[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J) => T, a: String, b: String, c: String, d: String, e: String,
f: String, g: String, h: String, i: String, j: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j)
)
}
def jsonFormat11[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J, K) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j, k) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J, K) => T, a: String, b: String, c: String, d: String, e: String,
f: String, g: String, h: String, i: String, j: String, k: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9,
productElement2Field[K](k, p, 10)))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j),
fromField[K](value, k)
)
}
def jsonFormat12[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j, k, l) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T, a: String, b: String, c: String, d: String, e: String,
f: String, g: String, h: String, i: String, j: String, k: String, l: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9,
productElement2Field[K](k, p, 10,
productElement2Field[L](l, p, 11))))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j),
fromField[K](value, k),
fromField[L](value, l)
)
}
def jsonFormat13[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j, k, l, m) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T, a: String, b: String, c: String, d: String, e: String,
f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9,
productElement2Field[K](k, p, 10,
productElement2Field[L](l, p, 11,
productElement2Field[M](m, p, 12)))))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j),
fromField[K](value, k),
fromField[L](value, l),
fromField[M](value, m)
)
}
def jsonFormat14[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T, a: String, b: String, c: String, d: String,
e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String,
n: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9,
productElement2Field[K](k, p, 10,
productElement2Field[L](l, p, 11,
productElement2Field[M](m, p, 12,
productElement2Field[N](n, p, 13))))))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j),
fromField[K](value, k),
fromField[L](value, l),
fromField[M](value, m),
fromField[N](value, n)
)
}
def jsonFormat15[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, O :JF, T <: Product :ClassManifest]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T): RootJsonFormat[T] = {
val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = extractFieldNames(classManifest[T])
jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o)
}
def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, O :JF, T <: Product]
(construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T, a: String, b: String, c: String, d: String,
e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, n: String,
o: String): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
productElement2Field[C](c, p, 2,
productElement2Field[D](d, p, 3,
productElement2Field[E](e, p, 4,
productElement2Field[F](f, p, 5,
productElement2Field[G](g, p, 6,
productElement2Field[H](h, p, 7,
productElement2Field[I](i, p, 8,
productElement2Field[J](j, p, 9,
productElement2Field[K](k, p, 10,
productElement2Field[L](l, p, 11,
productElement2Field[M](m, p, 12,
productElement2Field[N](n, p, 13,
productElement2Field[O](o, p, 14)))))))))))))))
)
def read(value: JsValue) = construct(
fromField[A](value, a),
fromField[B](value, b),
fromField[C](value, c),
fromField[D](value, d),
fromField[E](value, e),
fromField[F](value, f),
fromField[G](value, g),
fromField[H](value, h),
fromField[I](value, i),
fromField[J](value, j),
fromField[K](value, k),
fromField[L](value, l),
fromField[M](value, m),
fromField[N](value, n),
fromField[O](value, o)
)
}
// helpers
protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField] = Nil)
(implicit writer: JsonWriter[T]): List[JsField] = {
val value = p.productElement(ix).asInstanceOf[T]
writer match {
case _: OptionFormat[_] if (value == None) => rest
case _ => (fieldName, writer.write(value)) :: rest
}
}
private def fromField[T](value: JsValue, fieldName: String)(implicit reader: JsonReader[T]) = {
value match {
case x: JsObject =>
var fieldFound = false
try {
val fieldValue = x.fields(fieldName)
fieldFound = true
reader.read(fieldValue)
}
catch {
case e: NoSuchElementException if !fieldFound =>
if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T]
else deserializationError("Object is missing required member '" + fieldName + "'", e)
}
case _ => deserializationError("Object expected")
}
}
protected def extractFieldNames(classManifest: ClassManifest[_]): Array[String] = {
val clazz = classManifest.erasure
try {
// copy methods have the form copy$default$N(), we need to sort them in order, but must account for the fact
// that lexical sorting of ...8(), ...9(), ...10() is not correct, so we extract N and sort by N.toInt
val copyDefaultMethods = clazz.getMethods.filter(_.getName.startsWith("copy$default$")).sortBy(
_.getName.drop("copy$default$".length).takeWhile(_ != '(').toInt)
val fields = clazz.getDeclaredFields.filterNot(_.getName.startsWith("$"))
if (copyDefaultMethods.length != fields.length)
sys.error("Case class " + clazz.getName + " declares additional fields")
if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType })
sys.error("Cannot determine field order of case class " + clazz.getName)
fields.map(_.getName)
} catch {
case ex => throw new RuntimeException("Cannot automatically determine case class field names and order " +
"for '" + clazz.getName + "', please use the 'jsonFormat' overload with explicit field name specification", ex)
}
}
}
/**
* This trait supplies an alternative rendering mode for optional case class members.
* Normally optional members that are undefined (`None`) are not rendered at all.
* By mixing in this trait into your custom JsonProtocol you can enforce the rendering of undefined members as `null`.
* (Note that this only affect JSON writing, spray-json will always read missing optional members as well as `null`
* optional members as `None`.)
*/
trait NullOptions extends ProductFormats {
this: StandardFormats =>
override protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField])
(implicit writer: JsonWriter[T]) = {
val value = p.productElement(ix).asInstanceOf[T]
(fieldName, writer.write(value)) :: rest
}
}