├── project
├── build.properties
└── build.scala
├── .gitignore
├── src
├── main
│ └── scala
│ │ └── jacks
│ │ ├── symbol.scala
│ │ ├── option.scala
│ │ ├── untyped.scala
│ │ ├── jacks.scala
│ │ ├── iterable.scala
│ │ ├── map.scala
│ │ ├── tuple.scala
│ │ ├── case.scala
│ │ └── module.scala
└── test
│ └── scala
│ └── jacks
│ ├── test.scala
│ └── spec.scala
├── README
└── LICENSE
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.13.8
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib_managed/
2 | project/boot
3 | target/
4 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/symbol.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.core._
6 | import com.fasterxml.jackson.databind._
7 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
8 |
9 | class SymbolSerializer(t: JavaType) extends StdSerializer[Symbol](t) {
10 | override def serialize(value: Symbol, g: JsonGenerator, p: SerializerProvider) {
11 | g.writeString(value.name)
12 | }
13 | }
14 |
15 | class SymbolDeserializer extends JsonDeserializer[Symbol] {
16 | override def deserialize(p: JsonParser, ctx: DeserializationContext) = Symbol(p.getText)
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/option.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.core._
6 | import com.fasterxml.jackson.databind._
7 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
8 |
9 | class OptionSerializer(t: JavaType) extends StdSerializer[Option[_]](t) {
10 | override def serialize(value: Option[_], g: JsonGenerator, p: SerializerProvider) {
11 | value match {
12 | case Some(v) =>
13 | val a = v.asInstanceOf[AnyRef]
14 | val s = p.findValueSerializer(a.getClass, null)
15 | s.serialize(a, g, p)
16 | case None =>
17 | p.defaultSerializeNull(g)
18 | }
19 | }
20 |
21 | override def isEmpty(v: Option[_]) = v.isEmpty
22 | }
23 |
24 | class OptionDeserializer(t: JavaType) extends JsonDeserializer[Option[_]] {
25 | override def deserialize(p: JsonParser, ctx: DeserializationContext): Option[_] = {
26 | val d = ctx.findContextualValueDeserializer(t, null)
27 | Some(d.deserialize(p, ctx))
28 | }
29 |
30 | override def getNullValue = None
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/untyped.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2012 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.core._
6 | import com.fasterxml.jackson.databind._
7 | import com.fasterxml.jackson.databind.deser.std.{UntypedObjectDeserializer => BaseUntypedObjectDeserializer}
8 |
9 | class UntypedObjectDeserializer(cfg: DeserializationConfig) extends BaseUntypedObjectDeserializer {
10 | val o = cfg.getTypeFactory.constructParametrizedType(classOf[Map[_, _]], classOf[Map[_, _]], classOf[String], classOf[AnyRef])
11 | val a = cfg.getTypeFactory.constructParametrizedType(classOf[List[_]], classOf[List[_]], classOf[AnyRef])
12 |
13 | override def mapArray(p: JsonParser, ctx: DeserializationContext): AnyRef = {
14 | val d = ctx.findContextualValueDeserializer(a, null)
15 | ctx.isEnabled(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY) match {
16 | case true => mapArrayToArray(p, ctx)
17 | case false => d.deserialize(p, ctx)
18 | }
19 | }
20 |
21 | override def mapObject(p: JsonParser, ctx: DeserializationContext): AnyRef = {
22 | val d = ctx.findContextualValueDeserializer(o, null)
23 | d.deserialize(p, ctx)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/jacks.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.databind.JavaType
6 | import com.fasterxml.jackson.databind.ObjectMapper
7 | import com.fasterxml.jackson.databind.ObjectWriter
8 |
9 | import scala.collection.convert.Wrappers.JConcurrentMapWrapper
10 |
11 | import java.io._
12 | import java.util.concurrent.ConcurrentHashMap
13 |
14 | trait JacksMapper {
15 | val mapper = new ObjectMapper
16 | mapper.registerModule(new ScalaModule)
17 |
18 | def readValue[T: Manifest](src: Array[Byte]): T = mapper.readValue(src, resolve)
19 | def readValue[T: Manifest](src: InputStream): T = mapper.readValue(src, resolve)
20 | def readValue[T: Manifest](src: Reader): T = mapper.readValue(src, resolve)
21 | def readValue[T: Manifest](src: String): T = mapper.readValue(src, resolve)
22 |
23 | def writeValue(w: Writer, v: Any) { mapper.writeValue(w, v) }
24 | def writeValue(o: OutputStream, v: Any) { mapper.writeValue(o, v) }
25 | def writeValueAsString[T: Manifest](v: T) = writerWithType.writeValueAsString(v)
26 |
27 | def writerWithType[T: Manifest]: ObjectWriter = mapper.writerFor(resolve)
28 |
29 | val cache = JConcurrentMapWrapper(new ConcurrentHashMap[Manifest[_], JavaType])
30 |
31 | def resolve(implicit m: Manifest[_]): JavaType = cache.getOrElseUpdate(m, {
32 | def params = m.typeArguments.map(resolve(_))
33 | val tf = mapper.getTypeFactory
34 | m.typeArguments.isEmpty match {
35 | case true => tf.constructType(m.erasure)
36 | case false => tf.constructParametrizedType(m.erasure, m.erasure, params: _*)
37 | }
38 | })
39 | }
40 |
41 | object JacksMapper extends JacksMapper
42 |
--------------------------------------------------------------------------------
/project/build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | object JacksBuild extends Build {
5 | val buildSettings = Project.defaultSettings ++ Seq(
6 | name := "jacks",
7 | version := "2.5.2",
8 | organization := "com.lambdaworks",
9 | scalaVersion := "2.11.6",
10 |
11 | crossScalaVersions := Seq("2.11.6", "2.10.4"),
12 |
13 | libraryDependencies <+= scalaVersion("org.scala-lang" % "scalap" % _),
14 | libraryDependencies ++= Seq(
15 | "com.fasterxml.jackson.core" % "jackson-databind" % "2.5.2",
16 | "org.scalatest" %% "scalatest" % "2.2.4" % "test",
17 | "org.scalacheck" %% "scalacheck" % "1.12.2" % "test"
18 | ),
19 |
20 | scalacOptions ++= Seq( "-language:_", "-unchecked", "-optimize", "-deprecation"),
21 |
22 | publishArtifact in Test := false,
23 | publishMavenStyle := true,
24 | publishTo <<= version {
25 | val nexus = "https://oss.sonatype.org/"
26 | _.trim.endsWith("SNAPSHOT") match {
27 | case false => Some("releases" at nexus + "service/local/staging/deploy/maven2")
28 | case true => Some("snapshots" at nexus + "content/repositories/snapshots")
29 | }
30 | },
31 |
32 | pomIncludeRepository := { _ => false },
33 | pomExtra := (
34 | http://github.com/wg/jacks
35 |
36 |
37 | scm:git:git://github.com/wg/jacks.git
38 | scm:git:git://github.com/wg/jacks.git
39 | http://github.com/wg/jacks
40 |
41 |
42 |
43 |
44 | Apache License, Version 2.0
45 | http://www.apache.org/licenses/LICENSE-2.0.txt
46 | repo
47 |
48 |
49 |
50 |
51 |
52 | will
53 | Will Glozer
54 |
55 |
56 | )
57 | )
58 |
59 | val jacks = Project(id = "jacks", base = file("."), settings = buildSettings)
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/iterable.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import scala.collection._
6 | import scala.collection.generic._
7 | import scala.collection.mutable.Builder
8 |
9 | import com.fasterxml.jackson.core._
10 | import com.fasterxml.jackson.core.JsonToken._
11 | import com.fasterxml.jackson.databind._
12 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
13 |
14 | class IterableSerializer(t: JavaType) extends StdSerializer[GenIterable[Any]](t) {
15 | override def serialize(iterable: GenIterable[Any], g: JsonGenerator, p: SerializerProvider) {
16 | var s: JsonSerializer[AnyRef] = null
17 | var c: Class[_] = null
18 |
19 | g.writeStartArray()
20 |
21 | for (v <- iterable) {
22 | val a = v.asInstanceOf[AnyRef]
23 | if (a ne null) {
24 | if (a.getClass ne c) {
25 | c = a.getClass
26 | s = p.findValueSerializer(c, null)
27 | }
28 | s.serialize(a, g, p)
29 | } else {
30 | p.defaultSerializeNull(g)
31 | }
32 | }
33 |
34 | g.writeEndArray()
35 | }
36 |
37 | override def isEmpty(v: GenIterable[Any]) = v.isEmpty
38 | }
39 |
40 | abstract class IterableDeserializer[T, C](t: JavaType) extends JsonDeserializer[C] {
41 | def newBuilder: Builder[T, C]
42 |
43 | override def deserialize(p: JsonParser, ctx: DeserializationContext): C = {
44 | val d = ctx.findContextualValueDeserializer(t, null)
45 | val builder = newBuilder
46 |
47 | if (!p.isExpectedStartArrayToken) throw ctx.mappingException(t.getRawClass)
48 |
49 | while (p.nextToken != END_ARRAY) {
50 | val value = p.getCurrentToken match {
51 | case VALUE_NULL => d.getNullValue
52 | case _ => d.deserialize(p, ctx)
53 | }
54 | builder += value.asInstanceOf[T]
55 | }
56 |
57 | builder.result
58 | }
59 | }
60 |
61 | class SeqDeserializer[T, C[T] <: GenTraversable[T]](c: GenericCompanion[C], t: JavaType)
62 | extends IterableDeserializer[T, C[_]](t) {
63 | def newBuilder = c.newBuilder[T]
64 | }
65 |
66 | class SortedSetDeserializer[T, C[T] <: SortedSet[T] with SortedSetLike[T, C[T]]]
67 | (c: SortedSetFactory[C], o: Ordering[T], t: JavaType) extends IterableDeserializer[T, C[_]](t) {
68 | def newBuilder = c.newBuilder[T](o)
69 | }
70 |
71 | class BitSetDeserializer[C <: BitSet](c: BitSetFactory[BitSet], t: JavaType)
72 | extends IterableDeserializer[Int, BitSet](t) {
73 | def newBuilder = c.newBuilder
74 | }
75 |
76 | class OrderedDeserializer[T, C[T] <: Traversable[T] with GenericOrderedTraversableTemplate[T, C]]
77 | (c: OrderedTraversableFactory[C], o: Ordering[T], t: JavaType) extends IterableDeserializer[T, C[_]](t) {
78 | def newBuilder = c.newBuilder[T](o)
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/map.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import scala.collection._
6 | import scala.collection.generic._
7 | import scala.collection.mutable.Builder
8 |
9 | import com.fasterxml.jackson.core._
10 | import com.fasterxml.jackson.core.JsonToken._
11 | import com.fasterxml.jackson.databind._
12 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
13 |
14 | class MapSerializer(t: JavaType) extends StdSerializer[GenMap[Any, Any]](t) {
15 | override def serialize(map: GenMap[Any, Any], g: JsonGenerator, p: SerializerProvider) {
16 | var kS, vS: JsonSerializer[AnyRef] = null
17 | var kC, vC: Class[_] = null
18 |
19 | g.writeStartObject()
20 |
21 | for ((k, v) <- map) {
22 | val kA = k.asInstanceOf[AnyRef]
23 | val vA = v.asInstanceOf[AnyRef]
24 |
25 | if (kA.getClass ne kC) {
26 | kC = kA.getClass
27 | kS = p.findKeySerializer(p.constructType(kC), null)
28 | }
29 | kS.serialize(kA, g, p)
30 |
31 | if (vA ne null) {
32 | if (vA.getClass ne vC) {
33 | vC = vA.getClass
34 | vS = p.findValueSerializer(vC, null)
35 | }
36 | vS.serialize(vA, g, p)
37 | } else {
38 | p.defaultSerializeNull(g)
39 | }
40 | }
41 |
42 | g.writeEndObject()
43 | }
44 |
45 | override def isEmpty(v: GenMap[Any, Any]) = v.isEmpty
46 | }
47 |
48 | abstract class GenMapDeserializer[K, V](k: JavaType, v: JavaType) extends JsonDeserializer[GenMap[_, _]] {
49 | def newBuilder: Builder[(K, V), GenMap[K, V]]
50 |
51 | override def deserialize(p: JsonParser, ctx: DeserializationContext): GenMap[_, _] = {
52 | val kD = ctx.findKeyDeserializer(k, null)
53 | val vD = ctx.findContextualValueDeserializer(v, null)
54 | val builder = newBuilder
55 |
56 | if (p.getCurrentToken != START_OBJECT) throw ctx.mappingException(classOf[GenMap[_, _]])
57 |
58 | while (p.nextToken == FIELD_NAME) {
59 | val name = p.getCurrentName
60 |
61 | val key = kD match {
62 | case kD:KeyDeserializer => kD.deserializeKey(name, ctx)
63 | case null => name
64 | }
65 |
66 | val value = p.nextToken match {
67 | case VALUE_NULL => vD.getNullValue
68 | case _ => vD.deserialize(p, ctx)
69 | }
70 |
71 | builder += ((key.asInstanceOf[K], value.asInstanceOf[V]))
72 | }
73 |
74 | builder.result
75 | }
76 | }
77 |
78 | class MapDeserializer[K, V](f: GenMapFactory[GenMap], k: JavaType, v: JavaType)
79 | extends GenMapDeserializer[K, V](k, v) {
80 | def newBuilder = f.newBuilder
81 | }
82 |
83 | class SortedMapDeserializer[K, V](f: SortedMapFactory[SortedMap], o: Ordering[K], k: JavaType, v: JavaType)
84 | extends GenMapDeserializer[K, V](k, v) {
85 | def newBuilder = f.newBuilder(o)
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/tuple.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.core._
6 | import com.fasterxml.jackson.core.JsonToken._
7 | import com.fasterxml.jackson.databind._
8 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
9 |
10 | import java.lang.reflect.Constructor
11 |
12 | class TupleSerializer(t: JavaType) extends StdSerializer[Product](t) {
13 | override def serialize(value: Product, g: JsonGenerator, p: SerializerProvider) {
14 | var s: JsonSerializer[AnyRef] = null
15 | var c: Class[_] = null
16 |
17 | g.writeStartArray()
18 |
19 | for (v <- value.productIterator) {
20 | val a = v.asInstanceOf[AnyRef]
21 | if (a ne null) {
22 | if (a.getClass ne c) {
23 | c = a.getClass
24 | s = p.findValueSerializer(c, null)
25 | }
26 | s.serialize(a, g, p)
27 | } else {
28 | p.defaultSerializeNull(g)
29 | }
30 | }
31 |
32 | g.writeEndArray()
33 | }
34 | }
35 |
36 | class TupleDeserializer(t: JavaType) extends JsonDeserializer[Product] {
37 | val constructor = findConstructor
38 |
39 | override def deserialize(p: JsonParser, ctx: DeserializationContext): Product = {
40 | val values = new Array[AnyRef](t.containedTypeCount)
41 |
42 | if (!p.isExpectedStartArrayToken) throw ctx.mappingException(t.getRawClass)
43 |
44 | for (i <- 0 until values.length) {
45 | val d = ctx.findContextualValueDeserializer(t.containedType(i), null)
46 | p.nextToken
47 | values(i) = p.getCurrentToken match {
48 | case VALUE_NULL => d.getNullValue
49 | case _ => d.deserialize(p, ctx)
50 | }
51 | }
52 | p.nextToken
53 |
54 | constructor.newInstance(values: _*)
55 | }
56 |
57 | def findConstructor: Constructor[Product] = {
58 | val specials = Set[Class[_]](classOf[Double], classOf[Int], classOf[Long])
59 |
60 | val ts = (for (i <- 0 until t.containedTypeCount) yield t.containedType(i))
61 |
62 | if (ts.length > 2 || ts.exists(t => !specials.contains(t.getRawClass))) {
63 | val types = Array.fill(ts.length)(classOf[AnyRef])
64 | val cls = t.getRawClass.asInstanceOf[Class[Product]]
65 | return cls.getConstructor(types: _*)
66 | }
67 |
68 | val name = new StringBuilder(ts.length + 11)
69 | val types = new Array[Class[_]](ts.length)
70 |
71 | name.append(t.getRawClass.getName).append("$mc")
72 | for (i <- 0 until ts.length) {
73 | name.append(ts(i).getRawClass match {
74 | case c if c == classOf[Double] => "D"
75 | case c if c == classOf[Int] => "I"
76 | case c if c == classOf[Long] => "J"
77 | })
78 | types(i) = ts(i).getRawClass
79 | }
80 | name.append("$sp")
81 |
82 | val cls = Class.forName(name.toString).asInstanceOf[Class[Product]]
83 | cls.getConstructor(types: _*)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | jacks - Jackson module for Scala
2 |
3 | Jacks provides a Jackson Module for reading and writing common Scala data
4 | types, including mutable & immutable collections, Option, Tuple, Symbol,
5 | and case classes.
6 |
7 | This version of jacks has been tested against Scala 2.11.0, 2.10.4 and
8 | Jackson 2.3.3
9 |
10 | Join the lambdaWorks-OSS Google Group to discuss this project:
11 |
12 | http://groups.google.com/group/lambdaworks-oss
13 | lambdaworks-oss@googlegroups.com
14 |
15 | Basic Usage
16 |
17 | The JacksMapper companion object provides a preconfigured ObjectMapper
18 | and a number of read/write methods:
19 |
20 | JacksMapper.writeValueAsString(List(1, 2, 3)) == "[1,2,3]"
21 |
22 | JacksMapper.readValue[List[Int]]("[1, 2, 3]") == List(1, 2, 3)
23 |
24 | The preconfigured ObjectMapper is available via JacksMapper.mapper
25 | and a new ScalaModule may be added to any existing ObjectMapper.
26 |
27 | Erasure & Manifests
28 |
29 | Reading parameterized types requires a Jackson JavaType which can be
30 | derived from a Scala Manifest. The readValue methods of JacksMapper take
31 | an implicit Manifest and look up the appropriate JavaType.
32 |
33 | JacksMapper.resolve(manifest) will also return the correct JavaType
34 | given a Manifest.
35 |
36 | Serialization Details
37 |
38 | Maps are serialized as JSON objects, Seqs and Sets are serialized as
39 | JSON arrays. Tuples are also serialized as JSON arrays.
40 |
41 | None is serialized as null, Some(v) is serialized as v.
42 |
43 | Symbols are serialized as strings.
44 |
45 | Case classes are serialized as JSON objects.
46 |
47 | Collections
48 |
49 | Jacks supports bidirectional mapping of nearly all Scala mutable and
50 | immutable collections. Custom collections types are also supported as
51 | long as they implement a supported interface and provide the
52 | appropriate companion object.
53 |
54 | Case Classes
55 |
56 | Jacks supports bidirectional mapping of case classes as JSON objects,
57 | with a few restrictions: a ScalaSig is required, only fields declared
58 | in the primary constructor are included, and inherited members are
59 | ignored.
60 |
61 | Jackson supports a rich set of annotations for modifying the default
62 | (de)serialization of standard classes and their fields. Jacks will
63 | not support all of these annotations for case classes, but does
64 | support the following:
65 |
66 | @JsonProperty, @JsonIgnore, @JsonIgnoreProperties, @JsonInclude
67 | @JsonCreator
68 |
69 | Jacks normally instantiates case classes via their primary constructor.
70 | Alternatively a method of the companion object may be used instead by
71 | annotating it with @JsonCreator and ensuring that each method parameter
72 | is annotated with @JsonProperty:
73 |
74 | sealed trait AB { @JsonValue def jsonValue: String }
75 | case object A extends AB { def jsonValue = "A" }
76 | case object B extends AB { def jsonValue = "B" }
77 |
78 | object Editor extends Enumeration {
79 | val vi, emacs = Value
80 | }
81 |
82 | case class KitchenSink[T](foo: String, bar: T, v: AB, ed: Editor.Value)
83 |
84 | object KitchenSink {
85 | @JsonCreator
86 | def create[T](
87 | @JsonProperty("foo") foo: String,
88 | @JsonProperty("bar") bar: T,
89 | @JsonProperty("v") v: String = "A",
90 | @JsonProperty("ed") ed: String = "emacs"
91 | ): KitchenSink[T] = KitchenSink[T](foo, bar, if (v == "A") A else B, Editor.withName(ed))
92 | }
93 |
94 | Maven Artifacts
95 |
96 | Releases of jacks are available in the maven central repository:
97 |
98 | "com.lambdaworks" %% "jacks" % "2.3.3"
99 |
100 |
101 | com.lambdaworks
102 | jacks_2.11
103 | 2.3.3
104 |
105 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/case.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.annotation.JsonInclude.Include._
6 | import com.fasterxml.jackson.core._
7 | import com.fasterxml.jackson.core.JsonToken._
8 | import com.fasterxml.jackson.databind._
9 | import com.fasterxml.jackson.databind.ser.std.StdSerializer
10 |
11 | import java.lang.reflect.{Constructor, Method}
12 |
13 | class CaseClassSerializer(t: JavaType, accessors: Array[Accessor]) extends StdSerializer[Product](t) {
14 | override def serialize(value: Product, g: JsonGenerator, p: SerializerProvider) {
15 | g.writeStartObject()
16 |
17 | for (i <- 0 until accessors.length) {
18 | val a = accessors(i)
19 | val v = value.productElement(i).asInstanceOf[AnyRef]
20 | val s = p.findValueSerializer(a.`type`, null)
21 | if (!a.ignored && include(a, p, s, v)) {
22 | g.writeFieldName(a.external)
23 | if (v != null) s.serialize(v, g, p) else p.defaultSerializeNull(g)
24 | }
25 | }
26 |
27 | g.writeEndObject()
28 | }
29 |
30 | @inline final def include(a: Accessor, p: SerializerProvider, s: JsonSerializer[AnyRef], v: AnyRef): Boolean = a.include match {
31 | case ALWAYS => true
32 | case NON_DEFAULT => default(a) != v
33 | case NON_EMPTY => !s.isEmpty(p, v)
34 | case NON_NULL => v != null
35 | }
36 |
37 | @inline final def default(a: Accessor) = a.default match {
38 | case Some(m) => m.invoke(null)
39 | case None => null
40 | }
41 | }
42 |
43 | class CaseClassDeserializer(t: JavaType, c: Creator) extends JsonDeserializer[Any] {
44 | val fields = c.accessors.map(a => a.name -> None).toMap[String, Option[Object]]
45 | val types = c.accessors.map(a => a.name -> a.`type`).toMap
46 | val names = c.accessors.map(a => a.external -> a.name).toMap
47 |
48 | override def deserialize(p: JsonParser, ctx: DeserializationContext): Any = {
49 | var values = fields
50 |
51 | if (p.getCurrentToken != START_OBJECT) throw ctx.mappingException(t.getRawClass)
52 | var token = p.nextToken
53 |
54 | while (token == FIELD_NAME) {
55 | val name = names.getOrElse(p.getCurrentName, null)
56 | val t = types.getOrElse(name, null)
57 | if (t ne null) {
58 | val d = ctx.findContextualValueDeserializer(t, null)
59 | val value = p.nextToken match {
60 | case VALUE_NULL => d.getNullValue
61 | case _ => d.deserialize(p, ctx)
62 | }
63 | values = values.updated(name, Some(value.asInstanceOf[AnyRef]))
64 | } else {
65 | p.nextToken
66 | p.skipChildren
67 | }
68 | token = p.nextToken
69 | }
70 |
71 | val params = c.accessors.map { a =>
72 | values(a.name) match {
73 | case Some(v) => v
74 | case None if !a.required => c.default(a)
75 | case None =>
76 | val msg = "%s missing required field '%s'".format(t.getRawClass.getName, a.name)
77 | throw ctx.mappingException(msg)
78 | }
79 | }
80 |
81 | c(params)
82 | }
83 | }
84 |
85 | trait Creator {
86 | val accessors: Array[Accessor]
87 | def apply(args: Seq[AnyRef]): Any
88 | def default(a: Accessor): AnyRef
89 | }
90 |
91 | class ConstructorCreator(c: Constructor[_], val accessors: Array[Accessor]) extends Creator {
92 | def apply(args: Seq[AnyRef]) = c.newInstance(args: _*)
93 |
94 | def default(a: Accessor) = a.default match {
95 | case Some(m) => m.invoke(null)
96 | case None => null
97 | }
98 | }
99 |
100 | class CompanionCreator(m: Method, c: Object, val accessors: Array[Accessor]) extends Creator {
101 | def apply(args: Seq[AnyRef]) = m.invoke(c, args: _*)
102 |
103 | def default(a: Accessor) = a.default match {
104 | case Some(m) => m.invoke(c)
105 | case None => null
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/src/test/scala/jacks/test.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import com.fasterxml.jackson.annotation._
6 | import com.fasterxml.jackson.annotation.JsonInclude.Include
7 | import com.fasterxml.jackson.databind.JsonMappingException
8 | import com.fasterxml.jackson.databind.ObjectReader
9 |
10 | import org.scalatest.FunSuite
11 | import org.scalatest.Matchers
12 |
13 | case class Primitives(
14 | boolean: Boolean = true,
15 | byte: Byte = 0,
16 | char: Char = 'A',
17 | double: Double = 3.14,
18 | float: Float = 1.23f,
19 | int: Int = 42,
20 | long: Long = 2L,
21 | short: Short = 0
22 | )
23 |
24 | case class Arrays(
25 | bytes: Array[Byte] = Array(1, 2, 3),
26 | strings: Array[String] = Array("a", "b")
27 | )
28 |
29 | case class Aliased(
30 | list: List[Char] = List('A'),
31 | map: Map[String, Int] = Map("one" -> 1),
32 | set: Set[Long] = Set(1, 2, 3),
33 | seq: Seq[Int] = Seq(1, 3),
34 | vector: Vector[Int] = Vector(1, 3),
35 | string: String = "foo"
36 | )
37 |
38 | case class Parameterized[T](value: T)
39 | case class NoDefault(int: Int, string: String)
40 | case class Constructors(var value: String) {
41 | private def this() { this(null) }
42 | private def this(int: Int) { this(int.toString) }
43 | }
44 |
45 | case class Empty(
46 | @JsonInclude(Include.NON_EMPTY) list: List[Int] = Nil,
47 | @JsonInclude(Include.NON_EMPTY) map: Map[String, Int] = Map.empty,
48 | @JsonInclude(Include.NON_EMPTY) option: Option[Int] = None
49 | )
50 |
51 | case class PropertyAnnotation(@JsonProperty("FOO") foo: String)
52 | case class PropertyRequired(@JsonProperty(required = true) foo: String)
53 | case class IgnoreAnnotation(@JsonIgnore foo: String = null, bar: String)
54 | case class IncludeAnnotation(
55 | @JsonInclude(Include.ALWAYS) always: Int = 0,
56 | @JsonInclude(Include.NON_DEFAULT) nonDefault: Int = 1,
57 | @JsonInclude(Include.NON_EMPTY) nonEmpty: Option[Int] = None,
58 | @JsonInclude(Include.NON_NULL) nonNull: String = null
59 | )
60 |
61 | @JsonInclude(Include.NON_NULL)
62 | @JsonIgnoreProperties(Array("ignored"))
63 | case class ClassAnnotations(foo: String = null, ignored: String = "bar")
64 |
65 | sealed trait AB { @JsonValue def jsonValue: String }
66 | case object A extends AB { def jsonValue = "A" }
67 | case object B extends AB { def jsonValue = "B" }
68 |
69 | object Editor extends Enumeration {
70 | val vi, emacs = Value
71 | }
72 |
73 | case class KitchenSink[T](foo: String, bar: T, v: AB, ed: Editor.Value)
74 |
75 | object KitchenSink {
76 | @JsonCreator
77 | def create[T](
78 | @JsonProperty("foo") foo: String,
79 | @JsonProperty("bar") bar: T,
80 | @JsonProperty("v") v: String = "A",
81 | @JsonProperty("ed") ed: String = "emacs"
82 | ): KitchenSink[T] = KitchenSink[T](foo, bar, if (v == "A") A else B, Editor.withName(ed))
83 | }
84 |
85 | object Outer {
86 | case class Nested(s: String)
87 | }
88 |
89 | case class NamingStrategy(camelCase: String, PascalCase: Int)
90 |
91 | case class WithAny(map: Map[String, Any])
92 |
93 | class CaseClassSuite extends JacksTestSuite {
94 | test("primitive types correct") {
95 | rw(Primitives(boolean = false)) should equal (Primitives(boolean = false))
96 | rw(Primitives(byte = 1)) should equal (Primitives(byte = 1))
97 | rw(Primitives(char = 'B')) should equal (Primitives(char = 'B'))
98 | rw(Primitives(double = 1.23)) should equal (Primitives(double = 1.23))
99 | rw(Primitives(float = 3.14f)) should equal (Primitives(float = 3.14f))
100 | rw(Primitives(int = 0)) should equal (Primitives(int = 0))
101 | rw(Primitives(long = 1L)) should equal (Primitives(long = 1L))
102 | rw(Primitives(short = 4)) should equal (Primitives(short = 4))
103 | }
104 |
105 | test("default used correctly") {
106 | rw(Primitives()).boolean should equal (true)
107 | rw(Primitives()).byte should equal (0)
108 | rw(Primitives()).char should equal ('A')
109 | rw(Primitives()).double should equal (3.14)
110 | rw(Primitives()).float should equal (1.23f)
111 | rw(Primitives()).int should equal (42)
112 | rw(Primitives()).long should equal (2L)
113 | rw(Primitives()).short should equal (0)
114 |
115 | rw(Arrays()).bytes should equal (Array[Byte](1, 2, 3))
116 | rw(Aliased()).list should equal (List[Char]('A'))
117 | }
118 |
119 | test("array types correct") {
120 | rw(Arrays(bytes = Array(1, 2))).bytes should equal (Array[Byte](1, 2))
121 | rw(Arrays(strings = Array("1", "2"))).strings should equal (Array[String]("1", "2"))
122 | }
123 |
124 | test("aliased types correct") {
125 | rw(Aliased(list = List('B'))) should equal (Aliased(list = List('B')))
126 | rw(Aliased(map = Map("A" -> 1))) should equal (Aliased(map = Map("A" -> 1)))
127 | rw(Aliased(set = Set(2, 4))) should equal (Aliased(set = Set(2, 4)))
128 | rw(Aliased(seq = Seq(1, 2))) should equal (Aliased(seq = Seq(1, 2)))
129 | rw(Aliased(vector = Vector(1, 2))) should equal (Aliased(vector = Vector(1, 2)))
130 | rw(Aliased(string = "test")) should equal (Aliased(string = "test"))
131 | }
132 |
133 | test("parameterized types correct") {
134 | rw(Parameterized[String]("foo")) should equal (Parameterized[String]("foo"))
135 | rw(Parameterized[Int](1)) should equal (Parameterized[Int](1))
136 | rw(Parameterized[List[Int]](List(1))) should equal (Parameterized[List[Int]](List(1)))
137 | }
138 |
139 | test("unknown properties skipped") {
140 | val json = """{"skip": [1], "set": [17, 23]}"""
141 | read[Aliased](json) should equal (Aliased(set = Set(17, 23)))
142 | }
143 |
144 | test("property without default is null") {
145 | read[NoDefault]("""{"int": 1}""").string should equal (null)
146 | rw(NoDefault(1, null)) should equal (NoDefault(1, null))
147 | }
148 |
149 | test("correct constructor found") {
150 | rw(Constructors("value")) should equal (Constructors("value"))
151 | }
152 |
153 | test("JsonGenerator.isEmpty correct") {
154 | write(Empty()) should equal ("""{}""")
155 | write(Empty(list = List(1))) should equal ("""{"list":[1]}""")
156 | write(Empty(map = Map("one" -> 1))) should equal ("""{"map":{"one":1}}""")
157 | write(Empty(option = Some(1))) should equal ("""{"option":1}""")
158 | }
159 |
160 | test("@JsonProperty handled correctly") {
161 | val json = """{"FOO":"a"}"""
162 | write(PropertyAnnotation("a")) should equal (json)
163 | read[PropertyAnnotation](json) should equal (PropertyAnnotation("a"))
164 | }
165 |
166 | test("@JsonProperty(required = true) handled correctly") {
167 | intercept[JsonMappingException] { read[PropertyRequired]("""{}""") }
168 | rw(PropertyRequired(null)) should equal (PropertyRequired(null))
169 | }
170 |
171 | test("@JsonIgnore handled correctly") {
172 | val json = """{"bar":"b"}"""
173 | write(IgnoreAnnotation("a", "b")) should equal (json)
174 | read[IgnoreAnnotation](json) should equal (IgnoreAnnotation(null, "b"))
175 | }
176 |
177 | test("@JsonIgnoreProperties handled correctly") {
178 | write(ClassAnnotations("a")) should equal ("""{"foo":"a"}""")
179 | }
180 |
181 | test("@JsonInclude handled correctly") {
182 | write(IncludeAnnotation()) should equal ("""{"always":0}""")
183 | write(IncludeAnnotation(nonDefault = 3)) should equal ("""{"always":0,"nonDefault":3}""")
184 | write(IncludeAnnotation(nonEmpty = Some(2))) should equal ("""{"always":0,"nonEmpty":2}""")
185 | write(IncludeAnnotation(nonNull = "a")) should equal ("""{"always":0,"nonNull":"a"}""")
186 |
187 | write(ClassAnnotations()) should equal ("""{}""")
188 | write(ClassAnnotations("a")) should equal ("""{"foo":"a"}""")
189 | }
190 |
191 | test("@JsonCreator handled correctly") {
192 | val kss = KitchenSink[String]("f", "s", B, Editor.vi)
193 | val ksi = KitchenSink[Int]("g", 1, A, Editor.emacs)
194 | rw(kss) should equal (kss)
195 | read[KitchenSink[Int]]("""{"foo":"g","bar":1}""") should equal (ksi)
196 | }
197 |
198 | test("nested case classes handled correctly") {
199 | rw(Outer.Nested("foo")) should equal (Outer.Nested("foo"))
200 | }
201 |
202 | test("PropertyNamingStrategy is used correctly") {
203 | import com.fasterxml.jackson.databind.PropertyNamingStrategy._
204 |
205 | val m = new Object with JacksMapper
206 | m.mapper.setPropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)
207 |
208 | val obj = NamingStrategy("foo", 1)
209 | val json = """{"camel_case":"foo","pascal_case":1}"""
210 |
211 | m.writeValueAsString(obj) should equal (json)
212 | m.readValue[NamingStrategy](json) should equal (obj)
213 | }
214 |
215 | test("Map[String, Any] handled correctly") {
216 | val map = Map[String, Any]("foo" -> 1, "bar" -> "2")
217 | rw(WithAny(map)) should equal (WithAny(map))
218 | }
219 | }
220 |
221 | class InvalidJsonSuite extends JacksTestSuite {
222 | test("deserializing case class from non-object throws JsonMappingException") {
223 | intercept[JsonMappingException] { read[Primitives]("123") }
224 | }
225 |
226 | test("deserializing Iterable from non-array throws JsonMappingException") {
227 | intercept[JsonMappingException] { read[List[Int]]("123") }
228 | }
229 |
230 | test("deserializing Map from non-object throws JsonMappingException") {
231 | intercept[JsonMappingException] { read[Map[String, Any]]("123") }
232 | }
233 |
234 | test("deserializing Tuple from non-array throws JsonMappingException") {
235 | intercept[JsonMappingException] { read[Tuple2[Any, Any]]("123") }
236 | }
237 | }
238 |
239 | class JacksMapperSuite extends JacksTestSuite {
240 | import java.io._
241 | import java.nio.charset.Charset
242 | import JacksMapper._
243 |
244 | val ASCII = Charset.forName("US-ASCII")
245 |
246 | test("readValue overloads correct") {
247 | val json = "[1, 2, 3]"
248 |
249 | val bytes = json.getBytes(ASCII)
250 | val stream = new ByteArrayInputStream(bytes)
251 | val reader = new InputStreamReader(new ByteArrayInputStream(bytes), ASCII)
252 |
253 | readValue[List[Int]](bytes) should equal (List(1, 2, 3))
254 | readValue[List[Int]](stream) should equal (List(1, 2, 3))
255 | readValue[List[Int]](reader) should equal (List(1, 2, 3))
256 | }
257 |
258 | test("writeValue overloads correct") {
259 | val list = List(1, 2, 3)
260 | val bytes = writeValueAsString(list).getBytes(ASCII)
261 |
262 | var stream = new ByteArrayOutputStream
263 | writeValue(stream, list)
264 | stream.toByteArray should equal (bytes)
265 |
266 | stream = new ByteArrayOutputStream
267 | writeValue(new OutputStreamWriter(stream, ASCII), list)
268 | stream.toByteArray should equal (bytes)
269 | }
270 |
271 | test("resolve caches JavaType") {
272 | val m = manifest[String]
273 | resolve(m) should be theSameInstanceAs resolve(m)
274 | }
275 | }
276 |
277 | class UntypedObjectDeserializerSuite extends JacksTestSuite {
278 | import com.fasterxml.jackson.databind.DeserializationFeature._
279 | import JacksMapper._
280 |
281 | test("reading JSON object returns Scala Map") {
282 | read[AnyRef]("""{"one":1,"two":[2]}""") should equal (Map("one" -> 1, "two" -> List(2)))
283 | }
284 |
285 | test("reading JSON array returns Scala List") {
286 | read[AnyRef]("[1, 2, 3]") should equal (List(1, 2, 3))
287 | }
288 |
289 | test("reading JSON array returns Java Array") {
290 | val r = mapper.reader[ObjectReader](USE_JAVA_ARRAY_FOR_JSON_ARRAY).forType(classOf[AnyRef])
291 | r.readValue[AnyRef]("[1, 2, 3]") should equal (Array(1, 2, 3))
292 | }
293 | }
294 |
295 | trait JacksTestSuite extends FunSuite with Matchers {
296 | import JacksMapper._
297 |
298 | def rw[T: Manifest](v: T) = read(write(v))
299 | def write[T: Manifest](v: T) = writeValueAsString(v)
300 | def read[T: Manifest](s: String) = readValue(s)
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/scala/jacks/module.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import scala.collection._
6 | import scala.collection.generic._
7 | import scala.collection.mutable.PriorityQueue
8 |
9 | import com.fasterxml.jackson.annotation._
10 | import com.fasterxml.jackson.annotation.JsonInclude.Include
11 | import com.fasterxml.jackson.annotation.JsonInclude.Include._
12 |
13 | import com.fasterxml.jackson.core._
14 | import com.fasterxml.jackson.databind._
15 | import com.fasterxml.jackson.databind.cfg.MapperConfig
16 | import com.fasterxml.jackson.databind.deser._
17 | import com.fasterxml.jackson.databind.ser._
18 | import com.fasterxml.jackson.databind.`type`._
19 | import com.fasterxml.jackson.databind.introspect._
20 |
21 | import java.lang.annotation.Annotation
22 | import java.lang.reflect.{Constructor, Method}
23 |
24 | import tools.scalap.scalax.rules.scalasig.ScalaSig
25 |
26 | class ScalaModule extends Module {
27 | def version = new Version(2, 3, 3, null, "com.lambdaworks", "jacks")
28 | def getModuleName = "ScalaModule"
29 |
30 | def setupModule(ctx: Module.SetupContext) {
31 | ctx.addSerializers(new ScalaSerializers)
32 | ctx.addDeserializers(new ScalaDeserializers)
33 | }
34 | }
35 |
36 | class ScalaDeserializers extends Deserializers.Base {
37 | override def findBeanDeserializer(t: JavaType, cfg: DeserializationConfig, bd: BeanDescription): JsonDeserializer[_] = {
38 | val cls = t.getRawClass
39 |
40 | if (classOf[GenTraversable[_]].isAssignableFrom(cls)) {
41 | if (classOf[GenSeq[_]].isAssignableFrom(cls)) {
42 | val c = companion[GenericCompanion[GenSeq]](cls)
43 | new SeqDeserializer[Any, GenSeq](c, t.containedType(0))
44 | } else if (classOf[SortedMap[_, _]].isAssignableFrom(cls)) {
45 | val c = companion[SortedMapFactory[SortedMap]](cls)
46 | val o = ordering(t.containedType(0))
47 | new SortedMapDeserializer[Any, Any](c, o, t.containedType(0), t.containedType(1))
48 | } else if (classOf[GenMap[_, _]].isAssignableFrom(cls)) {
49 | val c = companion[GenMapFactory[GenMap]](cls)
50 | new MapDeserializer[Any, Any](c, t.containedType(0), t.containedType(1))
51 | } else if (classOf[GenSet[_]].isAssignableFrom(cls)) {
52 | if (classOf[BitSet].isAssignableFrom(cls)) {
53 | val c = companion[BitSetFactory[BitSet]](cls)
54 | val t = cfg.getTypeFactory.constructType(classOf[Int])
55 | new BitSetDeserializer[BitSet](c, t)
56 | } else if (classOf[SortedSet[_]].isAssignableFrom(cls)) {
57 | val c = companion[SortedSetFactory[SortedSet]](cls)
58 | val o = ordering(t.containedType(0))
59 | new SortedSetDeserializer[Any, SortedSet](c, o, t.containedType(0))
60 | } else if (classOf[BitSet].isAssignableFrom(cls)) {
61 | val c = companion[BitSetFactory[BitSet]](cls)
62 | val t = cfg.getTypeFactory.constructType(classOf[Int])
63 | new BitSetDeserializer[BitSet](c, t)
64 | } else {
65 | val c = companion[GenericCompanion[GenSet]](cls)
66 | new SeqDeserializer[Any, GenSet](c, t.containedType(0))
67 | }
68 | } else if (classOf[PriorityQueue[_]].isAssignableFrom(cls)) {
69 | val c = companion[OrderedTraversableFactory[PriorityQueue]](cls)
70 | val o = ordering(t.containedType(0))
71 | new OrderedDeserializer[Any, PriorityQueue](c, o, t.containedType(0))
72 | } else {
73 | null
74 | }
75 | } else if (classOf[Option[_]].isAssignableFrom(cls)) {
76 | new OptionDeserializer(t.containedType(0))
77 | } else if (classOf[Product].isAssignableFrom(cls) && cls.getName.startsWith("scala.Tuple")) {
78 | new TupleDeserializer(t)
79 | } else if (classOf[Product].isAssignableFrom(cls)) {
80 | ScalaTypeSig(cfg.getTypeFactory, t) match {
81 | case Some(sts) if sts.isCaseClass => new CaseClassDeserializer(t, sts.creator(cfg))
82 | case _ => null
83 | }
84 | } else if (classOf[Symbol].isAssignableFrom(cls)) {
85 | new SymbolDeserializer
86 | } else if (classOf[AnyRef].equals(cls)) {
87 | new UntypedObjectDeserializer(cfg)
88 | } else {
89 | null
90 | }
91 | }
92 |
93 | def companion[T](cls: Class[_]): T = {
94 | Class.forName(cls.getName + "$").getField("MODULE$").get(null).asInstanceOf[T]
95 | }
96 |
97 | lazy val orderings = Map[Class[_], Ordering[_]](
98 | classOf[Boolean] -> Ordering.Boolean,
99 | classOf[Byte] -> Ordering.Byte,
100 | classOf[Char] -> Ordering.Char,
101 | classOf[Double] -> Ordering.Double,
102 | classOf[Int] -> Ordering.Int,
103 | classOf[Float] -> Ordering.Float,
104 | classOf[Long] -> Ordering.Long,
105 | classOf[Short] -> Ordering.Short,
106 | classOf[String] -> Ordering.String,
107 | classOf[BigInt] -> Ordering.BigInt,
108 | classOf[BigDecimal] -> Ordering.BigDecimal)
109 |
110 | def ordering(t: JavaType): Ordering[Any] = {
111 | val cls = t.getRawClass
112 | orderings.getOrElse(cls, {
113 | val orderings = for (i <- 0 until t.containedTypeCount) yield ordering(t.containedType(0))
114 | val params = Array.fill(orderings.length)(classOf[Ordering[_]])
115 | val method = Ordering.getClass.getMethod(cls.getSimpleName, params:_*)
116 | method.invoke(Ordering, orderings:_*)
117 | }).asInstanceOf[Ordering[Any]]
118 | }
119 | }
120 |
121 | class ScalaSerializers extends Serializers.Base {
122 | override def findSerializer(cfg: SerializationConfig, t: JavaType, bd: BeanDescription): JsonSerializer[_] = {
123 | val cls = t.getRawClass
124 |
125 | if (classOf[GenMap[_, _]].isAssignableFrom(cls)) {
126 | new MapSerializer(t)
127 | } else if (classOf[GenIterable[_]].isAssignableFrom(cls)) {
128 | var vT = t.containedType(0)
129 | if (vT == null) vT = cfg.getTypeFactory.constructType(classOf[AnyRef])
130 | new IterableSerializer(vT)
131 | } else if (classOf[Option[_]].isAssignableFrom(cls)) {
132 | new OptionSerializer(t)
133 | } else if (classOf[Product].isAssignableFrom(cls) && cls.getName.startsWith("scala.Tuple")) {
134 | new TupleSerializer(t)
135 | } else if (classOf[Product].isAssignableFrom(cls)) {
136 | ScalaTypeSig(cfg.getTypeFactory, t) match {
137 | case Some(sts) if sts.isCaseClass => new CaseClassSerializer(t, sts.annotatedAccessors(cfg))
138 | case _ => null
139 | }
140 | } else if (classOf[Symbol].isAssignableFrom(cls)) {
141 | new SymbolSerializer(t)
142 | } else if (classOf[Enumeration$Val].isAssignableFrom(cls)) {
143 | ser.std.ToStringSerializer.instance
144 | } else {
145 | null
146 | }
147 | }
148 | }
149 |
150 | case class Accessor(
151 | name: String,
152 | `type`: JavaType,
153 | default: Option[Method],
154 | external: String,
155 | ignored: Boolean = false,
156 | required: Boolean = false,
157 | include: Include = ALWAYS
158 | )
159 |
160 | class ScalaTypeSig(val tf: TypeFactory, val `type`: JavaType, val sig: ScalaSig) {
161 | import tools.scalap.scalax.rules.scalasig.{Method => _, _}
162 | import ScalaTypeSig.findClass
163 |
164 | val cls = {
165 | val name = `type`.getRawClass.getCanonicalName.replace('$', '.')
166 | sig.symbols.collectFirst {
167 | case c:ClassSymbol if c.path == name => c
168 | }.get
169 | }
170 |
171 | def isCaseClass = cls.isCase
172 |
173 | def constructor(cfg: MapperConfig[_]): Constructor[_] = {
174 | val types = accessors(cfg).map(_.`type`.getRawClass)
175 | `type`.getRawClass.getDeclaredConstructors.find { c =>
176 | val pairs = c.getParameterTypes.zip(types)
177 | pairs.length == types.length && pairs.forall {
178 | case (a, b) => a.isAssignableFrom(b) || (a == classOf[AnyRef] && b.isPrimitive)
179 | }
180 | }.get
181 | }
182 |
183 | def default(cls: Class[_], method: String, index: Int): Option[Method] = try {
184 | val name = "%s$default$%d".format(method, index)
185 | Some(cls.getDeclaredMethod(name))
186 | } catch {
187 | case e:NoSuchMethodException => None
188 | }
189 |
190 | def external(cfg: MapperConfig[_], name: String) = {
191 | val strategy = cfg.getPropertyNamingStrategy
192 | var external = name
193 | if (strategy != null) {
194 | external = strategy.nameForConstructorParameter(cfg, null, name)
195 | }
196 | external
197 | }
198 |
199 | def accessors(cfg: MapperConfig[_]): List[Accessor] = {
200 | var index = 1
201 |
202 | cls.children.foldLeft(List.newBuilder[Accessor]) {
203 | (accessors, c) =>
204 | if (c.isCaseAccessor && !c.isPrivate) {
205 | val sym = c.asInstanceOf[MethodSymbol]
206 | val name = sym.name
207 | val typ = resolve(sym.infoType)
208 | val dflt = default(`type`.getRawClass, "apply", index)
209 | accessors += Accessor(name, typ, dflt, external(cfg, name))
210 | index += 1
211 | }
212 | accessors
213 | }.result
214 | }
215 |
216 | def annotatedAccessors(cfg: MapperConfig[_]): Array[Accessor] = {
217 | def property(acc: Accessor, a: JsonProperty): Accessor = {
218 | val name = if (a.value != "") a.value else acc.name
219 | acc.copy(name = name, required = a.required, external = name)
220 | }
221 |
222 | classAnnotatedAccessors(cfg).zip(constructor(cfg).getParameterAnnotations).map {
223 | case (accessor: Accessor, annotations: Array[Annotation]) =>
224 | annotations.foldLeft(accessor) {
225 | case (acc, a:JsonProperty) => property(acc, a)
226 | case (acc, a:JsonIgnore) => acc.copy(ignored = a.value)
227 | case (acc, a:JsonInclude) => acc.copy(include = a.value)
228 | case (acc, _) => acc
229 | }
230 | }.toArray
231 | }
232 |
233 | def classAnnotatedAccessors(cfg: MapperConfig[_]): List[Accessor] = {
234 | `type`.getRawClass.getAnnotations.foldLeft(accessors(cfg)) {
235 | case (accs, ignore: JsonIgnoreProperties) =>
236 | val ignored = ignore.value.toSet
237 | accs.map(a => a.copy(ignored = ignored.contains(a.name)))
238 | case (accs, include: JsonInclude) =>
239 | accs.map(a => a.copy(include = include.value))
240 | case (accs, _) =>
241 | accs
242 | }
243 | }
244 |
245 | def creatorAccessors(cfg: MapperConfig[_], m: Method): Array[Accessor] = {
246 | val cls = m.getDeclaringClass
247 | var index = 0
248 | m.getGenericParameterTypes.zip(m.getParameterAnnotations).map {
249 | case (t: java.lang.reflect.Type, annotations: Array[Annotation]) =>
250 | index += 1
251 | val Some(a) = annotations.find(_.isInstanceOf[JsonProperty])
252 | val name = a.asInstanceOf[JsonProperty].value
253 | val dflt = default(cls, m.getName, index)
254 | Accessor(name, tf.constructType(t), dflt, external(cfg, name))
255 | }
256 | }
257 |
258 | def creator(cfg: DeserializationConfig): Creator = {
259 | val c = findClass(`type`.getRawClass.getName + "$").getField("MODULE$").get(null)
260 | c.getClass.getDeclaredMethods.find(_.getAnnotation(classOf[JsonCreator]) != null) match {
261 | case Some(m) => new CompanionCreator(m, c, creatorAccessors(cfg, m))
262 | case None => new ConstructorCreator(constructor(cfg), annotatedAccessors(cfg))
263 | }
264 | }
265 |
266 | lazy val contained = (0 until `type`.containedTypeCount).map {
267 | i => (`type`.containedTypeName(i) -> `type`.containedType(i))
268 | }.toMap
269 |
270 | def resolve(t: Type): JavaType = t match {
271 | case NullaryMethodType(t2) =>
272 | resolve(t2)
273 | case TypeRefType(_, TypeSymbol(s), Nil) =>
274 | contained(s.name)
275 | case TypeRefType(_, s, Nil) =>
276 | tf.constructType(ScalaTypeSig.resolve(s))
277 | case TypeRefType(_, s, a :: Nil) if s.path == "scala.Array" =>
278 | ArrayType.construct(resolve(a), null, null)
279 | case TypeRefType(_, s, args) =>
280 | val params = args.map(resolve(_))
281 | val actual = ScalaTypeSig.resolve(s)
282 | tf.constructParametrizedType(actual, actual, params: _ *)
283 | }
284 | }
285 |
286 | object ScalaTypeSig {
287 | import tools.scalap.scalax.rules.scalasig.{ScalaSigParser, Symbol}
288 | import scala.collection.immutable._
289 |
290 | def apply(tf: TypeFactory, t: JavaType): Option[ScalaTypeSig] = {
291 | def findSig(cls: Class[_]): Option[ScalaSig] = {
292 | ScalaSigParser.parse(cls) orElse {
293 | val enc = cls.getEnclosingClass
294 | if (enc != null) findSig(enc) else None
295 | }
296 | }
297 |
298 | findSig(t.getRawClass) match {
299 | case Some(sig) => Some(new ScalaTypeSig(tf, t, sig))
300 | case None => None
301 | }
302 | }
303 |
304 | val types = Map[String, Class[_]](
305 | "scala.Boolean" -> classOf[Boolean],
306 | "scala.Byte" -> classOf[Byte],
307 | "scala.Char" -> classOf[Char],
308 | "scala.Double" -> classOf[Double],
309 | "scala.Int" -> classOf[Int],
310 | "scala.Float" -> classOf[Float],
311 | "scala.Long" -> classOf[Long],
312 | "scala.Short" -> classOf[Short],
313 | "scala.Any" -> classOf[Any],
314 | "scala.AnyRef" -> classOf[AnyRef],
315 | "scala.Predef.Map" -> classOf[Map[_, _]],
316 | "scala.Predef.Set" -> classOf[Set[_]],
317 | "scala.Predef.String" -> classOf[String],
318 | "scala.package.List" -> classOf[List[_]],
319 | "scala.package.Seq" -> classOf[Seq[_]],
320 | "scala.package.Vector" -> classOf[Vector[_]],
321 | "scala.Enumeration.Value" -> classOf[Enumeration$Val]
322 | ).withDefault(findClass(_))
323 |
324 | def resolve(s: Symbol) = types(s.path)
325 |
326 | def findClass(name: String): Class[_] = {
327 | val cl = Thread.currentThread().getContextClassLoader()
328 | Class.forName(name, true, cl)
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/src/test/scala/jacks/spec.scala:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2011 - Will Glozer. All rights reserved.
2 |
3 | package com.lambdaworks.jacks
4 |
5 | import org.scalacheck._
6 | import org.scalacheck.Prop.forAll
7 |
8 | import scala.beans.BeanProperty
9 |
10 | object ImmutableCollectionSpec extends ScalaModuleSpec("") {
11 | import scala.collection.immutable._
12 | import ImmutableCollections._
13 |
14 | // Seq
15 | property("Vector[Int]") = forAll { (c: Vector[Int]) => c == rw(c) }
16 | property("List[Boolean]") = forAll { (c: List[Boolean]) => c == rw(c) }
17 | property("Stream[Int]") = forAll { (c: Stream[Int]) => c == rw(c) }
18 | property("Queue[Char]") = forAll { (c: Queue[Char]) => c == rw(c) }
19 |
20 | // Set
21 | property("Set[Boolean]") = forAll { (s: Set[Boolean]) => s == rw(s) }
22 | property("BitSet") = forAll { (s: BitSet) => s == rw(s) }
23 | property("HashSet[Byte]") = forAll { (s: HashSet[Byte]) => s == rw(s) }
24 | property("ListSet[Char]") = forAll { (s: ListSet[Char]) => s == rw(s) }
25 |
26 | // complex types
27 | property("List[Bean]") = forAll { (c: List[Bean]) => c == rw(c) }
28 | property("List[CaseClass]") = forAll { (c: List[CaseClass]) => c == rw(c) }
29 | property("List[(Int, Int)]") = forAll { (c: List[(Int, Int)]) => c == rw(c) }
30 | }
31 |
32 | object MutableCollectionSpec extends ScalaModuleSpec("") {
33 | import scala.collection.mutable._
34 | import MutableCollections._
35 |
36 | // IndexedSeq, Buffer
37 | property("ArraySeq[Boolean]") = forAll { (c: ArraySeq[Boolean]) => c == rw(c) }
38 | property("ArrayBuffer[Int]") = forAll { (c: ArrayBuffer[Int]) => c == rw(c) }
39 | property("ListBuffer[Char]") = forAll { (c: ListBuffer[Char]) => c == rw(c) }
40 |
41 | // Seq (ArrayStack Builder is broken in Scala 2.9.1)
42 | property("Stack[Int]") = forAll { (c: Stack[Int]) => c == rw(c) }
43 | //property("ArrayStack[Int]") = forAll { (c: ArrayStack[Int]) => c == rw(c) }
44 | property("PriorityQueue[Int]") = forAll { (c: PriorityQueue[Int]) => c.toList == rw(c).toList }
45 |
46 | // LinearSeq
47 | property("MutableList[Int]") = forAll { (c: MutableList[Int]) => c == rw(c) }
48 | property("Queue[Int]") = forAll { (c: Queue[Int]) => c == rw(c) }
49 |
50 | // Set
51 | property("Set[Boolean]") = forAll { (s: Set[Boolean]) => s == rw(s) }
52 | property("BitSet") = forAll { (s: BitSet) => s == rw(s) }
53 | property("HashSet[Byte]") = forAll { (s: HashSet[Byte]) => s == rw(s) }
54 | property("LinkedHashSet[Char]") = forAll { (s: LinkedHashSet[Char]) => s == rw(s) }
55 |
56 | property("null") = List("one", null) == rw(List("one", null))
57 | }
58 |
59 | object ImmutableMapSpec extends ScalaModuleSpec("") {
60 | import scala.collection.immutable._
61 | import ImmutableCollections._
62 |
63 | property("Map[String,Boolean]") = forAll { (m: Map[String,Boolean]) => m == rw(m) }
64 | property("Map[String,Byte]") = forAll { (m: Map[String,Byte]) => m == rw(m) }
65 | property("Map[String,Char]") = forAll { (m: Map[String,Char]) => m == rw(m) }
66 | property("Map[String,Double]") = forAll { (m: Map[String,Double]) => m == rw(m) }
67 | property("Map[String,Float]") = forAll { (m: Map[String,Float]) => m == rw(m) }
68 | property("Map[String,Int]") = forAll { (m: Map[String,Int]) => m == rw(m) }
69 | property("Map[String,Long]") = forAll { (m: Map[String,Long]) => m == rw(m) }
70 | property("Map[String,Short]") = forAll { (m: Map[String,Short]) => m == rw(m) }
71 | property("Map[String,String]") = forAll { (m: Map[String,String]) => m == rw(m) }
72 |
73 | property("HashMap[String,Int]") = forAll { (m: HashMap[String,Int]) => m == rw(m) }
74 | property("ListMap[String,Int]") = forAll { (m: ListMap[String,Int]) => m == rw(m) }
75 |
76 | property("Map[String,null]") = Map("one" -> null) == rw(Map("one" -> null))
77 |
78 | property("Map[String,List[Int]]") = forAll { (m: Map[String,List[Int]]) => m == rw(m) }
79 | property("Map[String,(Int,Int)]") = forAll { (m: Map[String,(Int,Int)]) => m == rw(m) }
80 |
81 | property("Map[Map[String,Bean]]") = forAll { (m: Map[String,Bean]) => m == rw(m) }
82 | }
83 |
84 | object MutableMapSpec extends ScalaModuleSpec("") {
85 | import scala.collection.mutable._
86 | import MutableCollections._
87 |
88 | property("HashMap[String,Int]") = forAll { (m: HashMap[String,Int]) => m == rw(m) }
89 | property("LinkedHashMap[String,Int]") = forAll { (m: LinkedHashMap[String,Int]) => m == rw(m) }
90 | property("ListMap[String,Int]") = forAll { (m: ListMap[String,Int]) => m == rw(m) }
91 |
92 | property("WeakHashMap[String,Int]") = forAll {
93 | (m: HashMap[String,Int]) =>
94 | val weakMap = WeakHashMap.empty ++ m
95 | weakMap == rw(weakMap)
96 | }
97 |
98 | // unsupported
99 | //property("OpenHashMap[String,Int]") = forAll { (m: OpenHashMap[String,Int]) => m == rw(m) }
100 | }
101 |
102 | object SortedSetSpec extends ScalaModuleSpec("") {
103 | import scala.collection.immutable.TreeSet
104 | import ImmutableCollections._
105 |
106 | property("TreeSet[Boolean]") = forAll { (c: TreeSet[Boolean]) => c == rw(c) }
107 | property("TreeSet[Byte]") = forAll { (c: TreeSet[Byte]) => c == rw(c) }
108 | property("TreeSet[Char]") = forAll { (c: TreeSet[Char]) => c == rw(c) }
109 | property("TreeSet[Double]") = forAll { (c: TreeSet[Double]) => c == rw(c) }
110 | property("TreeSet[Float]") = forAll { (c: TreeSet[Float]) => c == rw(c) }
111 | property("TreeSet[Int]") = forAll { (c: TreeSet[Int]) => c == rw(c) }
112 | property("TreeSet[Long]]") = forAll { (c: TreeSet[Long]) => c == rw(c) }
113 | property("TreeSet[Short]]") = forAll { (c: TreeSet[Short]) => c == rw(c) }
114 | property("TreeSet[String]") = forAll { (c: TreeSet[String]) => c == rw(c) }
115 | property("TreeSet[(Int, Int)]") = forAll { (c: TreeSet[(Int, Int)]) => c == rw(c) }
116 | property("TreeSet[Option[Int]]") = forAll { (c: TreeSet[Option[Int]]) => c == rw(c) }
117 | }
118 |
119 | object SortedMapSpec extends ScalaModuleSpec("") {
120 | import scala.collection.immutable.TreeMap
121 | import ImmutableCollections._
122 |
123 | property("TreeMap[String,Int]") = forAll { (c: TreeMap[String,Int]) => c == rw(c) }
124 | }
125 |
126 | object CollectionWrapperSpec extends ScalaModuleSpec("") {
127 | import scala.collection.JavaConversions._
128 |
129 | property("List -> JList") = forAll { (l: List[String]) => seqAsJavaList(l) == rw(l, seqAsJavaList(l)) }
130 | property("JList -> List") = forAll { (l: List[String]) => l == rw(seqAsJavaList(l), l) }
131 | property("Set -> JSet") = forAll { (s: Set[String]) => setAsJavaSet(s) == rw(s, setAsJavaSet(s)) }
132 | property("JSet -> Set") = forAll { (s: Set[String]) => s == rw(setAsJavaSet(s), s) }
133 | property("Map -> JMap") = forAll { (m: Map[String,String]) => mapAsJavaMap(m) == rw(m, mapAsJavaMap(m)) }
134 | property("JMap -> Map") = forAll { (m: Map[String,String]) => m == rw(mapAsJavaMap(m), m) }
135 | }
136 |
137 | object OptionSpec extends ScalaModuleSpec("Option") {
138 | import scala.collection._
139 | import ScalaCollections._
140 |
141 | // root level Options are not supported, Jackson returns null
142 | property("List") = forAll { (c: List[Option[String]]) => c == rw(c) }
143 | property("Map") = forAll { (c: Map[String, Option[String]]) => c == rw(c) }
144 | property("Set") = forAll { (c: Set[Option[String]]) => c == rw(c) }
145 | property("(Int, Int)") = forAll { (o: Option[(Int, Int)]) => o == rw(o) }
146 | }
147 |
148 | object TupleSpec extends ScalaModuleSpec("Tuple") {
149 | property("(Boolean, Byte)") = forAll { (t: (Boolean, Byte)) => t == rw(t) }
150 | property("(Char, Double, Int)") = forAll { (t: (Char, Double, Int)) => t == rw(t) }
151 | property("(Float, Long, Short)") = forAll { (t: (Float, Long, Short)) => t == rw(t) }
152 | property("(List[String], Bean)") = forAll { (t: (List[String], Bean)) => t == rw(t) }
153 | property("(Option[Int], Long)") = forAll { (t: (Option[Int], Long)) => t == rw(t) }
154 | property("(Int, Int, Int, Int)") = forAll { (t: (Int, Int, Int, Int)) => t == rw(t) }
155 | property("(Symbol, Float)") = forAll { (t: (Symbol, Float)) => t == rw(t) }
156 | property("(String)") = forAll { (t: Tuple1[String]) => t == rw(t) }
157 |
158 | property("(String, Array[Int])") = forAll {
159 | (t: (String, Array[Int])) => rw(t) match {
160 | case (s, a) => t._1 == s && java.util.Arrays.equals(t._2, a)
161 | }
162 | }
163 |
164 | property("(Int) specialization") = rw(Tuple1(1)).getClass == Tuple1(1).getClass
165 | property("(Int, Int) specialization") = rw((1, 2)).getClass == (1, 2).getClass
166 | property("(Double, Int) specialization") = rw((1.0, 2)).getClass == (1.0, 2).getClass
167 | property("(Long, Long) specialization") = rw((1L, 2L)).getClass == (1L, 2L).getClass
168 | }
169 |
170 | object SymbolSpec extends ScalaModuleSpec("Symbol") {
171 | property("All") = forAll { (s: Symbol) => s == rw(s) }
172 | }
173 |
174 | object CaseClassSpec extends ScalaModuleSpec("CaseClass") {
175 | property("All") = forAll { (cc: CaseClass) => cc == rw(cc) }
176 | }
177 |
178 | class Bean(@BeanProperty var name: String, @BeanProperty var value: Int) {
179 | def this() = this(null, 0)
180 |
181 | override def equals(o: Any): Boolean = o match {
182 | case b:Bean => (b.name == name && b.value == value)
183 | case _ => false
184 | }
185 |
186 | override def hashCode: Int = {
187 | (if (name != null) name.hashCode else 0) + value
188 | }
189 |
190 | override def toString = "Bean(name = \"%s\", value = %d)".format(name, value)
191 | }
192 |
193 | case class CaseClass(
194 | boolean: Boolean,
195 | char: Char,
196 | int: Int,
197 | bean: Bean,
198 | list: List[Char]
199 | )
200 |
201 | abstract class ScalaModuleSpec(name: String) extends Properties(name) {
202 | import JacksMapper._
203 |
204 | def r[T](s: String)(implicit m: Manifest[T]): T = readValue(s)
205 |
206 | def rw[T](v: T)(implicit m: Manifest[T]): T = {
207 | val t = resolve(m)
208 | val s = mapper.writeValueAsString(v)
209 | mapper.readValue(s, t)
210 | }
211 |
212 | def rw[T,U](v: T, v2: U)(implicit m: Manifest[U]): U = {
213 | val u = resolve(m)
214 | val s = mapper.writeValueAsString(v)
215 | mapper.readValue(s, u)
216 | }
217 |
218 | def beanGen = for {
219 | name <- Arbitrary.arbitrary[String]
220 | value <- Arbitrary.arbitrary[Int]
221 | } yield (new Bean(name, value))
222 |
223 | def caseClassGen = for {
224 | boolean <- Arbitrary.arbitrary[Boolean]
225 | char <- Arbitrary.arbitrary[Char]
226 | int <- Arbitrary.arbitrary[Int]
227 | bean <- Arbitrary.arbitrary[Bean]
228 | list <- Arbitrary.arbitrary[List[Char]]
229 | } yield CaseClass(boolean, char, int, bean, list)
230 |
231 | implicit def arbitraryBean: Arbitrary[Bean] = Arbitrary { beanGen }
232 |
233 | implicit def arbitraryCaseClass: Arbitrary[CaseClass] = Arbitrary { caseClassGen }
234 |
235 | implicit def arbitraryOption[T](implicit a: Arbitrary[T]) = Arbitrary {
236 | val genSome = for (v <- Arbitrary.arbitrary[T]) yield Some(v)
237 | Gen.oneOf(None, genSome)
238 | }
239 |
240 | implicit def arbitraryTuple1[T](implicit a: Arbitrary[T]): Arbitrary[Tuple1[T]] = Arbitrary {
241 | for (v <- Arbitrary.arbitrary[T]) yield Tuple1(v)
242 | }
243 |
244 | implicit def arbitrarySymbol: Arbitrary[Symbol] = Arbitrary {
245 | for (s <- Arbitrary.arbitrary[String]) yield Symbol(s)
246 | }
247 | }
248 |
249 | trait ArbitraryCollections {
250 | import scala.collection.generic._
251 | import scala.collection.{GenMap, GenMapLike}
252 |
253 | def positiveIntStream: Arbitrary[Stream[Int]] = Arbitrary {
254 | Gen.containerOf[Stream, Int](Gen.chooseNum(0, Short.MaxValue))
255 | }
256 |
257 | def arbitrarySeq[T: Arbitrary, C[T] <: Traversable[T]](c: GenericCompanion[C]): Arbitrary[C[T]] =
258 | Arbitrary {
259 | for (seq <- Arbitrary.arbitrary[Stream[T]]) yield c(seq: _*)
260 | }
261 |
262 | def arbitraryMap[K: Arbitrary, V: Arbitrary, C[K, V] <: GenMap[K, V] with GenMapLike[K, V, C[K, V]]]
263 | (f: GenMapFactory[C]): Arbitrary[C[K, V]] = Arbitrary {
264 | for (seq <- Arbitrary.arbitrary[Stream[(K, V)]]) yield f(seq: _*)
265 | }
266 | }
267 |
268 | object ScalaCollections extends ArbitraryCollections {
269 | import scala.collection._
270 |
271 | implicit def arbitraryMap[K: Arbitrary, V: Arbitrary]: Arbitrary[Map[K, V]] = arbitraryMap[K, V, Map](Map)
272 | }
273 |
274 | object ImmutableCollections extends ArbitraryCollections {
275 | import scala.collection.immutable._
276 |
277 | implicit def arbitraryVector[T: Arbitrary]: Arbitrary[Vector[T]] = arbitrarySeq[T, Vector](Vector)
278 | implicit def arbitraryList[T: Arbitrary]: Arbitrary[List[T]] = arbitrarySeq[T, List](List)
279 | implicit def arbitraryStream[T: Arbitrary]: Arbitrary[Stream[T]] = arbitrarySeq[T, Stream](Stream)
280 | implicit def arbitraryQueue[T: Arbitrary]: Arbitrary[Queue[T]] = arbitrarySeq[T, Queue](Queue)
281 |
282 | implicit def arbitraryHashMap[K: Arbitrary, V: Arbitrary]: Arbitrary[HashMap[K, V]] = arbitraryMap[K, V, HashMap](HashMap)
283 | implicit def arbitraryListMap[K: Arbitrary, V: Arbitrary]: Arbitrary[ListMap[K, V]] = arbitraryMap[K, V, ListMap](ListMap)
284 |
285 | implicit def arbitraryTreeMap[K, V](implicit ak: Arbitrary[K], av: Arbitrary[V], o: Ordering[K]):
286 | Arbitrary[TreeMap[K, V]] = Arbitrary {
287 | for (seq <- Arbitrary.arbitrary[Stream[(K, V)]]) yield TreeMap(seq:_*)
288 | }
289 |
290 | implicit def arbitraryHashSet[T: Arbitrary]: Arbitrary[HashSet[T]] = arbitrarySeq[T, HashSet](HashSet)
291 | implicit def arbitraryListSet[T: Arbitrary]: Arbitrary[ListSet[T]] = arbitrarySeq[T, ListSet](ListSet)
292 |
293 | implicit def arbitraryTreeSet[T](implicit a: Arbitrary[T], o: Ordering[T]):
294 | Arbitrary[TreeSet[T]] = Arbitrary {
295 | for (seq <- Arbitrary.arbitrary[Stream[T]]) yield TreeSet(seq: _*)
296 | }
297 |
298 | implicit def arbitraryBitSet: Arbitrary[BitSet] = Arbitrary {
299 | for (seq <- positiveIntStream.arbitrary) yield BitSet(seq: _*)
300 | }
301 | }
302 |
303 | object MutableCollections extends ArbitraryCollections {
304 | import scala.collection.mutable._
305 |
306 | implicit def arbitraryArrayBuffer[T: Arbitrary]: Arbitrary[ArrayBuffer[T]] = arbitrarySeq[T, ArrayBuffer](ArrayBuffer)
307 | implicit def arbitraryArraySeq[T: Arbitrary]: Arbitrary[ArraySeq[T]] = arbitrarySeq[T, ArraySeq](ArraySeq)
308 | implicit def arbitraryArrayStack[T: Arbitrary]: Arbitrary[ArrayStack[T]] = arbitrarySeq[T, ArrayStack](ArrayStack)
309 | implicit def arbitraryListBuffer[T: Arbitrary]: Arbitrary[ListBuffer[T]] = arbitrarySeq[T, ListBuffer](ListBuffer)
310 | implicit def arbitraryMutableList[T: Arbitrary]: Arbitrary[MutableList[T]] = arbitrarySeq[T, MutableList](MutableList)
311 | implicit def arbitraryQueue[T: Arbitrary]: Arbitrary[Queue[T]] = arbitrarySeq[T, Queue](Queue)
312 | implicit def arbitraryStack[T: Arbitrary]: Arbitrary[Stack[T]] = arbitrarySeq[T, Stack](Stack)
313 |
314 | implicit def arbitraryPriorityQueue[T](implicit a: Arbitrary[T], o: Ordering[T]): Arbitrary[PriorityQueue[T]] =
315 | Arbitrary {
316 | for (seq <- Arbitrary.arbitrary[Stream[T]]) yield PriorityQueue(seq: _*)
317 | }
318 |
319 | implicit def arbitraryHashMap[K: Arbitrary, V: Arbitrary]: Arbitrary[HashMap[K, V]] = arbitraryMap[K, V, HashMap](HashMap)
320 | implicit def arbitraryLinkedHashMap[K: Arbitrary, V: Arbitrary]: Arbitrary[LinkedHashMap[K, V]] =
321 | arbitraryMap[K, V, LinkedHashMap](LinkedHashMap)
322 | implicit def arbitraryListMap[K: Arbitrary, V: Arbitrary]: Arbitrary[ListMap[K, V]] = arbitraryMap[K, V, ListMap](ListMap)
323 |
324 | implicit def arbitraryHashSet[T: Arbitrary]: Arbitrary[HashSet[T]] = arbitrarySeq[T, HashSet](HashSet)
325 | implicit def arbitraryLinkedHashSet[T: Arbitrary]: Arbitrary[LinkedHashSet[T]] = arbitrarySeq[T, LinkedHashSet](LinkedHashSet)
326 |
327 | implicit def arbitraryBitSet: Arbitrary[BitSet] = Arbitrary {
328 | for (seq <- positiveIntStream.arbitrary) yield BitSet(seq: _*)
329 | }
330 | }
331 |
--------------------------------------------------------------------------------