├── .gitignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── codahale
│ │ └── jerkson
│ │ └── JsonSnakeCase.java
└── scala
│ └── com
│ └── codahale
│ └── jerkson
│ ├── AST.scala
│ ├── Factory.scala
│ ├── Generator.scala
│ ├── Json.scala
│ ├── Parser.scala
│ ├── ParsingException.scala
│ ├── ScalaModule.scala
│ ├── StreamingIterator.scala
│ ├── Types.scala
│ ├── Util.scala
│ ├── deser
│ ├── BigDecimalDeserializer.scala
│ ├── BigIntDeserializer.scala
│ ├── BitSetDeserializer.scala
│ ├── CaseClassDeserializer.scala
│ ├── EitherDeserializer.scala
│ ├── ImmutableMapDeserializer.scala
│ ├── IntMapDeserializer.scala
│ ├── IteratorDeserializer.scala
│ ├── JValueDeserializer.scala
│ ├── LongMapDeserializer.scala
│ ├── MutableLinkedHashMapDeserializer.scala
│ ├── MutableMapDeserializer.scala
│ ├── OptionDeserializer.scala
│ ├── RangeDeserializer.scala
│ ├── ScalaDeserializers.scala
│ ├── SeqDeserializer.scala
│ └── StringBuilderDeserializer.scala
│ ├── ser
│ ├── CaseClassSerializer.scala
│ ├── EitherSerializer.scala
│ ├── IterableSerializer.scala
│ ├── IteratorSerializer.scala
│ ├── JValueSerializer.scala
│ ├── MapSerializer.scala
│ ├── OptionSerializer.scala
│ ├── RangeSerializer.scala
│ ├── ScalaSerializers.scala
│ └── StringBuilderSerializer.scala
│ └── util
│ ├── CaseClassSigParser.scala
│ └── scalax
│ └── rules
│ ├── Memoisable.scala
│ ├── Result.scala
│ ├── Rule.scala
│ ├── Rules.scala
│ ├── SeqRule.scala
│ └── scalasig
│ ├── ClassFileParser.scala
│ ├── Flags.scala
│ ├── ScalaSig.scala
│ ├── Symbol.scala
│ └── Type.scala
└── test
└── scala
└── com
└── codahale
└── jerkson
└── tests
├── ASTTypeSupportSpec.scala
├── BasicTypeSupportSpec.scala
├── CaseClassSupportSpec.scala
├── CollectionSupportSpec.scala
├── DefaultCollectionSupportSpec.scala
├── EdgeCaseSpec.scala
├── ExampleCaseClasses.scala
├── FancyTypeSupportSpec.scala
├── ImmutableCollectionSupportSpec.scala
├── JValueSpec.scala
├── MutableCollectionSupportSpec.scala
└── StreamingSpec.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea*
2 | *.iml
3 | target
4 |
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v0.5.0: Oct 07 2011
2 | ===================
3 |
4 | * Added `canSerialize` and `canDeserialize`.
5 | * Upgraded Jackson to 1.9.0.
6 | * Dropped support for Scala 2.8.1, added support for 2.8.2. Upgrade.
7 | * Dropped support for 2.9.0-1. Upgrade.
8 |
9 | v0.4.2: Sep 16 2011
10 | ===================
11 |
12 | * Added support for case classes with multiple constructors.
13 | * Added support for `snake_cased` JSON field names via the `@JsonSnakeCase`
14 | annotation.
15 |
16 | v0.4.1: Sep 13 2011
17 | ===================
18 |
19 | * Added `@JsonIgnoreProperties` support.
20 | * No longer serializing `transient` members of case classes.
21 | * Upgraded to Jackson 1.8.x. (Jerkson will now track the highest available
22 | version in the 1.8.x series.)
23 |
24 |
25 | v0.4.0: Jul 25 2011
26 | ===================
27 |
28 | * Upgraded to Jackson 1.8.3.
29 | * Fixed deserialization of empty JSON objects as `JValue` instances.
30 | * Fixed deserialization of `Map[java.lang.Integer, A]` and
31 | `Map[java.lang.Long, A]` instances.
32 | * Fixed deserialization of case classes in weird bytecode environments like
33 | Play.
34 | * Added support for case classes nested in objects.
35 | * Allowed for deserializing `BigInt` and `BigDecimal` instances from anything
36 | those classes can parse as text.
37 | * Added a cache for type manifests.
38 |
39 |
40 | v0.3.2: Jun 09 2011
41 | ===================
42 |
43 | * Added `Json.stream[A](Reader)`.
44 | * Fix `NullPointerException` when deserializing `Map` instances from weird JSON
45 | values.
46 |
47 |
48 | v0.3.1: Jun 05 2011
49 | ===================
50 |
51 | * Added support for deserializing `Map[Int, _]` and `Map[Long, _]` instances.
52 |
53 |
54 | v0.3.0: Jun 04 2011
55 | ===================
56 | * Added a very comprehensive set of tests, refactored around support for various
57 | types. (h/t Daniel Brown)
58 | * Added support for `StringBuilder`, `Array[A]`, `immutable._`, `mutable._`,
59 | `collection._` classes, `AST` classes, and others.
60 | * Fixed error messages when parsing empty JSON objects as case classes.
61 | * Enabled caching of all serializers and deserializers.
62 | * Switched to Maven for builds.
63 | * Removed the deprecated `Parser#parseStreamOf`.
64 |
65 |
66 | v0.2.2: May 18 2011
67 | ===================
68 |
69 | * Upgraded to Jackson 1.7.7.
70 | * Fixed bugs in parsing case classes with other specially-namespaced types.
71 |
72 |
73 | v0.2.1: May 12 2011
74 | ===================
75 |
76 | * Fixed bug in parsing case classes with `List[A]` members (and anything else
77 | which is typed in the `scala` package.
78 |
79 |
80 | v0.2.0: May 12 2011
81 | ===================
82 |
83 | * Now cross-built for Scala 2.9.0.
84 | * Changed to parse the embedded Scala signature in case classes by using an
85 | embedded version of `scalap`. No longer depends on Paranamer.
86 | * Serializing a case class with a `None` field now elides the entire field
87 | instead of serializing the `Option[A]` as `null`.
88 | * Removed explicit flushes to output.
89 |
90 |
91 | v0.1.8: May 05 2011
92 | ===================
93 |
94 | * Upgraded to Jackson 1.7.6.
95 | * Added selectors to `JValue` and friends.
96 | * Extracted out the `Json` trait for extensibility.
97 | * Added support for `Iterator` and `Set` instances.
98 | * Fixed deserialization of empty `Map`s.
99 |
100 |
101 | v0.1.7: Mar 31 2011
102 | ===================
103 |
104 | * Upgraded to Jackson 1.7.4.
105 |
106 |
107 | v0.1.6: Feb 18 2011
108 | ===================
109 |
110 | * Serialize `None` instances to `null`. (h/t Alex Cruise again)
111 |
112 |
113 | v0.1.5: Feb 18 2011
114 | ===================
115 |
116 | * Added ability to actually serialize `Option` instances. (h/t Alex Cruise)
117 |
118 |
119 | v0.1.4: Jan 17 2011
120 | ===================
121 |
122 | * Upgraded to Jackson 1.7.1, which fixes the buffer overruns
123 | * Handle empty JSON documents w/o resorting to `EOFException`
124 |
125 |
126 | v0.1.3: Jan 12 2011
127 | ===================
128 |
129 | * Quick fix for potential buffer overrun errors in huge case classes (JACKSON-462).
130 |
131 |
132 | v0.1.2: Jan 07 2011
133 | ===================
134 |
135 | * Upgraded to [Jackson 1.7.0](http://jackson.codehaus.org/1.7.0/release-notes/VERSION).
136 | * Added support for `Either[A, B]` instances.
137 | * Internal refactoring of `Json`.
138 |
139 |
140 | v0.1.1: Dec 09 2010
141 | ===================
142 |
143 | * Upgraded to [Jackson 1.6.3](http://jackson.codehaus.org/1.6.3/release-notes/VERSION).
144 | * Added `StreamingIterator`, `Json.stream`, and deprecated `Json.parseStreamOf`.
145 | * Fixed support for `lazy` `val` and `var` members of case classes.
146 | * Added support for `@JsonIgnore` for case classes.
147 |
148 |
149 | v0.1.0: Dec 03 2010
150 | ===================
151 |
152 | * Initial release. Totally awesome.
153 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2011 Coda Hale
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Jerkson
2 | -------
3 |
4 | *Because I think you should use JSON.*
5 |
6 | Jerkson is a Scala wrapper for [Jackson](http://jackson.codehaus.org/) which
7 | brings Scala's ease-of-use to Jackson's features.
8 |
9 |
10 | Requirements
11 | ------------
12 |
13 | * Scala 2.8.2 or 2.9.1
14 | * Jackson 1.9.x
15 |
16 |
17 | Setting Up Your Project
18 | -----------------------
19 |
20 | Go ahead and add Jerkson as a dependency:
21 |
22 | ```xml
23 |
24 |
25 | repo.codahale.com
26 | http://repo.codahale.com
27 |
28 |
29 |
30 |
31 |
32 | com.codahale
33 | jerkson_${scala.version}
34 | 0.5.0
35 |
36 |
37 | ```
38 |
39 |
40 | Parsing JSON
41 | ------------
42 |
43 | ```scala
44 | import com.codahale.jerkson.Json._
45 |
46 | // Parse JSON arrays
47 | parse[List[Int]]("[1,2,3]") //=> List(1,2,3)
48 |
49 | // Parse JSON objects
50 | parse[Map[String, Int]]("""{"one":1,"two":2}""") //=> Map("one"->1,"two"->2)
51 |
52 | // Parse JSON objects as case classes
53 | // (Parsing case classes isn't supported in the REPL.)
54 | case class Person(id: Long, name: String)
55 | parse[Person]("""{"id":1,"name":"Coda"}""") //=> Person(1,"Coda")
56 |
57 | // Parse streaming arrays of things
58 | for (person <- stream[Person](inputStream)) {
59 | println("New person: " + person)
60 | }
61 | ```
62 |
63 | For more examples, check out the [specs](https://github.com/codahale/jerkson/blob/master/src/test/scala/com/codahale/jerkson/tests/).
64 |
65 |
66 | Generating JSON
67 | ---------------
68 |
69 | ```scala
70 | // Generate JSON arrays
71 | generate(List(1, 2, 3)) //=> [1,2,3]
72 |
73 | // Generate JSON objects
74 | generate(Map("one"->1, "two"->"dos")) //=> {"one":1,"two":"dos"}
75 | ```
76 |
77 | For more examples, check out the [specs](https://github.com/codahale/jerkson/blob/master/src/test/scala/com/codahale/jerkson/tests/).
78 |
79 |
80 | Handling `snake_case` Field Names
81 | =================================
82 |
83 | ```scala
84 | case class Person(firstName: String, lastName: String)
85 |
86 | @JsonSnakeCase
87 | case class Snake(firstName: String, lastName: String)
88 |
89 | generate(Person("Coda", "Hale")) //=> {"firstName": "Coda","lastName":"Hale"}
90 | generate(Snake("Windey", "Mover")) //=> {"first_name": "Windey","last_name":"Mover"}
91 | ```
92 |
93 |
94 | License
95 | -------
96 |
97 | Copyright (c) 2010-2011 Coda Hale
98 |
99 | Published under The MIT License, see LICENSE
100 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | 3.0.0
9 |
10 |
11 | com.codahale
12 | jerkson_2.9.1
13 | 0.6.0-SNAPSHOT
14 | Jerkson for Scala
15 |
16 |
17 | 2.9.1
18 | 2.0.2
19 |
20 |
21 |
22 |
23 | Coda Hale
24 | coda.hale@gmail.com
25 | -8
26 |
27 |
28 |
29 |
30 |
31 | http://codahale.com/mit.txt
32 | The MIT License
33 | repo
34 |
35 |
36 |
37 |
38 |
39 | repo.codahale.com
40 | http://repo.codahale.com
41 |
42 |
43 | nativelibs4java
44 | http://nativelibs4java.sourceforge.net/maven
45 |
46 |
47 |
48 |
49 |
50 | repo.codahale.com
51 | scp://codahale.com/home/codahale/repo.codahale.com
52 |
53 |
54 |
55 |
56 |
57 | org.scala-lang
58 | scala-library
59 | ${scala.version}
60 |
61 |
62 | com.fasterxml.jackson.core
63 | jackson-core
64 | ${jackson.version}
65 |
66 |
67 | com.fasterxml.jackson.core
68 | jackson-databind
69 | ${jackson.version}
70 |
71 |
72 | com.codahale
73 | simplespec_${scala.version}
74 | 0.5.2
75 | test
76 |
77 |
78 |
79 |
80 |
81 | release-sign-artifacts
82 |
83 |
84 | performRelease
85 | true
86 |
87 |
88 |
89 |
90 |
91 | org.apache.maven.plugins
92 | maven-gpg-plugin
93 | 1.2
94 |
95 |
96 | sign-artifacts
97 | verify
98 |
99 | sign
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | org.scala-tools
113 | maven-scala-plugin
114 | 2.15.2
115 |
116 |
117 |
118 | compile
119 | testCompile
120 |
121 |
122 |
123 |
124 |
125 | -optimise
126 | -unchecked
127 | -deprecation
128 |
129 |
130 |
131 | com.nativelibs4java
132 | scalacl-compiler-plugin
133 | 0.2
134 |
135 |
136 | UTF-8
137 |
138 |
139 |
140 | org.apache.maven.plugins
141 | maven-compiler-plugin
142 | 2.3.2
143 |
144 | 1.6
145 | 1.6
146 | UTF-8
147 |
148 |
149 |
150 | org.apache.maven.plugins
151 | maven-source-plugin
152 | 2.1.2
153 |
154 |
155 | attach-sources
156 |
157 | jar
158 |
159 |
160 |
161 |
162 |
163 | org.apache.maven.plugins
164 | maven-resources-plugin
165 | 2.5
166 |
167 | UTF-8
168 |
169 |
170 |
171 | org.apache.maven.plugins
172 | maven-surefire-plugin
173 | 2.8.1
174 |
175 | false
176 | -Xmx1024m
177 |
178 | **/*Spec.java
179 |
180 |
181 | **/*Test.java
182 |
183 |
184 |
185 |
186 | org.apache.maven.plugins
187 | maven-release-plugin
188 | 2.2.1
189 |
190 | true
191 | forked-path
192 | v@{project.version}
193 | clean test
194 |
195 |
196 |
197 |
198 |
199 | org.apache.maven.wagon
200 | wagon-ssh
201 | 2.2
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/src/main/java/com/codahale/jerkson/JsonSnakeCase.java:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson;
2 |
3 | import com.fasterxml.jackson.annotation.JacksonAnnotation;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * Marker annotation which indicates that the annotated case class should be
12 | * serialized and deserialized using {@code snake_case} JSON field names instead
13 | * of {@code camelCase} field names.
14 | */
15 | @Target({ElementType.TYPE})
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @JacksonAnnotation
18 | public @interface JsonSnakeCase {
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/AST.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | object AST {
4 | sealed trait JValue {
5 | def value: Any
6 |
7 | def valueAs[A]: A = value.asInstanceOf[A]
8 |
9 | def \(fieldName: String): JValue = JNull
10 |
11 | def apply(idx: Int): JValue = JNull
12 |
13 | def \\(fieldName: String): Seq[JValue] = Nil
14 | }
15 |
16 | case object JNull extends JValue {
17 | def value = null
18 | }
19 |
20 | case class JBoolean(value: Boolean) extends JValue
21 |
22 | case class JInt(value: BigInt) extends JValue
23 |
24 | case class JFloat(value: Double) extends JValue
25 |
26 | case class JString(value: String) extends JValue
27 |
28 | case class JArray(elements: List[JValue]) extends JValue {
29 | def value = null
30 |
31 | override def apply(index: Int): JValue = {
32 | try {
33 | elements(index)
34 | } catch {
35 | case _ => JNull
36 | }
37 | }
38 | }
39 |
40 | case class JField(name: String, value: JValue) extends JValue
41 |
42 | case class JObject(fields: List[JField]) extends JValue {
43 | def value = null
44 |
45 | override def \(fieldName: String): JValue = {
46 | fields.find { case JField(name, _) =>
47 | name == fieldName
48 | }.map { case JField(_, value) =>
49 | value
50 | }.getOrElse(JNull)
51 | }
52 |
53 | override def \\(fieldName: String): Seq[JValue] = {
54 | fields.flatMap {
55 | case JField(name, value) if name == fieldName => Seq(value) ++ (value \\ fieldName)
56 | case JField(_, value) => value \\ fieldName
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Factory.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import com.fasterxml.jackson.core.JsonFactory
4 | import com.fasterxml.jackson.databind.ObjectMapper
5 |
6 | trait Factory {
7 | /**
8 | * The ObjectMapper to be used by {@link Parser} and {@link Generator}.
9 | */
10 | protected val mapper: ObjectMapper
11 |
12 | /**
13 | * The JsonFactory to be used by {@link Parser} and {@link Generator}.
14 | */
15 | protected val factory: JsonFactory
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Generator.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import java.io.{File, OutputStream, Writer, StringWriter}
4 | import com.fasterxml.jackson.core.{JsonGenerator, JsonEncoding}
5 |
6 | trait Generator extends Factory {
7 | /**
8 | * Generate JSON from the given object and return it as a string.
9 | */
10 | def generate[A](obj: A): String = {
11 | val writer = new StringWriter
12 | generate(obj, writer)
13 | writer.toString
14 | }
15 |
16 | /**
17 | * Generate JSON from the given object and write to the given Writer.
18 | */
19 | def generate[A](obj: A, output: Writer) {
20 | generate(obj, factory.createJsonGenerator(output))
21 | }
22 |
23 | /**
24 | * Generate JSON from the given object and write it to the given OutputStream.
25 | */
26 | def generate[A](obj: A, output: OutputStream) {
27 | generate(obj, factory.createJsonGenerator(output, JsonEncoding.UTF8))
28 | }
29 |
30 | /**
31 | * Generate JSON from the given object and write it to the given File.
32 | */
33 | def generate[A](obj: A, output: File) {
34 | generate(obj, factory.createJsonGenerator(output, JsonEncoding.UTF8))
35 | }
36 |
37 | /**
38 | * Returns true if the given class is serializable.
39 | */
40 | def canSerialize[A](implicit mf: Manifest[A]) = mapper.canSerialize(mf.erasure)
41 |
42 | private def generate[A](obj: A, generator: JsonGenerator) {
43 | generator.writeObject(obj)
44 | generator.close()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Json.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import com.fasterxml.jackson.databind.{MappingJsonFactory, ObjectMapper}
4 | import com.fasterxml.jackson.core.{JsonGenerator, JsonParser => JacksonParser}
5 |
6 | object Json extends Json
7 |
8 | trait Json extends Parser with Generator {
9 | protected val classLoader = Thread.currentThread().getContextClassLoader
10 |
11 | protected val mapper = new ObjectMapper
12 | mapper.registerModule(new ScalaModule(classLoader))
13 |
14 | protected val factory = new MappingJsonFactory(mapper)
15 | factory.enable(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT)
16 | factory.enable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
17 | factory.enable(JsonGenerator.Feature.QUOTE_FIELD_NAMES)
18 | factory.enable(JacksonParser.Feature.ALLOW_COMMENTS)
19 | factory.enable(JacksonParser.Feature.AUTO_CLOSE_SOURCE)
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Parser.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import io.Source
4 | import java.net.URL
5 | import com.codahale.jerkson.AST.{JValue, JNull}
6 | import com.fasterxml.jackson.core.{JsonParser, JsonProcessingException}
7 | import com.fasterxml.jackson.databind.JsonNode
8 | import com.fasterxml.jackson.databind.node.TreeTraversingParser
9 | import java.io.{EOFException, Reader, File, InputStream}
10 |
11 | trait Parser extends Factory {
12 | /**
13 | * Parse a JSON string as a particular type.
14 | */
15 | def parse[A](input: String)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
16 |
17 | /**
18 | * Parse a JSON input stream as a particular type.
19 | */
20 | def parse[A](input: InputStream)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
21 |
22 | /**
23 | * Parse a JSON file as a particular type.
24 | */
25 | def parse[A](input: File)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
26 |
27 | /**
28 | * Parse a JSON URL as a particular type.
29 | */
30 | def parse[A](input: URL)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
31 |
32 | /**
33 | * Parse a JSON Reader as a particular type.
34 | */
35 | def parse[A](input: Reader)(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
36 |
37 | /**
38 | * Parse a JSON byte array as a particular type.
39 | */
40 | def parse[A](input: Array[Byte])(implicit mf: Manifest[A]): A = parse[A](factory.createJsonParser(input), mf)
41 |
42 | /**
43 | * Parse a JSON Source as a particular type.
44 | */
45 | def parse[A](input: Source)(implicit mf: Manifest[A]): A = parse[A](input.mkString)
46 |
47 | /**
48 | * Parse a JSON node as a particular type.
49 | */
50 | def parse[A](input: JsonNode)(implicit mf: Manifest[A]): A = {
51 | val parser = new TreeTraversingParser(input, mapper)
52 | parse(parser, mf)
53 | }
54 |
55 | /**
56 | * Parse a streaming JSON array of particular types, returning an iterator
57 | * of the elements of the stream.
58 | */
59 | def stream[A](input: InputStream)(implicit mf: Manifest[A]): Iterator[A] = {
60 | val parser = factory.createJsonParser(input)
61 | new StreamingIterator[A](parser, mf)
62 | }
63 |
64 | /**
65 | * Parse a streaming JSON array of particular types, returning an iterator
66 | * of the elements of the stream.
67 | */
68 | def stream[A](input: Reader)(implicit mf: Manifest[A]): Iterator[A] = {
69 | val parser = factory.createJsonParser(input)
70 | new StreamingIterator[A](parser, mf)
71 | }
72 |
73 | /**
74 | * Returns true if the given class is deserializable.
75 | */
76 | def canDeserialize[A](implicit mf: Manifest[A]) = mapper.canDeserialize(Types.build(mapper.getTypeFactory, mf))
77 |
78 | private[jerkson] def parse[A](parser: JsonParser, mf: Manifest[A]): A = {
79 | try {
80 | if (mf.erasure == classOf[JValue] || mf.erasure == JNull.getClass) {
81 | val value: A = parser.getCodec.readValue(parser, Types.build(mapper.getTypeFactory, mf))
82 | if (value == null) JNull.asInstanceOf[A] else value
83 | } else {
84 | parser.getCodec.readValue(parser, Types.build(mapper.getTypeFactory, mf))
85 | }
86 | } catch {
87 | case e: JsonProcessingException => throw ParsingException(e)
88 | case e: EOFException => throw new ParsingException("JSON document ended unexpectedly.", e)
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ParsingException.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import java.io.IOException
4 | import com.fasterxml.jackson.databind.JsonMappingException
5 | import com.fasterxml.jackson.core.{JsonParseException, JsonProcessingException}
6 |
7 | object ParsingException {
8 | def apply(cause: JsonProcessingException): ParsingException = {
9 | val message = cause match {
10 | case e: JsonMappingException => e.getMessage
11 | case e: JsonParseException => {
12 | val fake = new JsonParseException("", e.getLocation)
13 | val msg = e.getMessage.replace(fake.getMessage, "").replaceAll(""" (\(from.*\))""", "")
14 | "Malformed JSON. %s at character offset %d.".format(msg, e.getLocation.getCharOffset)
15 | }
16 | }
17 | new ParsingException(message, cause)
18 | }
19 | }
20 |
21 | class ParsingException(message: String, cause: Throwable)
22 | extends IOException(message, cause)
23 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ScalaModule.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import deser.ScalaDeserializers
4 | import com.fasterxml.jackson.databind.Module.SetupContext
5 | import com.fasterxml.jackson.core.Version
6 | import com.fasterxml.jackson.databind.Module
7 | import ser.ScalaSerializers
8 |
9 | class ScalaModule(classLoader: ClassLoader) extends Module {
10 | def version = new Version(0, 6, 0, "SNAPSHOT", "com.codahale", "jerkson")
11 | def getModuleName = "jerkson"
12 |
13 | def setupModule(context: SetupContext) {
14 | context.addDeserializers(new ScalaDeserializers(classLoader, context))
15 | context.addSerializers(new ScalaSerializers)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/StreamingIterator.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
4 |
5 | class StreamingIterator[A](parser: JsonParser, mf: Manifest[A])
6 | extends Iterator[A] {
7 |
8 | import Json._
9 |
10 | if (parser.getCurrentToken == null) {
11 | parser.nextToken()
12 | }
13 | require(parser.getCurrentToken == JsonToken.START_ARRAY)
14 | parser.nextToken()
15 |
16 | def hasNext = parser.getCurrentToken != JsonToken.END_ARRAY && !parser.isClosed
17 |
18 | def next() = if (hasNext) {
19 | val value = parse[A](parser, mf)
20 | parser.nextToken()
21 | value
22 | } else Iterator.empty.next()
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Types.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.`type`.{TypeFactory, ArrayType}
5 | import scala.collection.JavaConversions.asScalaConcurrentMap
6 | import java.util.concurrent.ConcurrentHashMap
7 |
8 | private[jerkson] object Types {
9 | private val cachedTypes = asScalaConcurrentMap(new ConcurrentHashMap[Manifest[_], JavaType]())
10 |
11 | def build(factory: TypeFactory, manifest: Manifest[_]): JavaType =
12 | cachedTypes.getOrElseUpdate(manifest, constructType(factory, manifest))
13 |
14 | private def constructType(factory: TypeFactory, manifest: Manifest[_]): JavaType = {
15 | if (manifest.erasure.isArray) {
16 | ArrayType.construct(factory.constructType(manifest.erasure.getComponentType), null, null)
17 | } else {
18 | factory.constructParametricType(
19 | manifest.erasure,
20 | manifest.typeArguments.map {m => build(factory, m)}.toArray: _*)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/Util.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson
2 |
3 | private[jerkson] object Util {
4 | /**
5 | * Convert a string from camelCase to snake_case.
6 | */
7 | def snakeCase(word: String) = {
8 | val len = word.length()
9 | val builder = new StringBuilder(len)
10 | if (len > 0) {
11 | var idx = if (word(0) == '_') {
12 | // preserve the first underscore
13 | builder += '_'
14 | 1
15 | } else 0
16 | while (idx < len) {
17 | val char = word(idx)
18 | if (Character.isUpperCase(char)) {
19 | builder += '_'
20 | builder += Character.toLowerCase(char)
21 | } else {
22 | builder += char
23 | }
24 | idx += 1
25 | }
26 | }
27 | builder.toString()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/BigDecimalDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.JsonParser
5 |
6 | class BigDecimalDeserializer extends JsonDeserializer[Object] {
7 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
8 | if (jp.getCurrentToken == null) {
9 | jp.nextToken()
10 | }
11 |
12 | try {
13 | BigDecimal(jp.getText)
14 | } catch {
15 | case e: NumberFormatException =>
16 | throw ctxt.mappingException(classOf[BigDecimal])
17 | }
18 | }
19 |
20 | override def isCachable = true
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/BigIntDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.JsonParser
5 |
6 | class BigIntDeserializer extends JsonDeserializer[Object] {
7 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
8 | if (jp.getCurrentToken == null) {
9 | jp.nextToken()
10 | }
11 |
12 | try {
13 | BigInt(jp.getText)
14 | } catch {
15 | case e: NumberFormatException =>
16 | throw ctxt.mappingException(classOf[BigInt])
17 | }
18 | }
19 |
20 | override def isCachable = true
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/BitSetDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import scala.collection.generic.BitSetFactory
4 | import scala.collection.{BitSetLike, BitSet}
5 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
6 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
7 |
8 | class BitSetDeserializer[Coll <: BitSet with BitSetLike[Coll]](factory: BitSetFactory[Coll])
9 | extends JsonDeserializer[Coll] {
10 |
11 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
12 | val builder = factory.newBuilder
13 |
14 | if (jp.getCurrentToken != JsonToken.START_ARRAY) {
15 | throw ctxt.mappingException(classOf[BitSet])
16 | }
17 |
18 | while (jp.nextToken() != JsonToken.END_ARRAY) {
19 | builder += jp.getIntValue
20 | }
21 |
22 | builder.result()
23 | }
24 |
25 | override def isCachable = true
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/CaseClassDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import scala.collection.JavaConversions._
4 | import scala.collection.mutable.ArrayBuffer
5 | import com.codahale.jerkson.JsonSnakeCase
6 | import com.codahale.jerkson.util._
7 | import com.codahale.jerkson.Util._
8 | import com.fasterxml.jackson.databind._
9 | import com.fasterxml.jackson.databind.node.{ObjectNode, NullNode, TreeTraversingParser}
10 | import com.fasterxml.jackson.databind.JavaType
11 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
12 |
13 | class CaseClassDeserializer(config: DeserializationConfig,
14 | javaType: JavaType,
15 | classLoader: ClassLoader) extends JsonDeserializer[Object] {
16 | private val isSnakeCase = javaType.getRawClass.isAnnotationPresent(classOf[JsonSnakeCase])
17 | private val params = CaseClassSigParser.parse(javaType.getRawClass, config.getTypeFactory, classLoader).map {
18 | case (name, jt) => (if (isSnakeCase) snakeCase(name) else name, jt)
19 | }.toArray
20 | private val paramTypes = params.map { _._2.getRawClass }.toList
21 | private val constructor = javaType.getRawClass.getConstructors.find { c =>
22 | val constructorTypes = c.getParameterTypes.toList.map { t =>
23 | t.toString match {
24 | case "byte" => classOf[java.lang.Byte]
25 | case "short" => classOf[java.lang.Short]
26 | case "int" => classOf[java.lang.Integer]
27 | case "long" => classOf[java.lang.Long]
28 | case "float" => classOf[java.lang.Float]
29 | case "double" => classOf[java.lang.Double]
30 | case "char" => classOf[java.lang.Character]
31 | case "boolean" => classOf[java.lang.Boolean]
32 | case _ => t
33 | }
34 | }
35 | constructorTypes == paramTypes
36 | }.getOrElse { throw new JsonMappingException("Unable to find a case accessor for " + javaType.getRawClass.getName) }
37 |
38 | def deserialize(jp: JsonParser, ctxt: DeserializationContext): Object = {
39 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
40 | jp.nextToken()
41 | }
42 |
43 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
44 | jp.getCurrentToken != JsonToken.END_OBJECT) {
45 | throw ctxt.mappingException(javaType.getRawClass)
46 | }
47 |
48 | val node = jp.readValueAsTree[JsonNode]
49 |
50 | val values = new ArrayBuffer[AnyRef]
51 | for ((paramName, paramType) <- params) {
52 | val field = node.get(paramName)
53 | val tp = new TreeTraversingParser(if (field == null) NullNode.getInstance else field, jp.getCodec)
54 | val value = if (paramType.getRawClass == classOf[Option[_]]) {
55 | // thanks again for special-casing VALUE_NULL
56 | Option(tp.getCodec.readValue[Object](tp, paramType.containedType(0)))
57 | } else {
58 | tp.getCodec.readValue[Object](tp, paramType)
59 | }
60 |
61 | if (field != null || value != null) {
62 | values += value
63 | }
64 |
65 |
66 | if (values.size == params.size) {
67 | return constructor.newInstance(values.toArray: _*).asInstanceOf[Object]
68 | }
69 | }
70 |
71 | throw new JsonMappingException(errorMessage(node))
72 | }
73 |
74 | private def errorMessage(node: JsonNode) = {
75 | val names = params.map { _._1 }.mkString("[", ", ", "]")
76 | val existing = node match {
77 | case obj: ObjectNode => obj.fieldNames.mkString("[", ", ", "]")
78 | case _: NullNode => "[]" // this is what Jackson deserializes the inside of an empty object to
79 | case unknown => "a non-object"
80 | }
81 | "Invalid JSON. Needed %s, but found %s.".format(names, existing)
82 | }
83 |
84 | override def isCachable = true
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/EitherDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.core.JsonParser
4 | import com.fasterxml.jackson.databind.node.TreeTraversingParser
5 | import com.fasterxml.jackson.databind._
6 |
7 | class EitherDeserializer(config: DeserializationConfig,
8 | javaType: JavaType) extends JsonDeserializer[Object] {
9 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
10 | val node = jp.readValueAsTree[JsonNode]
11 | val tp = new TreeTraversingParser(node, jp.getCodec)
12 |
13 | try {
14 | Left(tp.getCodec.readValue[Object](tp, javaType.containedType(0)))
15 | } catch {
16 | case _ => Right(tp.getCodec.readValue[Object](tp, javaType.containedType(1)))
17 | }
18 | }
19 |
20 | override def isCachable = true
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/ImmutableMapDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
5 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
6 | import collection.generic.MapFactory
7 | import collection.MapLike
8 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
9 |
10 | class ImmutableMapDeserializer[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](companion: MapFactory[CC],
11 | valueType: JavaType)
12 | extends JsonDeserializer[Object] with ResolvableDeserializer {
13 |
14 | var valueDeserializer: JsonDeserializer[Object] = _
15 |
16 | def deserialize(jp: JsonParser, ctxt: DeserializationContext): CC[String, Object] = {
17 | val builder = companion.newBuilder[String, Object]
18 |
19 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
20 | jp.nextToken()
21 | }
22 |
23 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
24 | jp.getCurrentToken != JsonToken.END_OBJECT) {
25 | throw ctxt.mappingException(valueType.getRawClass)
26 | }
27 |
28 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
29 | val name = jp.getCurrentName
30 | jp.nextToken()
31 | builder += ((name, valueDeserializer.deserialize(jp, ctxt)))
32 | jp.nextToken()
33 | }
34 |
35 | builder.result()
36 | }
37 |
38 | def resolve(ctxt: DeserializationContext) {
39 | valueDeserializer = ctxt.findRootValueDeserializer(valueType)
40 | }
41 |
42 | override def isCachable = true
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/IntMapDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
5 | import com.fasterxml.jackson.databind.JavaType
6 | import scala.collection.immutable.IntMap
7 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
8 |
9 | class IntMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer {
10 | var valueDeserializer: JsonDeserializer[Object] = _
11 |
12 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
13 | var map = IntMap.empty[Object]
14 |
15 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
16 | jp.nextToken()
17 | }
18 |
19 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
20 | jp.getCurrentToken != JsonToken.END_OBJECT) {
21 | throw ctxt.mappingException(valueType.getRawClass)
22 | }
23 |
24 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
25 | try {
26 | val name = jp.getCurrentName.toInt
27 | jp.nextToken()
28 | map += ((name, valueDeserializer.deserialize(jp, ctxt)))
29 | jp.nextToken()
30 | } catch {
31 | case e: IllegalArgumentException => throw ctxt.mappingException(classOf[IntMap[_]])
32 | }
33 | }
34 |
35 | map
36 | }
37 |
38 | def resolve(ctxt: DeserializationContext) {
39 | valueDeserializer = ctxt.findRootValueDeserializer(valueType)
40 | }
41 |
42 | override def isCachable = true
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/IteratorDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
5 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
6 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
7 |
8 | class IteratorDeserializer(elementType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer {
9 | var elementDeserializer: JsonDeserializer[Object] = _
10 |
11 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
12 | val builder = Seq.newBuilder[Object]
13 |
14 | if (jp.getCurrentToken != JsonToken.START_ARRAY) {
15 | throw ctxt.mappingException(elementType.getRawClass)
16 | }
17 |
18 | while (jp.nextToken() != JsonToken.END_ARRAY) {
19 | builder += elementDeserializer.deserialize(jp, ctxt)
20 | }
21 |
22 | builder.result().iterator.buffered
23 | }
24 |
25 | def resolve(ctxt: DeserializationContext) {
26 | elementDeserializer = ctxt.findRootValueDeserializer(elementType)
27 | }
28 |
29 | override def isCachable = true
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/JValueDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
5 | import com.codahale.jerkson.AST._
6 | import collection.mutable.ArrayBuffer
7 | import com.fasterxml.jackson.databind.`type`.TypeFactory
8 | import com.codahale.jerkson.Types
9 |
10 | class JValueDeserializer(factory: TypeFactory, klass: Class[_]) extends JsonDeserializer[Object] {
11 | def deserialize(jp: JsonParser, ctxt: DeserializationContext): Object = {
12 | if (jp.getCurrentToken == null) {
13 | jp.nextToken()
14 | }
15 |
16 | val value = jp.getCurrentToken match {
17 | case JsonToken.VALUE_NUMBER_INT => JInt(BigInt(jp.getText))
18 | case JsonToken.VALUE_NUMBER_FLOAT => JFloat(jp.getDoubleValue)
19 | case JsonToken.VALUE_STRING => JString(jp.getText)
20 | case JsonToken.VALUE_TRUE => JBoolean(true)
21 | case JsonToken.VALUE_FALSE => JBoolean(false)
22 | case JsonToken.START_ARRAY => {
23 | JArray(jp.getCodec.readValue(jp, Types.build(factory, manifest[List[JValue]])))
24 | }
25 | case JsonToken.START_OBJECT => {
26 | jp.nextToken()
27 | deserialize(jp, ctxt)
28 | }
29 | case JsonToken.FIELD_NAME | JsonToken.END_OBJECT => {
30 | val fields = new ArrayBuffer[JField]
31 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
32 | val name = jp.getCurrentName
33 | jp.nextToken()
34 | fields += JField(name, jp.getCodec.readValue(jp, Types.build(factory, manifest[JValue])))
35 | jp.nextToken()
36 | }
37 | JObject(fields.toList)
38 | }
39 | case _ => throw ctxt.mappingException(classOf[JValue])
40 | }
41 |
42 | if (!klass.isAssignableFrom(value.getClass)) {
43 | throw ctxt.mappingException(klass)
44 | }
45 |
46 | value
47 | }
48 |
49 | override def isCachable = true
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/LongMapDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
5 | import com.fasterxml.jackson.databind.JavaType
6 | import scala.collection.immutable.LongMap
7 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
8 |
9 | class LongMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer {
10 | var valueDeserializer: JsonDeserializer[Object] = _
11 |
12 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
13 | var map = LongMap.empty[Object]
14 |
15 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
16 | jp.nextToken()
17 | }
18 |
19 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
20 | jp.getCurrentToken != JsonToken.END_OBJECT) {
21 | throw ctxt.mappingException(valueType.getRawClass)
22 | }
23 |
24 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
25 | try {
26 | val name = jp.getCurrentName.toLong
27 | jp.nextToken()
28 | map += ((name, valueDeserializer.deserialize(jp, ctxt)))
29 | jp.nextToken()
30 | } catch {
31 | case e: IllegalArgumentException => throw ctxt.mappingException(classOf[LongMap[_]])
32 | }
33 | }
34 | map
35 | }
36 |
37 | def resolve(ctxt: DeserializationContext) {
38 | valueDeserializer = ctxt.findRootValueDeserializer(valueType)
39 | }
40 |
41 | override def isCachable = true
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/MutableLinkedHashMapDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
5 | import scala.collection.mutable
6 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
7 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
8 |
9 | class MutableLinkedHashMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer {
10 | var valueDeserializer: JsonDeserializer[Object] = _
11 |
12 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
13 | val builder = mutable.LinkedHashMap.newBuilder[String, Object]
14 |
15 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
16 | jp.nextToken()
17 | }
18 |
19 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
20 | val name = jp.getCurrentName
21 | jp.nextToken()
22 | builder += ((name, valueDeserializer.deserialize(jp, ctxt)))
23 | jp.nextToken()
24 | }
25 |
26 | builder.result()
27 | }
28 |
29 | def resolve(ctxt: DeserializationContext) {
30 | valueDeserializer = ctxt.findRootValueDeserializer(valueType)
31 | }
32 |
33 | override def isCachable = true
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/MutableMapDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
5 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
6 | import scala.collection.mutable
7 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
8 |
9 | class MutableMapDeserializer(valueType: JavaType) extends JsonDeserializer[Object] with ResolvableDeserializer {
10 | var valueDeserializer: JsonDeserializer[Object] = _
11 |
12 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
13 | val builder = mutable.HashMap.newBuilder[String, Object]
14 |
15 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
16 | jp.nextToken()
17 | }
18 |
19 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
20 | jp.getCurrentToken != JsonToken.END_OBJECT) {
21 | throw ctxt.mappingException(valueType.getRawClass)
22 | }
23 |
24 | while (jp.getCurrentToken != JsonToken.END_OBJECT) {
25 | val name = jp.getCurrentName
26 | jp.nextToken()
27 | builder += ((name, valueDeserializer.deserialize(jp, ctxt)))
28 | jp.nextToken()
29 | }
30 |
31 | builder.result()
32 | }
33 |
34 | def resolve(ctxt: DeserializationContext) {
35 | valueDeserializer = ctxt.findRootValueDeserializer(valueType)
36 | }
37 |
38 | override def isCachable = true
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/OptionDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.JavaType
4 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
5 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
6 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
7 |
8 | class OptionDeserializer(elementType: JavaType)
9 | extends JsonDeserializer[Object] with ResolvableDeserializer {
10 |
11 | var elementDeserializer: JsonDeserializer[Object] = _
12 |
13 | override def getEmptyValue = None
14 |
15 | override def getNullValue = None
16 |
17 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
18 | if (jp.getCurrentToken == JsonToken.VALUE_NULL) {
19 | None
20 | } else {
21 | Some(elementDeserializer.deserialize(jp, ctxt))
22 | }
23 | }
24 |
25 | def resolve(ctxt: DeserializationContext) {
26 | elementDeserializer = ctxt.findRootValueDeserializer(elementType)
27 | }
28 |
29 | override def isCachable = true
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/RangeDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import collection.JavaConversions._
4 | import com.fasterxml.jackson.databind.JsonNode
5 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
6 | import com.fasterxml.jackson.databind.{JsonMappingException, DeserializationContext, JsonDeserializer}
7 | import com.fasterxml.jackson.databind.node.{IntNode, BooleanNode, NullNode, ObjectNode}
8 |
9 | class RangeDeserializer extends JsonDeserializer[Object] {
10 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
11 | if (jp.getCurrentToken == JsonToken.START_OBJECT) {
12 | jp.nextToken()
13 | }
14 |
15 | if (jp.getCurrentToken != JsonToken.FIELD_NAME &&
16 | jp.getCurrentToken != JsonToken.END_OBJECT) {
17 | throw ctxt.mappingException(classOf[Range])
18 | }
19 |
20 | val node = jp.readValueAsTree[JsonNode]
21 | val inclusiveNode = node.get("inclusive")
22 | val stepNode = node.get("step")
23 | val startNode = node.get("start")
24 | val endNode = node.get("end")
25 |
26 | if (startNode == null || !classOf[IntNode].isAssignableFrom(startNode.getClass) ||
27 | endNode == null || !classOf[IntNode].isAssignableFrom(endNode.getClass)) {
28 | throw new JsonMappingException(errorMessage(node))
29 | }
30 |
31 | val step = if (stepNode == null || !classOf[IntNode].isAssignableFrom(stepNode.getClass)) {
32 | 1
33 | } else {
34 | stepNode.intValue
35 | }
36 |
37 | val start = startNode.asInstanceOf[IntNode].intValue
38 | val end = endNode.asInstanceOf[IntNode].intValue
39 |
40 | if (inclusiveNode == null || inclusiveNode == BooleanNode.FALSE) {
41 | Range(start, end, step)
42 | } else {
43 | Range.inclusive(start, end, step)
44 | }
45 | }
46 |
47 | private def errorMessage(node: JsonNode) = {
48 | val existing = node match {
49 | case obj: ObjectNode => obj.fieldNames.mkString("[", ", ", "]")
50 | case _: NullNode => "[]" // this is what Jackson deserializes the inside of an empty object to
51 | case unknown => "a non-object"
52 | }
53 | "Invalid JSON. Needed [start, end, , ], but found %s.".format(existing)
54 | }
55 |
56 | override def isCachable = true
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/ScalaDeserializers.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind._
4 | import scala.collection.{Traversable, MapLike, immutable, mutable}
5 | import com.codahale.jerkson.AST.{JNull, JValue}
6 | import scala.collection.generic.{MapFactory, GenericCompanion}
7 | import com.fasterxml.jackson.databind.deser.Deserializers
8 | import com.fasterxml.jackson.databind.Module.SetupContext
9 |
10 | class ScalaDeserializers(classLoader: ClassLoader, context: SetupContext) extends Deserializers.Base {
11 | override def findBeanDeserializer(javaType: JavaType, config: DeserializationConfig,
12 | beanDesc: BeanDescription) = {
13 | val klass = javaType.getRawClass
14 | if (klass == classOf[Range] || klass == classOf[immutable.Range]) {
15 | new RangeDeserializer
16 | } else if (klass == classOf[StringBuilder]) {
17 | new StringBuilderDeserializer
18 | } else if (klass == classOf[List[_]] || klass == classOf[immutable.List[_]]) {
19 | createSeqDeserializer(config, javaType, List)
20 | } else if (klass == classOf[Seq[_]] || klass == classOf[immutable.Seq[_]] ||
21 | klass == classOf[Iterable[_]] || klass == classOf[Traversable[_]] ||
22 | klass == classOf[immutable.Traversable[_]]) {
23 | createSeqDeserializer(config, javaType, Seq)
24 | } else if (klass == classOf[Stream[_]] || klass == classOf[immutable.Stream[_]]) {
25 | createSeqDeserializer(config, javaType, Stream)
26 | } else if (klass == classOf[immutable.Queue[_]]) {
27 | createSeqDeserializer(config, javaType, immutable.Queue)
28 | } else if (klass == classOf[Vector[_]]) {
29 | createSeqDeserializer(config, javaType, Vector)
30 | } else if (klass == classOf[IndexedSeq[_]] || klass == classOf[immutable.IndexedSeq[_]]) {
31 | createSeqDeserializer(config, javaType, IndexedSeq)
32 | } else if (klass == classOf[mutable.ResizableArray[_]]) {
33 | createSeqDeserializer(config, javaType, mutable.ResizableArray)
34 | } else if (klass == classOf[mutable.ArraySeq[_]]) {
35 | createSeqDeserializer(config, javaType, mutable.ArraySeq)
36 | } else if (klass == classOf[mutable.MutableList[_]]) {
37 | createSeqDeserializer(config, javaType, mutable.MutableList)
38 | } else if (klass == classOf[mutable.Queue[_]]) {
39 | createSeqDeserializer(config, javaType, mutable.Queue)
40 | } else if (klass == classOf[mutable.ListBuffer[_]]) {
41 | createSeqDeserializer(config, javaType, mutable.ListBuffer)
42 | } else if (klass == classOf[mutable.ArrayBuffer[_]] || klass == classOf[mutable.Traversable[_]]) {
43 | createSeqDeserializer(config, javaType, mutable.ArrayBuffer)
44 | } else if (klass == classOf[collection.BitSet] || klass == classOf[immutable.BitSet]) {
45 | new BitSetDeserializer(immutable.BitSet)
46 | } else if (klass == classOf[mutable.BitSet]) {
47 | new BitSetDeserializer(mutable.BitSet)
48 | } else if (klass == classOf[immutable.HashSet[_]]) {
49 | createSeqDeserializer(config, javaType, immutable.HashSet)
50 | } else if (klass == classOf[Set[_]] || klass == classOf[immutable.Set[_]] || klass == classOf[collection.Set[_]]) {
51 | createSeqDeserializer(config, javaType, Set)
52 | } else if (klass == classOf[mutable.HashSet[_]]) {
53 | createSeqDeserializer(config, javaType, mutable.HashSet)
54 | } else if (klass == classOf[mutable.LinkedHashSet[_]]) {
55 | createSeqDeserializer(config, javaType, mutable.LinkedHashSet)
56 | } else if (klass == classOf[Iterator[_]] || klass == classOf[BufferedIterator[_]]) {
57 | val elementType = javaType.containedType(0)
58 | new IteratorDeserializer(elementType)
59 | } else if (klass == classOf[immutable.HashMap[_, _]] || klass == classOf[Map[_, _]] || klass == classOf[collection.Map[_, _]]) {
60 | createImmutableMapDeserializer(config, javaType, immutable.HashMap)
61 | } else if (klass == classOf[immutable.IntMap[_]]) {
62 | val valueType = javaType.containedType(0)
63 | new IntMapDeserializer(valueType)
64 | } else if (klass == classOf[immutable.LongMap[_]]) {
65 | val valueType = javaType.containedType(0)
66 | new LongMapDeserializer(valueType)
67 | } else if (klass == classOf[mutable.HashMap[_, _]] || klass == classOf[mutable.Map[_, _]]) {
68 | if (javaType.containedType(0).getRawClass == classOf[String]) {
69 | val valueType = javaType.containedType(1)
70 | new MutableMapDeserializer(valueType)
71 | } else {
72 | null
73 | }
74 | } else if (klass == classOf[mutable.LinkedHashMap[_, _]]) {
75 | if (javaType.containedType(0).getRawClass == classOf[String]) {
76 | val valueType = javaType.containedType(1)
77 | new MutableLinkedHashMapDeserializer(valueType)
78 | } else {
79 | null
80 | }
81 | } else if (klass == classOf[Option[_]]) {
82 | createOptionDeserializer(config, javaType)
83 | } else if (classOf[JValue].isAssignableFrom(klass) || klass == JNull.getClass) {
84 | new JValueDeserializer(config.getTypeFactory, klass)
85 | } else if (klass == classOf[BigInt]) {
86 | new BigIntDeserializer
87 | } else if (klass == classOf[BigDecimal]) {
88 | new BigDecimalDeserializer
89 | } else if (klass == classOf[Either[_,_]]) {
90 | new EitherDeserializer(config, javaType)
91 | } else if (classOf[Product].isAssignableFrom(klass)) {
92 | new CaseClassDeserializer(config, javaType, classLoader)
93 | } else null
94 | }
95 |
96 | private def createSeqDeserializer[CC[X] <: Traversable[X]](config: DeserializationConfig,
97 | javaType: JavaType,
98 | companion: GenericCompanion[CC]) = {
99 | val elementType = javaType.containedType(0)
100 | new SeqDeserializer[CC](companion, elementType)
101 | }
102 |
103 | private def createOptionDeserializer(config: DeserializationConfig,
104 | javaType: JavaType) = {
105 | val elementType = javaType.containedType(0)
106 | new OptionDeserializer(elementType)
107 | }
108 |
109 | private def createImmutableMapDeserializer[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](config: DeserializationConfig,
110 | javaType: JavaType,
111 | companion: MapFactory[CC]) = {
112 | val keyType = javaType.containedType(0)
113 | val valueType = javaType.containedType(1)
114 | if (keyType.getRawClass == classOf[String]) {
115 | new ImmutableMapDeserializer[CC](companion, valueType)
116 | } else if (keyType.getRawClass == classOf[Int] || keyType.getRawClass == classOf[java.lang.Integer]) {
117 | new IntMapDeserializer(valueType)
118 | } else if (keyType.getRawClass == classOf[Long] || keyType.getRawClass == classOf[java.lang.Long]) {
119 | new LongMapDeserializer(valueType)
120 | } else {
121 | null
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/SeqDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
4 | import com.fasterxml.jackson.databind.JavaType
5 | import com.fasterxml.jackson.databind.{JsonDeserializer, DeserializationContext}
6 | import collection.generic.GenericCompanion
7 | import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
8 |
9 | class SeqDeserializer[+CC[X] <: Traversable[X]](companion: GenericCompanion[CC],
10 | elementType: JavaType)
11 | extends JsonDeserializer[Object] with ResolvableDeserializer {
12 |
13 | var elementDeserializer: JsonDeserializer[Object] = _
14 |
15 | def deserialize(jp: JsonParser, ctxt: DeserializationContext): CC[Object] = {
16 | val builder = companion.newBuilder[Object]
17 |
18 | if (jp.getCurrentToken != JsonToken.START_ARRAY) {
19 | throw ctxt.mappingException(elementType.getRawClass)
20 | }
21 |
22 | while (jp.nextToken() != JsonToken.END_ARRAY) {
23 | builder += elementDeserializer.deserialize(jp, ctxt)
24 | }
25 |
26 | builder.result()
27 | }
28 |
29 | def resolve(ctxt: DeserializationContext) {
30 | elementDeserializer = ctxt.findRootValueDeserializer(elementType)
31 | }
32 |
33 | override def isCachable = true
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/deser/StringBuilderDeserializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.deser
2 |
3 | import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
4 | import com.fasterxml.jackson.core.{JsonToken, JsonParser}
5 |
6 | class StringBuilderDeserializer extends JsonDeserializer[Object] {
7 | def deserialize(jp: JsonParser, ctxt: DeserializationContext) = {
8 | if (jp.getCurrentToken != JsonToken.VALUE_STRING) {
9 | throw ctxt.mappingException(classOf[StringBuilder])
10 | }
11 |
12 | new StringBuilder(jp.getText)
13 | }
14 |
15 | override def isCachable = true
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/CaseClassSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import java.lang.reflect.Modifier
4 | import com.codahale.jerkson.JsonSnakeCase
5 | import com.codahale.jerkson.Util._
6 | import com.fasterxml.jackson.core.JsonGenerator
7 | import com.fasterxml.jackson.annotation.{JsonIgnore, JsonIgnoreProperties}
8 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
9 |
10 | class CaseClassSerializer[A <: Product](klass: Class[_]) extends JsonSerializer[A] {
11 | private val isSnakeCase = klass.isAnnotationPresent(classOf[JsonSnakeCase])
12 | private val ignoredFields = if (klass.isAnnotationPresent(classOf[JsonIgnoreProperties])) {
13 | klass.getAnnotation(classOf[JsonIgnoreProperties]).value().toSet
14 | } else Set.empty[String]
15 |
16 | private val nonIgnoredFields = klass.getDeclaredFields.filterNot { f =>
17 | f.getAnnotation(classOf[JsonIgnore]) != null ||
18 | ignoredFields(f.getName) ||
19 | (f.getModifiers & Modifier.TRANSIENT) != 0 ||
20 | f.getName.contains("$")
21 | }
22 |
23 | private val methods = klass.getDeclaredMethods
24 | .filter { _.getParameterTypes.isEmpty }
25 | .map { m => m.getName -> m }.toMap
26 |
27 | def serialize(value: A, json: JsonGenerator, provider: SerializerProvider) {
28 | json.writeStartObject()
29 | for (field <- nonIgnoredFields) {
30 | val methodOpt = methods.get(field.getName)
31 | val fieldValue: Object = methodOpt.map { _.invoke(value) }.getOrElse(field.get(value))
32 | if (fieldValue != None) {
33 | val fieldName = methodOpt.map { _.getName }.getOrElse(field.getName)
34 | provider.defaultSerializeField(if (isSnakeCase) snakeCase(fieldName) else fieldName, fieldValue, json)
35 | }
36 | }
37 | json.writeEndObject()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/EitherSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class EitherSerializer extends JsonSerializer[Either[_, _]] {
7 | def serialize(value: Either[_, _], json: JsonGenerator, provider: SerializerProvider) {
8 | provider.defaultSerializeValue(value match {
9 | case Left(o) => o.asInstanceOf[Object]
10 | case Right(o) => o.asInstanceOf[Object]
11 | }, json)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/IterableSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class IterableSerializer extends JsonSerializer[Iterable[_]] {
7 | def serialize(value: Iterable[_], json: JsonGenerator, provider: SerializerProvider) {
8 | json.writeStartArray()
9 | for (element <- value) {
10 | provider.defaultSerializeValue(element, json)
11 | }
12 | json.writeEndArray()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/IteratorSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class IteratorSerializer extends JsonSerializer[Iterator[_]] {
7 | def serialize(value: Iterator[_], json: JsonGenerator,
8 | provider: SerializerProvider) {
9 | json.writeStartArray()
10 | for (element <- value) {
11 | provider.defaultSerializeValue(element, json)
12 | }
13 | json.writeEndArray()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/JValueSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 | import com.codahale.jerkson.AST._
6 | import java.math.BigInteger
7 |
8 | class JValueSerializer extends JsonSerializer[JValue] {
9 | def serialize(value: JValue, json: JsonGenerator, provider: SerializerProvider) {
10 | value match {
11 | case JInt(v) => json.writeNumber(new BigInteger(v.toString()))
12 | case JFloat(v) => json.writeNumber(v)
13 | case JString(v) => json.writeString(v)
14 | case JBoolean(v) => json.writeBoolean(v)
15 | case JArray(elements) => json.writeObject(elements)
16 | case JField(name, value) => {
17 | json.writeFieldName(name)
18 | json.writeObject(value)
19 | }
20 | case JObject(fields) => {
21 | json.writeStartObject()
22 | fields.foreach(json.writeObject)
23 | json.writeEndObject()
24 | }
25 | case JNull => json.writeNull()
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/MapSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class MapSerializer extends JsonSerializer[collection.Map[_ ,_]] {
7 | def serialize(map: collection.Map[_,_], json: JsonGenerator, provider: SerializerProvider) {
8 | json.writeStartObject()
9 | for ((key, value) <- map) {
10 | provider.defaultSerializeField(key.toString, value, json)
11 | }
12 | json.writeEndObject()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/OptionSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class OptionSerializer extends JsonSerializer[Option[_]] {
7 | def serialize(value: Option[_], json: JsonGenerator,
8 | provider: SerializerProvider) {
9 | provider.defaultSerializeValue(value.orNull, json)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/RangeSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class RangeSerializer extends JsonSerializer[Range] {
7 | def serialize(value: Range, json: JsonGenerator, provider: SerializerProvider) {
8 | json.writeStartObject()
9 | json.writeNumberField("start", value.start)
10 | json.writeNumberField("end", value.end)
11 |
12 | if (value.step != 1) {
13 | json.writeNumberField("step", value.step)
14 | }
15 |
16 | if (value.isInclusive) {
17 | json.writeBooleanField("inclusive", value.isInclusive)
18 | }
19 |
20 | json.writeEndObject()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/ScalaSerializers.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.codahale.jerkson.AST.JValue
4 | import com.fasterxml.jackson.databind._
5 | import com.fasterxml.jackson.databind.ser.Serializers
6 |
7 | class ScalaSerializers extends Serializers.Base {
8 | override def findSerializer(config: SerializationConfig, javaType: JavaType, beanDesc: BeanDescription) = {
9 | val ser: Object = if (classOf[Option[_]].isAssignableFrom(beanDesc.getBeanClass)) {
10 | new OptionSerializer
11 | } else if (classOf[StringBuilder].isAssignableFrom(beanDesc.getBeanClass)) {
12 | new StringBuilderSerializer
13 | } else if (classOf[collection.Map[_,_]].isAssignableFrom(beanDesc.getBeanClass)) {
14 | new MapSerializer
15 | } else if (classOf[Range].isAssignableFrom(beanDesc.getBeanClass)) {
16 | new RangeSerializer
17 | } else if (classOf[Iterable[_]].isAssignableFrom(beanDesc.getBeanClass)) {
18 | new IterableSerializer
19 | } else if (classOf[Iterator[_]].isAssignableFrom(beanDesc.getBeanClass)) {
20 | new IteratorSerializer
21 | } else if (classOf[JValue].isAssignableFrom(beanDesc.getBeanClass)) {
22 | new JValueSerializer
23 | } else if (classOf[Either[_,_]].isAssignableFrom(beanDesc.getBeanClass)) {
24 | new EitherSerializer
25 | } else if (classOf[Product].isAssignableFrom(beanDesc.getBeanClass)) {
26 | new CaseClassSerializer(beanDesc.getBeanClass)
27 | } else {
28 | null
29 | }
30 | ser.asInstanceOf[JsonSerializer[Object]]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/ser/StringBuilderSerializer.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.ser
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator
4 | import com.fasterxml.jackson.databind.{SerializerProvider, JsonSerializer}
5 |
6 | class StringBuilderSerializer extends JsonSerializer[StringBuilder] {
7 | def serialize(value: StringBuilder, json: JsonGenerator, provider: SerializerProvider) {
8 | json.writeString(value.toString())
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/CaseClassSigParser.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 |
3 | import com.codahale.jerkson.util.scalax.rules.scalasig._
4 | import com.fasterxml.jackson.databind.JavaType
5 | import com.fasterxml.jackson.databind.`type`.TypeFactory
6 | import scala.reflect.ScalaSignature
7 | import scala.reflect.generic.ByteCodecs
8 |
9 | class MissingPickledSig(clazz: Class[_]) extends Error("Failed to parse pickled Scala signature from: %s".format(clazz))
10 |
11 | class MissingExpectedType(clazz: Class[_]) extends Error(
12 | "Parsed pickled Scala signature, but no expected type found: %s"
13 | .format(clazz)
14 | )
15 |
16 | object CaseClassSigParser {
17 | val SCALA_SIG = "ScalaSig"
18 | val SCALA_SIG_ANNOTATION = "Lscala/reflect/ScalaSignature;"
19 | val BYTES_VALUE = "bytes"
20 |
21 | private def parseClassFileFromByteCode(clazz: Class[_]): Option[ClassFile] = try {
22 | // taken from ScalaSigParser parse method with the explicit purpose of walking away from NPE
23 | val byteCode = ByteCode.forClass(clazz)
24 | Option(ClassFileParser.parse(byteCode))
25 | }
26 | catch {
27 | case e: NullPointerException => None // yes, this is the exception, but it is totally unhelpful to the end user
28 | }
29 |
30 | private def parseByteCodeFromAnnotation(clazz: Class[_]): Option[ByteCode] = {
31 | if (clazz.isAnnotationPresent(classOf[ScalaSignature])) {
32 | val sig = clazz.getAnnotation(classOf[ScalaSignature])
33 | val bytes = sig.bytes.getBytes("UTF-8")
34 | val len = ByteCodecs.decode(bytes)
35 | Option(ByteCode(bytes.take(len)))
36 | } else {
37 | None
38 | }
39 | }
40 |
41 | private def parseScalaSig(_clazz: Class[_], classLoader: ClassLoader): Option[ScalaSig] = {
42 | val clazz = findRootClass(_clazz, classLoader)
43 | parseClassFileFromByteCode(clazz).map(ScalaSigParser.parse(_)).getOrElse(None) orElse
44 | parseByteCodeFromAnnotation(clazz).map(ScalaSigAttributeParsers.parse(_)) orElse
45 | None
46 | }
47 |
48 | protected def findRootClass(klass: Class[_], classLoader: ClassLoader) =
49 | loadClass(klass.getName.split("\\$").head, classLoader)
50 |
51 | protected def simpleName(klass: Class[_]) =
52 | klass.getName.split("\\$").last
53 |
54 | protected def findSym[A](clazz: Class[A], classLoader: ClassLoader) = {
55 | val name = simpleName(clazz)
56 | val pss = parseScalaSig(clazz, classLoader)
57 | pss match {
58 | case Some(x) => {
59 | val topLevelClasses = x.topLevelClasses
60 | topLevelClasses.headOption match {
61 | case Some(tlc) => {
62 | tlc
63 | }
64 | case None => {
65 | val topLevelObjects = x.topLevelObjects
66 | topLevelObjects.headOption match {
67 | case Some(tlo) => {
68 | x.symbols.find { s => !s.isModule && s.name == name } match {
69 | case Some(s) => s.asInstanceOf[ClassSymbol]
70 | case None => throw new MissingExpectedType(clazz)
71 | }
72 | }
73 | case _ => throw new MissingExpectedType(clazz)
74 | }
75 | }
76 | }
77 | }
78 | case None => throw new MissingPickledSig(clazz)
79 | }
80 | }
81 |
82 | def parse[A](clazz: Class[A], factory: TypeFactory, classLoader: ClassLoader) = {
83 | findSym(clazz, classLoader).children.filter(c => c.isCaseAccessor && !c.isPrivate)
84 | .flatMap { ms =>
85 | ms.asInstanceOf[MethodSymbol].infoType match {
86 | case NullaryMethodType(t: TypeRefType) => ms.name -> typeRef2JavaType(t, factory, classLoader) :: Nil
87 | case _ => Nil
88 | }
89 | }
90 | }
91 |
92 | protected def typeRef2JavaType(ref: TypeRefType, factory: TypeFactory, classLoader: ClassLoader): JavaType = {
93 | try {
94 | if (ref.symbol.path == "scala.Array") {
95 | val elementType = typeRef2JavaType(ref.typeArgs.head.asInstanceOf[TypeRefType], factory, classLoader)
96 | val realElementType = elementType.getRawClass.getName match {
97 | case "java.lang.Boolean" => classOf[Boolean]
98 | case "java.lang.Byte" => classOf[Byte]
99 | case "java.lang.Character" => classOf[Char]
100 | case "java.lang.Double" => classOf[Double]
101 | case "java.lang.Float" => classOf[Float]
102 | case "java.lang.Integer" => classOf[Int]
103 | case "java.lang.Long" => classOf[Long]
104 | case "java.lang.Short" => classOf[Short]
105 | case _ => elementType.getRawClass
106 | }
107 |
108 | val array = java.lang.reflect.Array.newInstance(realElementType, 0)
109 | factory.constructType(array.getClass)
110 | } else {
111 | val klass = loadClass(ref.symbol.path, classLoader)
112 | factory.constructParametricType(
113 | klass, ref.typeArgs.map {
114 | t => typeRef2JavaType(t.asInstanceOf[TypeRefType], factory, classLoader)
115 | }: _*
116 | )
117 | }
118 | } catch {
119 | case e: Throwable => {
120 | e.printStackTrace()
121 | null
122 | }
123 | }
124 | }
125 |
126 | protected def loadClass(path: String, classLoader: ClassLoader) = path match {
127 | case "scala.Predef.Map" => classOf[Map[_, _]]
128 | case "scala.Predef.Set" => classOf[Set[_]]
129 | case "scala.Predef.String" => classOf[String]
130 | case "scala.package.List" => classOf[List[_]]
131 | case "scala.package.Seq" => classOf[Seq[_]]
132 | case "scala.package.Sequence" => classOf[Seq[_]]
133 | case "scala.package.Collection" => classOf[Seq[_]]
134 | case "scala.package.IndexedSeq" => classOf[IndexedSeq[_]]
135 | case "scala.package.RandomAccessSeq" => classOf[IndexedSeq[_]]
136 | case "scala.package.Iterable" => classOf[Iterable[_]]
137 | case "scala.package.Iterator" => classOf[Iterator[_]]
138 | case "scala.package.Vector" => classOf[Vector[_]]
139 | case "scala.package.BigDecimal" => classOf[BigDecimal]
140 | case "scala.package.BigInt" => classOf[BigInt]
141 | case "scala.package.Integer" => classOf[java.lang.Integer]
142 | case "scala.package.Character" => classOf[java.lang.Character]
143 | case "scala.Long" => classOf[java.lang.Long]
144 | case "scala.Int" => classOf[java.lang.Integer]
145 | case "scala.Boolean" => classOf[java.lang.Boolean]
146 | case "scala.Short" => classOf[java.lang.Short]
147 | case "scala.Byte" => classOf[java.lang.Byte]
148 | case "scala.Float" => classOf[java.lang.Float]
149 | case "scala.Double" => classOf[java.lang.Double]
150 | case "scala.Char" => classOf[java.lang.Character]
151 | case "scala.Any" => classOf[Any]
152 | case "scala.AnyRef" => classOf[AnyRef]
153 | case name => classLoader.loadClass(name)
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/Memoisable.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 |
5 | import scala.collection.mutable.HashMap
6 |
7 | trait MemoisableRules extends Rules {
8 | def memo[In <: Memoisable, Out, A, X](key: AnyRef)
9 | (toRule: => In => Result[Out, A, X]) = {
10 | lazy val rule = toRule
11 | from[In] {in => in.memo(key, rule(in))}
12 | }
13 |
14 | override def ruleWithName[In, Out, A, X](name: String,
15 | f: In => rules.Result[Out, A, X]) = super.ruleWithName(
16 | name, (in: In) =>
17 | in match {
18 | case s: Memoisable => s.memo(name, f(in))
19 | case _ => f(in)
20 | }
21 | )
22 | }
23 |
24 | trait Memoisable {
25 | def memo[A](key: AnyRef, a: => A): A
26 | }
27 |
28 |
29 | object DefaultMemoisable {
30 | var debug = false
31 | }
32 |
33 | trait DefaultMemoisable extends Memoisable {
34 | protected val map = new HashMap[AnyRef, Any]
35 |
36 | def memo[A](key: AnyRef, a: => A) = {
37 | map.getOrElseUpdate(key, compute(key, a)).asInstanceOf[A]
38 | }
39 |
40 | protected def compute[A](key: AnyRef, a: => A): Any = a match {
41 | case success: Success[_, _] => onSuccess(key, success); success
42 | case other =>
43 | if (DefaultMemoisable.debug) println(key + " -> " + other)
44 | other
45 | }
46 |
47 | protected def onSuccess[S, T](key: AnyRef, result: Success[S, T]) {
48 | val Success(out, t) = result
49 | if (DefaultMemoisable.debug) println(key + " -> " + t + " (" + out + ")")
50 | }
51 | }
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/Result.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 |
5 | /**Represents the combined value of two rules applied in sequence.
6 | *
7 | * @see the Scala parser combinator
8 | */
9 | case class ~[+A, +B](_1: A, _2: B) {
10 | override def toString = "(" + _1 + " ~ " + _2 + ")"
11 | }
12 |
13 |
14 | sealed abstract class Result[+Out, +A, +X] {
15 | def out: Out
16 |
17 | def value: A
18 |
19 | def error: X
20 |
21 | implicit def toOption: Option[A]
22 |
23 | def map[B](f: A => B): Result[Out, B, X]
24 |
25 | def mapOut[Out2](f: Out => Out2): Result[Out2, A, X]
26 |
27 | def map[Out2, B](f: (Out, A) => (Out2, B)): Result[Out2, B, X]
28 |
29 | def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, X]
30 |
31 | def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, X]
32 | }
33 |
34 | case class Success[+Out, +A](out: Out,
35 | value: A) extends Result[Out, A, Nothing] {
36 | def error = throw new ScalaSigParserError("No error")
37 |
38 | def toOption = Some(value)
39 |
40 | def map[B](f: A => B): Result[Out, B, Nothing] = Success(out, f(value))
41 |
42 | def mapOut[Out2](f: Out => Out2): Result[Out2, A, Nothing] = Success(f(out), value)
43 |
44 | def map[Out2, B](f: (Out, A) => (Out2, B)): Success[Out2, B] = f(out, value) match {case (out2, b) => Success(out2, b)}
45 |
46 | def flatMap[Out2, B](f: (Out, A) => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = f(out, value)
47 |
48 | def orElse[Out2 >: Out, B >: A](other: => Result[Out2, B, Nothing]): Result[Out2, B, Nothing] = this
49 | }
50 |
51 | sealed abstract class NoSuccess[+X] extends Result[Nothing, Nothing, X] {
52 | def out = throw new ScalaSigParserError("No output")
53 |
54 | def value = throw new ScalaSigParserError("No value")
55 |
56 | def toOption = None
57 |
58 | def map[B](f: Nothing => B) = this
59 |
60 | def mapOut[Out2](f: Nothing => Out2) = this
61 |
62 | def map[Out2, B](f: (Nothing, Nothing) => (Out2, B)) = this
63 |
64 | def flatMap[Out2, B](f: (Nothing, Nothing) => Result[Out2, B, Nothing]) = this
65 |
66 | def orElse[Out2, B](other: => Result[Out2, B, Nothing]) = other
67 | }
68 |
69 | case object Failure extends NoSuccess[Nothing] {
70 | def error = throw new ScalaSigParserError("No error")
71 | }
72 |
73 | case class ScalaSigParserError(msg: String) extends RuntimeException(msg)
74 |
75 | case class Error[+X](error: X) extends NoSuccess[X] {
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/Rule.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 |
5 | /**A Rule is a function from some input to a Result. The result may be:
6 | *
7 | * - Success, with a value of some type and an output that may serve as the input to subsequent rules.
8 | * - Failure. A failure may result in some alternative rule being applied.
9 | * - Error. No further rules should be attempted.
10 | *
11 | *
12 | * @author Andrew Foggin
13 | *
14 | * Inspired by the Scala parser combinator.
15 | */
16 | trait Rule[-In, +Out, +A, +X] extends (In => Result[Out, A, X]) {
17 | val factory: Rules
18 |
19 | import factory._
20 |
21 | def as(name: String) = ruleWithName(name, this)
22 |
23 | def flatMap[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = mapResult {
24 | case Success(out, a) => fa2ruleb(a)(out)
25 | case Failure => Failure
26 | case err@Error(_) => err
27 | }
28 |
29 | def map[B](fa2b: A => B) = flatMap {a => out => Success(out, fa2b(a))}
30 |
31 | def filter(f: A => Boolean) = flatMap {a =>
32 | out => if (f(a)) Success(out, a) else Failure
33 | }
34 |
35 | def mapResult[Out2, B, Y](f: Result[Out, A, X] => Result[Out2, B, Y]) = rule {
36 | in: In => f(apply(in))
37 | }
38 |
39 | def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] {
40 | val factory = Rule.this.factory
41 | lazy val choices = Rule.this :: other :: Nil
42 | }
43 |
44 | def orError[In2 <: In] = this orElse (error[In2])
45 |
46 | def |[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]) = orElse(other)
47 |
48 | def ^^[B](fa2b: A => B) = map(fa2b)
49 |
50 | def ^^?[B](pf: PartialFunction[A, B]) = filter(pf.isDefinedAt(_)) ^^ pf
51 |
52 | def ??(pf: PartialFunction[A, Any]) = filter(pf.isDefinedAt(_))
53 |
54 | def -^[B](b: B) = map {any => b}
55 |
56 | /**Maps an Error */
57 | def !^[Y](fx2y: X => Y) = mapResult {
58 | case s@Success(_, _) => s
59 | case Failure => Failure
60 | case Error(x) => Error(fx2y(x))
61 | }
62 |
63 | def >>[Out2, B, X2 >: X](fa2ruleb: A => Out => Result[Out2, B, X2]) = flatMap(fa2ruleb)
64 |
65 | def >->[Out2, B, X2 >: X](fa2resultb: A => Result[Out2, B, X2]) = flatMap {
66 | a => any => fa2resultb(a)
67 | }
68 |
69 | def >>?[Out2, B, X2 >: X](pf: PartialFunction[A, Rule[Out, Out2, B, X2]]) = filter(pf isDefinedAt _) flatMap pf
70 |
71 | def >>&[B, X2 >: X](fa2ruleb: A => Out => Result[Any, B, X2]) = flatMap {a =>
72 | out => fa2ruleb(a)(out) mapOut {any => out}
73 | }
74 |
75 | def ~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield new ~(a, b)
76 |
77 | def ~-[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield a
78 |
79 | def -~[Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next) yield b
80 |
81 | def ~++[Out2, B >: A, X2 >: X](next: => Rule[Out, Out2, Seq[B], X2]) = for (a <- this; b <- next) yield a :: b.toList
82 |
83 | /**Apply the result of this rule to the function returned by the next rule */
84 | def ~>[Out2, B, X2 >: X](next: => Rule[Out, Out2, A => B, X2]) = for (a <- this; fa2b <- next) yield fa2b(a)
85 |
86 | /**Apply the result of this rule to the function returned by the previous rule */
87 | def <~:[InPrev, B, X2 >: X](prev: => Rule[InPrev, In, A => B, X2]) = for (fa2b <- prev; a <- this) yield fa2b(a)
88 |
89 | def ~ = for (a <- this; b <- next orError) yield new ~(a, b)
90 |
91 | def ~- = for (a <- this; b <- next orError) yield a
92 |
93 | def -~ = for (a <- this; b <- next orError) yield b
94 |
95 | def -[In2 <: In](exclude: => Rule[In2, Any, Any, Any]) = !exclude -~ this
96 |
97 | /**^~^(f) is equivalent to ^^ { case b1 ~ b2 => f(b1, b2) }
98 | */
99 | def ^~^[B1, B2, B >: A <% B1 ~ B2, C](f: (B1, B2) => C) = map {a =>
100 | (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)}
101 | }
102 |
103 | /**^~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 => f(b1, b2, b3) }
104 | */
105 | def ^~~^[B1, B2, B3, B >: A <% B1 ~ B2 ~ B3, C](f: (B1, B2, B3) => C) = map {
106 | a =>
107 | (a: B1 ~ B2 ~ B3) match {case b1 ~ b2 ~ b3 => f(b1, b2, b3)}
108 | }
109 |
110 | /**^~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4) }
111 | */
112 | def ^~~~^[B1, B2, B3, B4, B >: A <% B1 ~ B2 ~ B3 ~ B4, C](f: (B1, B2, B3, B4) => C) = map {
113 | a =>
114 | (a: B1 ~ B2 ~ B3 ~ B4) match {case b1 ~ b2 ~ b3 ~ b4 => f(b1, b2, b3, b4)}
115 | }
116 |
117 | /**^~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5) }
118 | */
119 | def ^~~~~^[B1, B2, B3, B4, B5, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5, C](f: (B1, B2, B3, B4, B5) => C) = map {
120 | a =>
121 | (a: B1 ~ B2 ~ B3 ~ B4 ~ B5) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 => f(b1, b2, b3, b4, b5)}
122 | }
123 |
124 | /**^~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) }
125 | */
126 | def ^~~~~~^[B1, B2, B3, B4, B5, B6, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6, C](f: (B1, B2, B3, B4, B5, B6) => C) = map {
127 | a =>
128 | (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6)}
129 | }
130 |
131 | /**^~~~~~~^(f) is equivalent to ^^ { case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 => f(b1, b2, b3, b4, b5, b6) }
132 | */
133 | def ^~~~~~~^[B1, B2, B3, B4, B5, B6, B7, B >: A <% B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7, C](f: (B1, B2, B3, B4, B5, B6, B7) => C) = map {
134 | a =>
135 | (a: B1 ~ B2 ~ B3 ~ B4 ~ B5 ~ B6 ~ B7) match {case b1 ~ b2 ~ b3 ~ b4 ~ b5 ~ b6 ~ b7 => f(b1, b2, b3, b4, b5, b6, b7)}
136 | }
137 |
138 | /**>~>(f) is equivalent to >> { case b1 ~ b2 => f(b1, b2) }
139 | */
140 | def >~>[Out2, B1, B2, B >: A <% B1 ~ B2, C, X2 >: X](f: (B1, B2) => Out => Result[Out2, C, X2]) = flatMap {
141 | a =>
142 | (a: B1 ~ B2) match {case b1 ~ b2 => f(b1, b2)}
143 | }
144 |
145 | /**^-^(f) is equivalent to ^^ { b2 => b1 => f(b1, b2) }
146 | */
147 | def ^-^[B1, B2 >: A, C](f: (B1, B2) => C) = map {b2: B2 =>
148 | b1: B1 => f(b1, b2)
149 | }
150 |
151 | /**^~>~^(f) is equivalent to ^^ { case b2 ~ b3 => b1 => f(b1, b2, b3) }
152 | */
153 | def ^~>~^[B1, B2, B3, B >: A <% B2 ~ B3, C](f: (B1, B2, B3) => C) = map {a =>
154 | (a: B2 ~ B3) match {case b2 ~ b3 => b1: B1 => f(b1, b2, b3)}
155 | }
156 | }
157 |
158 |
159 | trait Choice[-In, +Out, +A, +X] extends Rule[In, Out, A, X] {
160 | def choices: List[Rule[In, Out, A, X]]
161 |
162 | def apply(in: In) = {
163 | def oneOf(list: List[Rule[In, Out, A, X]]): Result[Out, A, X] = list match {
164 | case Nil => Failure
165 | case first :: rest => first(in) match {
166 | case Failure => oneOf(rest)
167 | case result => result
168 | }
169 | }
170 | oneOf(choices)
171 | }
172 |
173 | override def orElse[In2 <: In, Out2 >: Out, A2 >: A, X2 >: X](other: => Rule[In2, Out2, A2, X2]): Rule[In2, Out2, A2, X2] = new Choice[In2, Out2, A2, X2] {
174 | val factory = Choice.this.factory
175 | lazy val choices = Choice.this.choices ::: other :: Nil
176 | }
177 | }
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/Rules.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 |
5 | trait Name {
6 | def name: String
7 |
8 | override def toString = name
9 | }
10 |
11 | /**A factory for rules.
12 | *
13 | * @author Andrew Foggin
14 | *
15 | * Inspired by the Scala parser combinator.
16 | */
17 | trait Rules {
18 | implicit def rule[In, Out, A, X](f: In => Result[Out, A, X]): Rule[In, Out, A, X] = new DefaultRule(f)
19 |
20 | implicit def inRule[In, Out, A, X](rule: Rule[In, Out, A, X]): InRule[In, Out, A, X] = new InRule(rule)
21 |
22 | implicit def seqRule[In, A, X](rule: Rule[In, In, A, X]): SeqRule[In, A, X] = new SeqRule(rule)
23 |
24 | def from[In] = new {
25 | def apply[Out, A, X](f: In => Result[Out, A, X]) = rule(f)
26 | }
27 |
28 | def state[s] = new StateRules {
29 | type S = s
30 | val factory = Rules.this
31 | }
32 |
33 | def success[Out, A](out: Out, a: A) = rule {in: Any => Success(out, a)}
34 |
35 | def failure = rule {in: Any => Failure}
36 |
37 | def error[In] = rule {in: In => Error(in)}
38 |
39 | def error[X](err: X) = rule {in: Any => Error(err)}
40 |
41 | def oneOf[In, Out, A, X](rules: Rule[In, Out, A, X]*): Rule[In, Out, A, X] = new Choice[In, Out, A, X] {
42 | val factory = Rules.this
43 | val choices = rules.toList
44 | }
45 |
46 | def ruleWithName[In, Out, A, X](_name: String,
47 | f: In => Result[Out, A, X]): Rule[In, Out, A, X] with Name =
48 | new DefaultRule(f) with Name {
49 | val name = _name
50 | }
51 |
52 | class DefaultRule[In, Out, A, X](f: In => Result[Out, A, X]) extends Rule[In, Out, A, X] {
53 | val factory = Rules.this
54 |
55 | def apply(in: In) = f(in)
56 | }
57 |
58 | /**Converts a rule into a function that throws an Exception on failure. */
59 | def expect[In, Out, A, Any](rule: Rule[In, Out, A, Any]): In => A = (in) =>
60 | rule(in) match {
61 | case Success(_, a) => a
62 | case Failure => throw new ScalaSigParserError("Unexpected failure")
63 | case Error(x) => throw new ScalaSigParserError("Unexpected error: " + x)
64 | }
65 | }
66 |
67 | /**A factory for rules that apply to a particular context.
68 | *
69 | * @requires S the context to which rules apply.
70 | *
71 | * @author Andrew Foggin
72 | *
73 | * Inspired by the Scala parser combinator.
74 | */
75 | trait StateRules {
76 | type S
77 | type Rule[+A, +X] = rules.Rule[S, S, A, X]
78 |
79 | val factory: Rules
80 |
81 | import factory._
82 |
83 | def apply[A, X](f: S => Result[S, A, X]) = rule(f)
84 |
85 | def unit[A](a: => A) = apply {s => Success(s, a)}
86 |
87 | def read[A](f: S => A) = apply {s => Success(s, f(s))}
88 |
89 | def get = apply {s => Success(s, s)}
90 |
91 | def set(s: => S) = apply {oldS => Success(s, oldS)}
92 |
93 | def update(f: S => S) = apply {s => Success(s, f(s))}
94 |
95 | def nil = unit(Nil)
96 |
97 | def none = unit(None)
98 |
99 | /**Create a rule that identities if f(in) is true. */
100 | def cond(f: S => Boolean) = get filter f
101 |
102 | /**Create a rule that succeeds if all of the given rules succeed.
103 | @param rules the rules to apply in sequence.
104 | */
105 | def allOf[A, X](rules: Seq[Rule[A, X]]) = {
106 | def rep(in: S, rules: List[Rule[A, X]],
107 | results: List[A]): Result[S, List[A], X] = {
108 | rules match {
109 | case Nil => Success(in, results.reverse)
110 | case rule :: tl => rule(in) match {
111 | case Failure => Failure
112 | case Error(x) => Error(x)
113 | case Success(out, v) => rep(out, tl, v :: results)
114 | }
115 | }
116 | }
117 | in: S => rep(in, rules.toList, Nil)
118 | }
119 |
120 |
121 | /**Create a rule that succeeds with a list of all the provided rules that succeed.
122 | @param rules the rules to apply in sequence.
123 | */
124 | def anyOf[A, X](rules: Seq[Rule[A, X]]) = allOf(rules.map(_ ?)) ^^ {
125 | opts => opts.flatMap(x => x)
126 | }
127 |
128 | /**Repeatedly apply a rule from initial value until finished condition is met. */
129 | def repeatUntil[T, X](rule: Rule[T => T, X])(finished: T => Boolean)
130 | (initial: T) = apply {
131 | // more compact using HoF but written this way so it's tail-recursive
132 | def rep(in: S, t: T): Result[S, T, X] = {
133 | if (finished(t)) Success(in, t)
134 | else rule(in) match {
135 | case Success(out, f) => rep(out, f(t))
136 | case Failure => Failure
137 | case Error(x) => Error(x)
138 | }
139 | }
140 | in => rep(in, initial)
141 | }
142 |
143 |
144 | }
145 |
146 | trait RulesWithState extends Rules with StateRules {
147 | val factory = this
148 | }
149 |
150 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/SeqRule.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 |
5 | /**
6 | * A workaround for the difficulties of dealing with
7 | * a contravariant 'In' parameter type...
8 | */
9 | class InRule[In, +Out, +A, +X](rule: Rule[In, Out, A, X]) {
10 |
11 | def mapRule[Out2, B, Y](f: Result[Out, A, X] => In => Result[Out2, B, Y]): Rule[In, Out2, B, Y] = rule.factory.rule {
12 | in: In => f(rule(in))(in)
13 | }
14 |
15 | /**Creates a rule that succeeds only if the original rule would fail on the given context. */
16 | def unary_! : Rule[In, In, Unit, Nothing] = mapRule {
17 | case Success(_, _) => in: In => Failure
18 | case _ => in: In => Success(in, ())
19 | }
20 |
21 | /**Creates a rule that succeeds if the original rule succeeds, but returns the original input. */
22 | def & : Rule[In, In, A, X] = mapRule {
23 | case Success(_, a) => in: In => Success(in, a)
24 | case Failure => in: In => Failure
25 | case Error(x) => in: In => Error(x)
26 | }
27 | }
28 |
29 | class SeqRule[S, +A, +X](rule: Rule[S, S, A, X]) {
30 |
31 | import rule.factory._
32 |
33 | def ? = rule mapRule {
34 | case Success(out, a) => in: S => Success(out, Some(a))
35 | case Failure => in: S => Success(in, None)
36 | case Error(x) => in: S => Error(x)
37 | }
38 |
39 | /**Creates a rule that always succeeds with a Boolean value.
40 | * Value is 'true' if this rule succeeds, 'false' otherwise */
41 | def -? = ? map {_ isDefined}
42 |
43 | def * = from[S] {
44 | // tail-recursive function with reverse list accumulator
45 | def rep(in: S, acc: List[A]): Result[S, List[A], X] = rule(in) match {
46 | case Success(out, a) => rep(out, a :: acc)
47 | case Failure => Success(in, acc.reverse)
48 | case err: Error[_] => err
49 | }
50 | in => rep(in, Nil)
51 | }
52 |
53 | def + = rule ~++ *
54 |
55 | def ~>?[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f ?) yield fs.foldLeft[B](a) {(b,
56 | f) => f(b)
57 | }
58 |
59 | def ~>*[B >: A, X2 >: X](f: => Rule[S, S, B => B, X2]) = for (a <- rule; fs <- f *) yield fs.foldLeft[B](a) {(b,
60 | f) => f(b)
61 | }
62 |
63 | def ~*~[B >: A, X2 >: X](join: => Rule[S, S, (B, B) => B, X2]) = {
64 | this ~>* (for (f <- join; a <- rule) yield f(_: B, a))
65 | }
66 |
67 | /**Repeats this rule one or more times with a separator (which is discarded) */
68 | def +/[X2 >: X](sep: => Rule[S, S, Any, X2]) = rule ~++ (sep -~ rule *)
69 |
70 | /**Repeats this rule zero or more times with a separator (which is discarded) */
71 | def */[X2 >: X](sep: => Rule[S, S, Any, X2]) = +/(sep) | state[S].nil
72 |
73 | def *~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end *) ~- end
74 |
75 | def +~-[Out, X2 >: X](end: => Rule[S, Out, Any, X2]) = (rule - end +) ~- end
76 |
77 | /**Repeats this rule num times */
78 | def times(num: Int): Rule[S, S, Seq[A], X] = from[S] {
79 | val result = new collection.mutable.ArraySeq[A](num)
80 | // more compact using HoF but written this way so it's tail-recursive
81 | def rep(i: Int, in: S): Result[S, Seq[A], X] = {
82 | if (i == num) Success(in, result)
83 | else rule(in) match {
84 | case Success(out, a) => {
85 | result(i) = a
86 | rep(i + 1, out)
87 | }
88 | case Failure => Failure
89 | case err: Error[_] => err
90 | }
91 | }
92 | in => rep(0, in)
93 | }
94 | }
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/ClassFileParser.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 | package scalasig
5 |
6 |
7 | import java.io.IOException
8 |
9 | import scala._
10 | import scala.Predef._
11 |
12 | object ByteCode {
13 | def apply(bytes: Array[Byte]) = new ByteCode(bytes, 0, bytes.length)
14 |
15 | def forClass(clazz: Class[_]) = {
16 | val name = clazz.getName
17 | val subPath = name.substring(name.lastIndexOf('.') + 1) + ".class"
18 | val in = clazz.getResourceAsStream(subPath)
19 |
20 | try {
21 | var rest = in.available()
22 | val bytes = new Array[Byte](rest)
23 | while (rest > 0) {
24 | val res = in.read(bytes, bytes.length - rest, rest)
25 | if (res == -1) throw new IOException("read error")
26 | rest -= res
27 | }
28 | ByteCode(bytes)
29 |
30 | } finally {
31 | in.close()
32 | }
33 | }
34 | }
35 |
36 | /**Represents a chunk of raw bytecode. Used as input for the parsers
37 | */
38 | class ByteCode(val bytes: Array[Byte], val pos: Int, val length: Int) {
39 |
40 | assert(pos >= 0 && length >= 0 && pos + length <= bytes.length)
41 |
42 | def nextByte = if (length == 0) Failure else Success(drop(1), bytes(pos))
43 |
44 | def next(n: Int) = if (length >= n) Success(drop(n), take(n)) else Failure
45 |
46 | def take(n: Int) = new ByteCode(bytes, pos, n)
47 |
48 | def drop(n: Int) = new ByteCode(bytes, pos + n, length - n)
49 |
50 | def fold[X](x: X)(f: (X, Byte) => X): X = {
51 | var result = x
52 | var i = pos
53 | while (i < pos + length) {
54 | result = f(result, bytes(i))
55 | i += 1
56 | }
57 | result
58 | }
59 |
60 | override def toString = length + " bytes"
61 |
62 | def toInt = fold(0) {(x, b) => (x << 8) + (b & 0xFF)}
63 |
64 | def toLong = fold(0L) {(x, b) => (x << 8) + (b & 0xFF)}
65 |
66 | private val utf8: Array[Byte] => Array[Char] = {
67 | // scala 2.8.1
68 | try {
69 | val m1 = io.Codec.getClass.getDeclaredMethod("toUTF8", classOf[Array[Byte]]);
70 | {f => m1.invoke(io.Codec, f).asInstanceOf[Array[Char]]}
71 | } catch {
72 | case e: NoSuchMethodException => {
73 | // scala 2.9.0.RC1
74 | val m2 = io.Codec.getClass.getDeclaredMethod("fromUTF8", classOf[Array[Byte]]);
75 | {f => m2.invoke(io.Codec, f).asInstanceOf[Array[Char]]}
76 | }
77 | }
78 | }
79 |
80 | /**
81 | * Transforms array subsequence of the current buffer into the UTF8 String and
82 | * stores and array of bytes for the decompiler
83 | */
84 | def fromUTF8StringAndBytes: StringBytesPair = {
85 | val chunk: Array[Byte] = bytes drop pos take length
86 | StringBytesPair(utf8(chunk).mkString, chunk)
87 | }
88 |
89 | def byte(i: Int) = bytes(pos) & 0xFF
90 | }
91 |
92 | /**
93 | * The wrapper for decode UTF-8 string
94 | */
95 | case class StringBytesPair(string: String, bytes: Array[Byte])
96 |
97 | /**Provides rules for parsing byte-code.
98 | */
99 | trait ByteCodeReader extends RulesWithState {
100 | type S = ByteCode
101 | type Parser[A] = Rule[A, String]
102 |
103 | val byte = apply(_ nextByte)
104 |
105 | val u1 = byte ^^ (_ & 0xFF)
106 | val u2 = bytes(2) ^^ (_ toInt)
107 | val u4 = bytes(4) ^^ (_ toInt)
108 | // should map to Long??
109 |
110 | def bytes(n: Int) = apply(_ next n)
111 | }
112 |
113 | object ClassFileParser extends ByteCodeReader {
114 | def parse(byteCode: ByteCode) = expect(classFile)(byteCode)
115 |
116 | def parseAnnotations(byteCode: ByteCode) = expect(annotations)(byteCode)
117 |
118 | val magicNumber = (u4 filter (_ == 0xCAFEBABE)) | error("Not a valid class file")
119 | val version = u2 ~ u2 ^^ {case minor ~ major => (major, minor)}
120 | val constantPool = (u2 ^^ ConstantPool) >> repeatUntil(constantPoolEntry)(_ isFull)
121 |
122 | // NOTE currently most constants just evaluate to a string description
123 | // TODO evaluate to useful values
124 | val utf8String = (u2 >> bytes) ^^ add1 {raw =>
125 | pool => raw.fromUTF8StringAndBytes
126 | }
127 | val intConstant = u4 ^^ add1 {x => pool => x}
128 | val floatConstant = bytes(4) ^^ add1 {raw => pool => "Float: TODO"}
129 | val longConstant = bytes(8) ^^ add2 {raw => pool => raw.toLong}
130 | val doubleConstant = bytes(8) ^^ add2 {raw => pool => "Double: TODO"}
131 | val classRef = u2 ^^ add1 {x => pool => "Class: " + pool(x)}
132 | val stringRef = u2 ^^ add1 {x => pool => "String: " + pool(x)}
133 | val fieldRef = memberRef("Field")
134 | val methodRef = memberRef("Method")
135 | val interfaceMethodRef = memberRef("InterfaceMethod")
136 | val nameAndType = u2 ~ u2 ^^ add1 {
137 | case name ~ descriptor =>
138 | pool => "NameAndType: " + pool(name) + ", " + pool(descriptor)
139 | }
140 |
141 | val constantPoolEntry = u1 >> {
142 | case 1 => utf8String
143 | case 3 => intConstant
144 | case 4 => floatConstant
145 | case 5 => longConstant
146 | case 6 => doubleConstant
147 | case 7 => classRef
148 | case 8 => stringRef
149 | case 9 => fieldRef
150 | case 10 => methodRef
151 | case 11 => interfaceMethodRef
152 | case 12 => nameAndType
153 | }
154 |
155 | val interfaces = u2 >> u2.times
156 |
157 | // bytes are parametrizes by the length, declared in u4 section
158 | val attribute = u2 ~ (u4 >> bytes) ^~^ Attribute
159 | // parse attributes u2 times
160 | val attributes = u2 >> attribute.times
161 |
162 | // parse runtime-visible annotations
163 | abstract class ElementValue
164 |
165 | case class AnnotationElement(elementNameIndex: Int,
166 | elementValue: ElementValue)
167 |
168 | case class ConstValueIndex(index: Int) extends ElementValue
169 |
170 | case class EnumConstValue(typeNameIndex: Int,
171 | constNameIndex: Int) extends ElementValue
172 |
173 | case class ClassInfoIndex(index: Int) extends ElementValue
174 |
175 | case class Annotation(typeIndex: Int,
176 | elementValuePairs: Seq[AnnotationElement]) extends ElementValue
177 |
178 | case class ArrayValue(values: Seq[ElementValue]) extends ElementValue
179 |
180 | def element_value: Parser[ElementValue] = u1 >> {
181 | case 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z' | 's' => u2 ^^ ConstValueIndex
182 | case 'e' => u2 ~ u2 ^~^ EnumConstValue
183 | case 'c' => u2 ^^ ClassInfoIndex
184 | case '@' => annotation //nested annotation
185 | case '[' => u2 >> element_value.times ^^ ArrayValue
186 | }
187 |
188 | val element_value_pair = u2 ~ element_value ^~^ AnnotationElement
189 | val annotation: Parser[Annotation] = u2 ~ (u2 >> element_value_pair.times) ^~^ Annotation
190 | val annotations = u2 >> annotation.times
191 |
192 | val field = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Field
193 | val fields = u2 >> field.times
194 |
195 | val method = u2 ~ u2 ~ u2 ~ attributes ^~~~^ Method
196 | val methods = u2 >> method.times
197 |
198 | val header = magicNumber -~ u2 ~ u2 ~ constantPool ~ u2 ~ u2 ~ u2 ~ interfaces ^~~~~~~^ ClassFileHeader
199 | val classFile = header ~ fields ~ methods ~ attributes ~- !u1 ^~~~^ ClassFile
200 |
201 | // TODO create a useful object, not just a string
202 | def memberRef(description: String) = u2 ~ u2 ^^ add1 {
203 | case classRef ~ nameAndTypeRef =>
204 | pool => description + ": " + pool(classRef) + ", " + pool(nameAndTypeRef)
205 | }
206 |
207 | def add1[T](f: T => ConstantPool => Any)(raw: T)
208 | (pool: ConstantPool) = pool add f(raw)
209 |
210 | def add2[T](f: T => ConstantPool => Any)(raw: T)
211 | (pool: ConstantPool) = pool add f(raw) add {pool => ""}
212 | }
213 |
214 | case class ClassFile(
215 | header: ClassFileHeader,
216 | fields: Seq[Field],
217 | methods: Seq[Method],
218 | attributes: Seq[Attribute]) {
219 |
220 | def majorVersion = header.major
221 |
222 | def minorVersion = header.minor
223 |
224 | def className = constant(header.classIndex)
225 |
226 | def superClass = constant(header.superClassIndex)
227 |
228 | def interfaces = header.interfaces.map(constant)
229 |
230 | def constant(index: Int) = header.constants(index) match {
231 | case StringBytesPair(str, _) => str
232 | case z => z
233 | }
234 |
235 | def constantWrapped(index: Int) = header.constants(index)
236 |
237 | def attribute(name: String) = attributes.find {
238 | attrib => constant(attrib.nameIndex) == name
239 | }
240 |
241 | val RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"
242 |
243 | def annotations = (attributes.find(
244 | attr => constant(attr.nameIndex) == RUNTIME_VISIBLE_ANNOTATIONS
245 | )
246 | .map(
247 | attr => ClassFileParser.parseAnnotations(attr.byteCode)
248 | ))
249 |
250 | def annotation(name: String) = annotations.flatMap(
251 | seq => seq.find(
252 | annot => constant(annot.typeIndex) == name
253 | )
254 | )
255 | }
256 |
257 | case class Attribute(nameIndex: Int, byteCode: ByteCode)
258 |
259 | case class Field(flags: Int, nameIndex: Int, descriptorIndex: Int,
260 | attributes: Seq[Attribute])
261 |
262 | case class Method(flags: Int, nameIndex: Int, descriptorIndex: Int,
263 | attributes: Seq[Attribute])
264 |
265 | case class ClassFileHeader(
266 | minor: Int,
267 | major: Int,
268 | constants: ConstantPool,
269 | flags: Int,
270 | classIndex: Int,
271 | superClassIndex: Int,
272 | interfaces: Seq[Int]) {
273 |
274 | def constant(index: Int) = constants(index)
275 | }
276 |
277 | case class ConstantPool(len: Int) {
278 | val size = len - 1
279 |
280 | private val buffer = new scala.collection.mutable.ArrayBuffer[ConstantPool => Any]
281 | private val values = Array.fill[Option[Any]](size)(None)
282 |
283 | def isFull = buffer.length >= size
284 |
285 | def apply(index: Int) = {
286 | // Note constant pool indices are 1-based
287 | val i = index - 1
288 | values(i) getOrElse {
289 | val value = buffer(i)(this)
290 | buffer(i) = null
291 | values(i) = Some(value)
292 | value
293 | }
294 | }
295 |
296 | def add(f: ConstantPool => Any) = {
297 | buffer += f
298 | this
299 | }
300 | }
301 |
302 |
303 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Flags.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util.scalax.rules.scalasig
2 |
3 | trait Flags {
4 | def hasFlag(flag: Long): Boolean
5 |
6 | def isImplicit = hasFlag(0x00000001)
7 |
8 | def isFinal = hasFlag(0x00000002)
9 |
10 | def isPrivate = hasFlag(0x00000004)
11 |
12 | def isProtected = hasFlag(0x00000008)
13 |
14 | def isSealed = hasFlag(0x00000010)
15 |
16 | def isOverride = hasFlag(0x00000020)
17 |
18 | def isCase = hasFlag(0x00000040)
19 |
20 | def isAbstract = hasFlag(0x00000080)
21 |
22 | def isDeferred = hasFlag(0x00000100)
23 |
24 | def isMethod = hasFlag(0x00000200)
25 |
26 | def isModule = hasFlag(0x00000400)
27 |
28 | def isInterface = hasFlag(0x00000800)
29 |
30 | def isMutable = hasFlag(0x00001000)
31 |
32 | def isParam = hasFlag(0x00002000)
33 |
34 | def isPackage = hasFlag(0x00004000)
35 |
36 | def isDeprecated = hasFlag(0x00008000)
37 |
38 | def isCovariant = hasFlag(0x00010000)
39 |
40 | def isCaptured = hasFlag(0x00010000)
41 |
42 | def isByNameParam = hasFlag(0x00010000)
43 |
44 | def isContravariant = hasFlag(0x00020000)
45 |
46 | def isLabel = hasFlag(0x00020000)
47 |
48 | // method symbol is a label. Set by TailCall
49 | def isInConstructor = hasFlag(0x00020000)
50 |
51 | // class symbol is defined in this/superclass constructor
52 |
53 | def isAbstractOverride = hasFlag(0x00040000)
54 |
55 | def isLocal = hasFlag(0x00080000)
56 |
57 | def isJava = hasFlag(0x00100000)
58 |
59 | def isSynthetic = hasFlag(0x00200000)
60 |
61 | def isStable = hasFlag(0x00400000)
62 |
63 | def isStatic = hasFlag(0x00800000)
64 |
65 | def isCaseAccessor = hasFlag(0x01000000)
66 |
67 | def isTrait = hasFlag(0x02000000)
68 |
69 | def isBridge = hasFlag(0x04000000)
70 |
71 | def isAccessor = hasFlag(0x08000000)
72 |
73 | def isSuperAccessor = hasFlag(0x10000000)
74 |
75 | def isParamAccessor = hasFlag(0x20000000)
76 |
77 | def isModuleVar = hasFlag(0x40000000)
78 |
79 | // for variables: is the variable caching a module value
80 | def isMonomorphic = hasFlag(0x40000000)
81 |
82 | // for type symbols: does not have type parameters
83 | def isLazy = hasFlag(0x80000000L)
84 |
85 | // symbol is a lazy val. can't have MUTABLE unless transformed by typer
86 |
87 | def isError = hasFlag(0x100000000L)
88 |
89 | def isOverloaded = hasFlag(0x200000000L)
90 |
91 | def isLifted = hasFlag(0x400000000L)
92 |
93 | def isMixedIn = hasFlag(0x800000000L)
94 |
95 | def isExistential = hasFlag(0x800000000L)
96 |
97 | def isExpandedName = hasFlag(0x1000000000L)
98 |
99 | def isImplementationClass = hasFlag(0x2000000000L)
100 |
101 | def isPreSuper = hasFlag(0x2000000000L)
102 |
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/ScalaSig.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 | package scalasig
5 |
6 | import ClassFileParser.{ConstValueIndex, Annotation}
7 | import scala.reflect.generic.ByteCodecs
8 |
9 | object ScalaSigParser {
10 |
11 | import CaseClassSigParser.{SCALA_SIG, SCALA_SIG_ANNOTATION, BYTES_VALUE}
12 |
13 | def scalaSigFromAnnotation(classFile: ClassFile): Option[ScalaSig] = {
14 | import classFile._
15 |
16 | classFile.annotation(SCALA_SIG_ANNOTATION) map {
17 | case Annotation(_, elements) =>
18 | val bytesElem = elements.find(
19 | elem => constant(elem.elementNameIndex) == BYTES_VALUE
20 | ).get
21 | val bytes = ((bytesElem.elementValue match {case ConstValueIndex(index) => constantWrapped(index)})
22 | .asInstanceOf[StringBytesPair].bytes)
23 | val length = ByteCodecs.decode(bytes)
24 |
25 | ScalaSigAttributeParsers.parse(ByteCode(bytes.take(length)))
26 | }
27 | }
28 |
29 | def scalaSigFromAttribute(classFile: ClassFile): Option[ScalaSig] =
30 | classFile.attribute(SCALA_SIG).map(_.byteCode).map(ScalaSigAttributeParsers.parse)
31 |
32 | def parse(classFile: ClassFile): Option[ScalaSig] = {
33 | val scalaSig = scalaSigFromAttribute(classFile)
34 |
35 | scalaSig match {
36 | // No entries in ScalaSig attribute implies that the signature is stored in the annotation
37 | case Some(ScalaSig(_, _, entries)) if entries.length == 0 =>
38 | scalaSigFromAnnotation(classFile)
39 | case x => x
40 | }
41 | }
42 |
43 | def parse(clazz: Class[_]): Option[ScalaSig] = {
44 | val byteCode = ByteCode.forClass(clazz)
45 | val classFile = ClassFileParser.parse(byteCode)
46 |
47 | parse(classFile)
48 | }
49 | }
50 |
51 | object ScalaSigAttributeParsers extends ByteCodeReader {
52 | def parse(byteCode: ByteCode) = expect(scalaSig)(byteCode)
53 |
54 | val nat = apply {
55 | def natN(in: ByteCode,
56 | x: Int): Result[ByteCode, Int, Nothing] = in.nextByte match {
57 | case Success(out, b) => {
58 | val y = (x << 7) + (b & 0x7f)
59 | if ((b & 0x80) == 0) Success(out, y) else natN(out, y)
60 | }
61 | case _ => Failure
62 | }
63 | in => natN(in, 0)
64 | }
65 |
66 | val rawBytes = nat >> bytes
67 | val entry = nat ~ rawBytes
68 | val symtab = nat >> entry.times
69 | val scalaSig = nat ~ nat ~ symtab ^~~^ ScalaSig
70 |
71 | val utf8 = read(x => x.fromUTF8StringAndBytes.string)
72 | val longValue = read(_ toLong)
73 | }
74 |
75 | case class ScalaSig(majorVersion: Int, minorVersion: Int,
76 | table: Seq[Int ~ ByteCode]) extends DefaultMemoisable {
77 |
78 | case class Entry(index: Int, entryType: Int,
79 | byteCode: ByteCode) extends DefaultMemoisable {
80 | def scalaSig = ScalaSig.this
81 |
82 | def setByteCode(byteCode: ByteCode) = Entry(index, entryType, byteCode)
83 | }
84 |
85 | def hasEntry(index: Int) = table isDefinedAt index
86 |
87 | def getEntry(index: Int) = {
88 | val entryType ~ byteCode = table(index)
89 | Entry(index, entryType, byteCode)
90 | }
91 |
92 | def parseEntry(index: Int) = applyRule(ScalaSigParsers.parseEntry(ScalaSigEntryParsers.entry)(index))
93 |
94 | implicit def applyRule[A](parser: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(parser)(this)
95 |
96 | override def toString = "ScalaSig version " + majorVersion + "." + minorVersion + {
97 | for (i <- 0 until table.size) yield i + ":\t" + parseEntry(i) // + "\n\t" + getEntry(i)
98 | }.mkString("\n", "\n", "")
99 |
100 | lazy val symbols: Seq[Symbol] = ScalaSigParsers.symbols
101 |
102 | lazy val topLevelClasses: List[ClassSymbol] = ScalaSigParsers.topLevelClasses
103 | lazy val topLevelObjects: List[ObjectSymbol] = ScalaSigParsers.topLevelObjects
104 | }
105 |
106 | object ScalaSigParsers extends RulesWithState with MemoisableRules {
107 | type S = ScalaSig
108 | type Parser[A] = Rule[A, String]
109 |
110 | val symTab = read(_.table)
111 | val size = symTab ^^ (_.size)
112 |
113 | def entry(index: Int) = memo(("entry", index)) {
114 | cond(_ hasEntry index) -~ read(_ getEntry index) >-> {
115 | entry => Success(entry, entry.entryType)
116 | }
117 | }
118 |
119 | def parseEntry[A](parser: ScalaSigEntryParsers.EntryParser[A])
120 | (index: Int): Parser[A] =
121 | entry(index) -~ parser >> {a => entry => Success(entry.scalaSig, a)}
122 |
123 | def allEntries[A](f: ScalaSigEntryParsers.EntryParser[A]) = size >> {
124 | n => anyOf((0 until n) map parseEntry(f))
125 | }
126 |
127 | lazy val entries = allEntries(ScalaSigEntryParsers.entry) as "entries"
128 | lazy val symbols = allEntries(ScalaSigEntryParsers.symbol) as "symbols"
129 | lazy val methods = allEntries(ScalaSigEntryParsers.methodSymbol) as "methods"
130 | lazy val attributes = allEntries(ScalaSigEntryParsers.attributeInfo) as "attributes"
131 |
132 | lazy val topLevelClasses = allEntries(ScalaSigEntryParsers.topLevelClass)
133 | lazy val topLevelObjects = allEntries(ScalaSigEntryParsers.topLevelObject)
134 | }
135 |
136 | object ScalaSigEntryParsers extends RulesWithState with MemoisableRules {
137 |
138 | import ScalaSigAttributeParsers.{nat, utf8, longValue}
139 |
140 | type S = ScalaSig#Entry
141 | type EntryParser[A] = Rule[A, String]
142 |
143 | implicit def byteCodeEntryParser[A](rule: ScalaSigAttributeParsers.Parser[A]): EntryParser[A] = apply {
144 | entry =>
145 | rule(entry.byteCode) mapOut (entry setByteCode _)
146 | }
147 |
148 | def toEntry[A](index: Int) = apply {
149 | sigEntry => ScalaSigParsers.entry(index)(sigEntry.scalaSig)
150 | }
151 |
152 | def parseEntry[A](parser: EntryParser[A])
153 | (index: Int) = (toEntry(index) -~ parser)
154 |
155 | implicit def entryType(code: Int) = key filter (_ == code)
156 |
157 | val index = read(_.index)
158 | val key = read(_.entryType)
159 |
160 | lazy val entry: EntryParser[Any] = symbol | typeEntry | literal | name | attributeInfo | annotInfo | children | get
161 |
162 | val ref = byteCodeEntryParser(nat)
163 |
164 | val termName = 1 -~ utf8
165 | val typeName = 2 -~ utf8
166 |
167 | val name = termName | typeName as "name"
168 |
169 | def refTo[A](rule: EntryParser[A]): EntryParser[A] = ref >>& parseEntry(rule)
170 |
171 | lazy val nameRef = refTo(name)
172 | lazy val symbolRef = refTo(symbol)
173 | lazy val typeRef = refTo(typeEntry)
174 | lazy val constantRef = refTo(literal)
175 |
176 | val symbolInfo = nameRef ~ symbolRef ~ nat ~ (symbolRef ?) ~ ref ~ get ^~~~~~^ SymbolInfo
177 |
178 | def symHeader(key: Int) = (key -~ none | (key + 64) -~ nat)
179 |
180 | def symbolEntry(key: Int) = symHeader(key) -~ symbolInfo
181 |
182 | /***************************************************
183 | * Symbol table attribute format:
184 | * Symtab = nentries_Nat {Entry}
185 | * Entry = 1 TERMNAME len_Nat NameInfo
186 | * | 2 TYPENAME len_Nat NameInfo
187 | * | 3 NONEsym len_Nat
188 | * | 4 TYPEsym len_Nat SymbolInfo
189 | * | 5 ALIASsym len_Nat SymbolInfo
190 | * | 6 CLASSsym len_Nat SymbolInfo [thistype_Ref]
191 | * | 7 MODULEsym len_Nat SymbolInfo
192 | * | 8 VALsym len_Nat [defaultGetter_Ref /* no longer needed*/] SymbolInfo [alias_Ref]
193 | * | 9 EXTref len_Nat name_Ref [owner_Ref]
194 | * | 10 EXTMODCLASSref len_Nat name_Ref [owner_Ref]
195 | * | 11 NOtpe len_Nat
196 | * | 12 NOPREFIXtpe len_Nat
197 | * | 13 THIStpe len_Nat sym_Ref
198 | * | 14 SINGLEtpe len_Nat type_Ref sym_Ref
199 | * | 15 CONSTANTtpe len_Nat constant_Ref
200 | * | 16 TYPEREFtpe len_Nat type_Ref sym_Ref {targ_Ref}
201 | * | 17 TYPEBOUNDStpe len_Nat tpe_Ref tpe_Ref
202 | * | 18 REFINEDtpe len_Nat classsym_Ref {tpe_Ref}
203 | * | 19 CLASSINFOtpe len_Nat classsym_Ref {tpe_Ref}
204 | * | 20 METHODtpe len_Nat tpe_Ref {sym_Ref}
205 | * | 21 POLYTtpe len_Nat tpe_Ref {sym_Ref}
206 | * | 22 IMPLICITMETHODtpe len_Nat tpe_Ref {sym_Ref} /* no longer needed */
207 | * | 52 SUPERtpe len_Nat tpe_Ref tpe_Ref
208 | * | 24 LITERALunit len_Nat
209 | * | 25 LITERALboolean len_Nat value_Long
210 | * | 26 LITERALbyte len_Nat value_Long
211 | * | 27 LITERALshort len_Nat value_Long
212 | * | 28 LITERALchar len_Nat value_Long
213 | * | 29 LITERALint len_Nat value_Long
214 | * | 30 LITERALlong len_Nat value_Long
215 | * | 31 LITERALfloat len_Nat value_Long
216 | * | 32 LITERALdouble len_Nat value_Long
217 | * | 33 LITERALstring len_Nat name_Ref
218 | * | 34 LITERALnull len_Nat
219 | * | 35 LITERALclass len_Nat tpe_Ref
220 | * | 36 LITERALenum len_Nat sym_Ref
221 | * | 40 SYMANNOT len_Nat sym_Ref AnnotInfoBody
222 | * | 41 CHILDREN len_Nat sym_Ref {sym_Ref}
223 | * | 42 ANNOTATEDtpe len_Nat [sym_Ref /* no longer needed */] tpe_Ref {annotinfo_Ref}
224 | * | 43 ANNOTINFO len_Nat AnnotInfoBody
225 | * | 44 ANNOTARGARRAY len_Nat {constAnnotArg_Ref}
226 | * | 47 DEBRUIJNINDEXtpe len_Nat level_Nat index_Nat
227 | * | 48 EXISTENTIALtpe len_Nat type_Ref {symbol_Ref}
228 | */
229 | val noSymbol = 3 -^ NoSymbol
230 | val typeSymbol = symbolEntry(4) ^^ TypeSymbol as "typeSymbol"
231 | val aliasSymbol = symbolEntry(5) ^^ AliasSymbol as "alias"
232 | val classSymbol = symbolEntry(6) ~ (ref ?) ^~^ ClassSymbol as "class"
233 | val objectSymbol = symbolEntry(7) ^^ ObjectSymbol as "object"
234 | val methodSymbol = symHeader(8) -~ /*(ref?) -~*/ symbolInfo ~ (ref ?) ^~^ MethodSymbol as "method"
235 | val extRef = 9 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extRef"
236 | val extModClassRef = 10 -~ nameRef ~ (symbolRef ?) ~ get ^~~^ ExternalSymbol as "extModClassRef"
237 |
238 | lazy val symbol: EntryParser[Symbol] = oneOf(
239 | noSymbol,
240 | typeSymbol,
241 | aliasSymbol,
242 | classSymbol,
243 | objectSymbol,
244 | methodSymbol,
245 | extRef,
246 | extModClassRef
247 | ) as "symbol"
248 |
249 | val classSymRef = refTo(classSymbol)
250 | val attribTreeRef = ref
251 | val typeLevel = nat
252 | val typeIndex = nat
253 |
254 | lazy val typeEntry: EntryParser[Type] = oneOf(
255 | 11 -^ NoType,
256 | 12 -^ NoPrefixType,
257 | 13 -~ symbolRef ^^ ThisType,
258 | 14 -~ typeRef ~ symbolRef ^~^ SingleType,
259 | 15 -~ constantRef ^^ ConstantType,
260 | 16 -~ typeRef ~ symbolRef ~ (typeRef *) ^~~^ TypeRefType,
261 | 17 -~ typeRef ~ typeRef ^~^ TypeBoundsType,
262 | 18 -~ classSymRef ~ (typeRef *) ^~^ RefinedType,
263 | 19 -~ symbolRef ~ (typeRef *) ^~^ ClassInfoType,
264 | 20 -~ typeRef ~ (symbolRef *) ^~^ MethodType,
265 | 21 -~ typeRef ~ (refTo(typeSymbol) +) ^~^ PolyType, // TODO: make future safe for past by doing the same transformation as in the full unpickler in case we're reading pre-2.9 classfiles
266 | 21 -~ typeRef ^^ NullaryMethodType,
267 | 22 -~ typeRef ~ (symbolRef *) ^~^ ImplicitMethodType,
268 | 42 -~ typeRef ~ (attribTreeRef *) ^~^ AnnotatedType,
269 | 51 -~ typeRef ~ symbolRef ~ (attribTreeRef *) ^~~^ AnnotatedWithSelfType,
270 | 47 -~ typeLevel ~ typeIndex ^~^ DeBruijnIndexType,
271 | 48 -~ typeRef ~ (symbolRef *) ^~^ ExistentialType
272 | ) as "type"
273 |
274 | lazy val literal = oneOf(
275 | 24 -^ (),
276 | 25 -~ longValue ^^ (_ != 0L),
277 | 26 -~ longValue ^^ (_.toByte),
278 | 27 -~ longValue ^^ (_.toShort),
279 | 28 -~ longValue ^^ (_.toChar),
280 | 29 -~ longValue ^^ (_.toInt),
281 | 30 -~ longValue ^^ (_.toLong),
282 | 31 -~ longValue ^^ (l => java.lang.Float.intBitsToFloat(l.toInt)),
283 | 32 -~ longValue ^^ (java.lang.Double.longBitsToDouble),
284 | 33 -~ nameRef,
285 | 34 -^ null,
286 | 35 -~ typeRef
287 | )
288 |
289 | lazy val attributeInfo = 40 -~ symbolRef ~ typeRef ~ (constantRef ?) ~ (nameRef ~ constantRef *) ^~~~^ AttributeInfo
290 | // sym_Ref info_Ref {constant_Ref} {nameRef constantRef}
291 | lazy val children = 41 -~ (nat *) ^^ Children
292 | //sym_Ref {sym_Ref}
293 | lazy val annotInfo = 43 -~ (nat *) ^^ AnnotInfo
294 | // attarg_Ref {constant_Ref attarg_Ref}
295 |
296 | lazy val topLevelClass = classSymbol filter isTopLevelClass
297 | lazy val topLevelObject = objectSymbol filter isTopLevel
298 |
299 | def isTopLevel(symbol: Symbol) = symbol.parent match {
300 | case Some(ext: ExternalSymbol) => true
301 | case _ => false
302 | }
303 |
304 | def isTopLevelClass(symbol: Symbol) = !symbol.isModule && isTopLevel(symbol)
305 | }
306 |
307 | case class AttributeInfo(symbol: Symbol, typeRef: Type, value: Option[Any],
308 | values: Seq[String ~ Any])
309 |
310 | // sym_Ref info_Ref {constant_Ref} {nameRef constantRef}
311 | case class Children(symbolRefs: Seq[Int])
312 |
313 | //sym_Ref {sym_Ref}
314 |
315 | case class AnnotInfo(refs: Seq[Int])
316 |
317 | // attarg_Ref {constant_Ref attarg_Ref}
318 |
319 | /***************************************************
320 | * | 49 TREE len_Nat 1 EMPTYtree
321 | * | 49 TREE len_Nat 2 PACKAGEtree type_Ref sym_Ref mods_Ref name_Ref {tree_Ref}
322 | * | 49 TREE len_Nat 3 CLASStree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref}
323 | * | 49 TREE len_Nat 4 MODULEtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref
324 | * | 49 TREE len_Nat 5 VALDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref tree_Ref
325 | * | 49 TREE len_Nat 6 DEFDEFtree type_Ref sym_Ref mods_Ref name_Ref numtparams_Nat {tree_Ref} numparamss_Nat {numparams_Nat {tree_Ref}} tree_Ref tree_Ref
326 | * | 49 TREE len_Nat 7 TYPEDEFtree type_Ref sym_Ref mods_Ref name_Ref tree_Ref {tree_Ref}
327 | * | 49 TREE len_Nat 8 LABELtree type_Ref sym_Ref tree_Ref {tree_Ref}
328 | * | 49 TREE len_Nat 9 IMPORTtree type_Ref sym_Ref tree_Ref {name_Ref name_Ref}
329 | * | 49 TREE len_Nat 11 DOCDEFtree type_Ref sym_Ref string_Ref tree_Ref
330 | * | 49 TREE len_Nat 12 TEMPLATEtree type_Ref sym_Ref numparents_Nat {tree_Ref} tree_Ref {tree_Ref}
331 | * | 49 TREE len_Nat 13 BLOCKtree type_Ref tree_Ref {tree_Ref}
332 | * | 49 TREE len_Nat 14 CASEtree type_Ref tree_Ref tree_Ref tree_Ref
333 | * | 49 TREE len_Nat 15 SEQUENCEtree type_Ref {tree_Ref}
334 | * | 49 TREE len_Nat 16 ALTERNATIVEtree type_Ref {tree_Ref}
335 | * | 49 TREE len_Nat 17 STARtree type_Ref {tree_Ref}
336 | * | 49 TREE len_Nat 18 BINDtree type_Ref sym_Ref name_Ref tree_Ref
337 | * | 49 TREE len_Nat 19 UNAPPLYtree type_Ref tree_Ref {tree_Ref}
338 | * | 49 TREE len_Nat 20 ARRAYVALUEtree type_Ref tree_Ref {tree_Ref}
339 | * | 49 TREE len_Nat 21 FUNCTIONtree type_Ref sym_Ref tree_Ref {tree_Ref}
340 | * | 49 TREE len_Nat 22 ASSIGNtree type_Ref tree_Ref tree_Ref
341 | * | 49 TREE len_Nat 23 IFtree type_Ref tree_Ref tree_Ref tree_Ref
342 | * | 49 TREE len_Nat 24 MATCHtree type_Ref tree_Ref {tree_Ref}
343 | * | 49 TREE len_Nat 25 RETURNtree type_Ref sym_Ref tree_Ref
344 | * | 49 TREE len_Nat 26 TREtree type_Ref tree_Ref tree_Ref {tree_Ref}
345 | * | 49 TREE len_Nat 27 THROWtree type_Ref tree_Ref
346 | * | 49 TREE len_Nat 28 NEWtree type_Ref tree_Ref
347 | * | 49 TREE len_Nat 29 TYPEDtree type_Ref tree_Ref tree_Ref
348 | * | 49 TREE len_Nat 30 TYPEAPPLYtree type_Ref tree_Ref {tree_Ref}
349 | * | 49 TREE len_Nat 31 APPLYtree type_Ref tree_Ref {tree_Ref}
350 | * | 49 TREE len_Nat 32 APPLYDYNAMICtree type_Ref sym_Ref tree_Ref {tree_Ref}
351 | * | 49 TREE len_Nat 33 SUPERtree type_Ref sym_Ref tree_Ref name_Ref
352 | * | 49 TREE len_Nat 34 THIStree type_Ref sym_Ref name_Ref
353 | * | 49 TREE len_Nat 35 SELECTtree type_Ref sym_Ref tree_Ref name_Ref
354 | * | 49 TREE len_Nat 36 IDENTtree type_Ref sym_Ref name_Ref
355 | * | 49 TREE len_Nat 37 LITERALtree type_Ref constant_Ref
356 | * | 49 TREE len_Nat 38 TYPEtree type_Ref
357 | * | 49 TREE len_Nat 39 ANNOTATEDtree type_Ref tree_Ref tree_Ref
358 | * | 49 TREE len_Nat 40 SINGLETONTYPEtree type_Ref tree_Ref
359 | * | 49 TREE len_Nat 41 SELECTFROMTYPEtree type_Ref tree_Ref name_Ref
360 | * | 49 TREE len_Nat 42 COMPOUNDTYPEtree type_Ref tree_Ref
361 | * | 49 TREE len_Nat 43 APPLIEDTYPEtree type_Ref tree_Ref {tree_Ref}
362 | * | 49 TREE len_Nat 44 TYPEBOUNDStree type_Ref tree_Ref tree_Ref
363 | * | 49 TREE len_Nat 45 EXISTENTIALTYPEtree type_Ref tree_Ref {tree_Ref}
364 | * | 50 MODIFIERS len_Nat flags_Long privateWithin_Ref
365 | * SymbolInfo = name_Ref owner_Ref flags_LongNat [privateWithin_Ref] info_Ref
366 | * NameInfo =
367 | * NumInfo =
368 | * Ref = Nat
369 | * AnnotInfoBody = info_Ref {annotArg_Ref} {name_Ref constAnnotArg_Ref}
370 | * AnnotArg = Tree | Constant
371 | * ConstAnnotArg = Constant | AnnotInfo | AnnotArgArray
372 | *
373 | * len is remaining length after `len'.
374 | */
375 |
376 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Symbol.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 | package scalasig
5 |
6 | import ScalaSigEntryParsers._
7 |
8 | trait Symbol extends Flags {
9 | def name: String
10 |
11 | def parent: Option[Symbol]
12 |
13 | def children: Seq[Symbol]
14 |
15 | def path: String = parent.map(_.path + ".").getOrElse("") + name
16 | }
17 |
18 | case object NoSymbol extends Symbol {
19 | def name = ""
20 |
21 | def parent = None
22 |
23 | def hasFlag(flag: Long) = false
24 |
25 | def children = Nil
26 | }
27 |
28 | abstract class ScalaSigSymbol extends Symbol {
29 | def applyRule[A](rule: EntryParser[A]): A = expect(rule)(entry)
30 |
31 | def applyScalaSigRule[A](rule: ScalaSigParsers.Parser[A]) = ScalaSigParsers.expect(rule)(entry.scalaSig)
32 |
33 | def entry: ScalaSig#Entry
34 |
35 | def index = entry.index
36 |
37 | lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (_.parent == Some(this))
38 | lazy val attributes: Seq[AttributeInfo] = applyScalaSigRule(ScalaSigParsers.attributes) filter (_.symbol == this)
39 | }
40 |
41 | case class ExternalSymbol(name: String, parent: Option[Symbol],
42 | entry: ScalaSig#Entry) extends ScalaSigSymbol {
43 | override def toString = path
44 |
45 | def hasFlag(flag: Long) = false
46 | }
47 |
48 | case class SymbolInfo(name: String, owner: Symbol, flags: Int,
49 | privateWithin: Option[AnyRef], info: Int,
50 | entry: ScalaSig#Entry) {
51 | def symbolString(any: AnyRef) = any match {
52 | case sym: SymbolInfoSymbol => sym.index.toString
53 | case other => other.toString
54 | }
55 |
56 | override def toString = name + ", owner=" + symbolString(owner) + ", flags=" + flags.toHexString + ", info=" + info + (privateWithin match {
57 | case Some(any) => ", privateWithin=" + symbolString(any)
58 | case None => " "
59 | })
60 | }
61 |
62 | abstract class SymbolInfoSymbol extends ScalaSigSymbol {
63 | def symbolInfo: SymbolInfo
64 |
65 | def entry = symbolInfo.entry
66 |
67 | def name = symbolInfo.name
68 |
69 | def parent = Some(symbolInfo.owner)
70 |
71 | def hasFlag(flag: Long) = (symbolInfo.flags & flag) != 0L
72 |
73 | lazy val infoType = applyRule(parseEntry(typeEntry)(symbolInfo.info))
74 | }
75 |
76 | case class TypeSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol {
77 | override def path = name
78 | }
79 |
80 | case class AliasSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol {
81 | override def path = name
82 | }
83 |
84 | case class ClassSymbol(symbolInfo: SymbolInfo,
85 | thisTypeRef: Option[Int]) extends SymbolInfoSymbol {
86 | lazy val selfType = thisTypeRef.map {(x: Int) => applyRule(parseEntry(typeEntry)(x))}
87 | }
88 |
89 | case class ObjectSymbol(symbolInfo: SymbolInfo) extends SymbolInfoSymbol
90 |
91 | case class MethodSymbol(symbolInfo: SymbolInfo,
92 | aliasRef: Option[Int]) extends SymbolInfoSymbol
93 |
94 |
--------------------------------------------------------------------------------
/src/main/scala/com/codahale/jerkson/util/scalax/rules/scalasig/Type.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.util
2 | package scalax
3 | package rules
4 | package scalasig
5 |
6 | abstract class Type
7 |
8 | case object NoType extends Type
9 |
10 | case object NoPrefixType extends Type
11 |
12 | case class ThisType(symbol: Symbol) extends Type
13 |
14 | case class SingleType(typeRef: Type, symbol: Symbol) extends Type
15 |
16 | case class ConstantType(constant: Any) extends Type
17 |
18 | case class TypeRefType(prefix: Type, symbol: Symbol,
19 | typeArgs: Seq[Type]) extends Type
20 |
21 | case class TypeBoundsType(lower: Type, upper: Type) extends Type
22 |
23 | case class RefinedType(classSym: Symbol, typeRefs: List[Type]) extends Type
24 |
25 | case class ClassInfoType(symbol: Symbol, typeRefs: Seq[Type]) extends Type
26 |
27 | case class ClassInfoTypeWithCons(symbol: Symbol, typeRefs: Seq[Type],
28 | cons: String) extends Type
29 |
30 | case class MethodType(resultType: Type, paramSymbols: Seq[Symbol]) extends Type
31 |
32 | case class NullaryMethodType(resultType: Type) extends Type
33 |
34 | case class PolyType(typeRef: Type, symbols: Seq[TypeSymbol]) extends Type
35 |
36 | case class PolyTypeWithCons(typeRef: Type, symbols: Seq[TypeSymbol],
37 | cons: String) extends Type
38 |
39 | case class ImplicitMethodType(resultType: Type,
40 | paramSymbols: Seq[Symbol]) extends Type
41 |
42 | case class AnnotatedType(typeRef: Type, attribTreeRefs: List[Int]) extends Type
43 |
44 | case class AnnotatedWithSelfType(typeRef: Type, symbol: Symbol,
45 | attribTreeRefs: List[Int]) extends Type
46 |
47 | case class DeBruijnIndexType(typeLevel: Int, typeIndex: Int) extends Type
48 |
49 | case class ExistentialType(typeRef: Type, symbols: Seq[Symbol]) extends Type
50 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/ASTTypeSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import com.codahale.jerkson.AST._
5 | import com.codahale.simplespec.Spec
6 | import org.junit.Test
7 |
8 | class ASTTypeSupportSpec extends Spec {
9 | class `An AST.JInt` {
10 | @Test def `generates a JSON int` = {
11 | generate(JInt(15)).must(be("15"))
12 | }
13 |
14 | @Test def `is parsable from a JSON int` = {
15 | parse[JInt]("15").must(be(JInt(15)))
16 | }
17 |
18 | @Test def `is parsable from a JSON int as a JValue` = {
19 | parse[JValue]("15").must(be(JInt(15)))
20 | }
21 | }
22 |
23 | class `An AST.JFloat` {
24 | @Test def `generates a JSON int` = {
25 | generate(JFloat(15.1)).must(be("15.1"))
26 | }
27 |
28 | @Test def `is parsable from a JSON float` = {
29 | parse[JFloat]("15.1").must(be(JFloat(15.1)))
30 | }
31 |
32 | @Test def `is parsable from a JSON float as a JValue` = {
33 | parse[JValue]("15.1").must(be(JFloat(15.1)))
34 | }
35 | }
36 |
37 |
38 | class `An AST.JString` {
39 | @Test def `generates a JSON string` = {
40 | generate(JString("woo")).must(be("\"woo\""))
41 | }
42 |
43 | @Test def `is parsable from a JSON string` = {
44 | parse[JString]("\"woo\"").must(be(JString("woo")))
45 | }
46 |
47 | @Test def `is parsable from a JSON string as a JValue` = {
48 | parse[JValue]("\"woo\"").must(be(JString("woo")))
49 | }
50 | }
51 |
52 | class `An AST.JNull` {
53 | @Test def `generates a JSON null` = {
54 | generate(JNull).must(be("null"))
55 | }
56 |
57 | @Test def `is parsable from a JSON null` = {
58 | parse[JNull.type]("null").must(be(JNull))
59 | }
60 |
61 | @Test def `is parsable from a JSON null as a JValue` = {
62 | parse[JValue]("null").must(be(JNull))
63 | }
64 | }
65 |
66 | class `An AST.JBoolean` {
67 | @Test def `generates a JSON true` = {
68 | generate(JBoolean(true)).must(be("true"))
69 | }
70 |
71 | @Test def `generates a JSON false` = {
72 | generate(JBoolean(false)).must(be("false"))
73 | }
74 |
75 | @Test def `is parsable from a JSON true` = {
76 | parse[JBoolean]("true").must(be(JBoolean(true)))
77 | }
78 |
79 | @Test def `is parsable from a JSON false` = {
80 | parse[JBoolean]("false").must(be(JBoolean(false)))
81 | }
82 |
83 | @Test def `is parsable from a JSON true as a JValue` = {
84 | parse[JValue]("true").must(be(JBoolean(true)))
85 | }
86 |
87 | @Test def `is parsable from a JSON false as a JValue` = {
88 | parse[JValue]("false").must(be(JBoolean(false)))
89 | }
90 | }
91 |
92 | class `An AST.JArray of JInts` {
93 | @Test def `generates a JSON array of ints` = {
94 | generate(JArray(List(JInt(1), JInt(2), JInt(3)))).must(be("[1,2,3]"))
95 | }
96 |
97 | @Test def `is parsable from a JSON array of ints` = {
98 | parse[JArray]("[1,2,3]").must(be(JArray(List(JInt(1), JInt(2), JInt(3)))))
99 | }
100 |
101 | @Test def `is parsable from a JSON array of ints as a JValue` = {
102 | parse[JValue]("[1,2,3]").must(be(JArray(List(JInt(1), JInt(2), JInt(3)))))
103 | }
104 | }
105 |
106 | class `An AST.JObject` {
107 | val obj = JObject(List(JField("id", JInt(1)), JField("name", JString("Coda"))))
108 |
109 | @Test def `generates a JSON object with matching field values` = {
110 | generate(obj).must(be("""{"id":1,"name":"Coda"}"""))
111 | }
112 |
113 | @Test def `is parsable from a JSON object` = {
114 | parse[JObject]("""{"id":1,"name":"Coda"}""").must(be(obj))
115 | }
116 |
117 | @Test def `is parsable from a JSON object as a JValue` = {
118 | parse[JValue]("""{"id":1,"name":"Coda"}""").must(be(obj))
119 | }
120 |
121 | @Test def `is parsable from an empty JSON object` = {
122 | parse[JObject]("""{}""").must(be(JObject(Nil)))
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/BasicTypeSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.simplespec.Spec
4 | import com.codahale.jerkson.Json._
5 | import com.fasterxml.jackson.databind.node.IntNode
6 | import com.fasterxml.jackson.databind.JsonNode
7 | import org.junit.Test
8 |
9 | class BasicTypeSupportSpec extends Spec {
10 | class `A Byte` {
11 | @Test def `generates a JSON int` = {
12 | generate(15.toByte).must(be("15"))
13 | }
14 |
15 | @Test def `is parsable from a JSON int` = {
16 | parse[Byte]("15").must(be(15))
17 | }
18 | }
19 |
20 | class `A Short` {
21 | @Test def `generates a JSON int` = {
22 | generate(15.toShort).must(be("15"))
23 | }
24 |
25 | @Test def `is parsable from a JSON int` = {
26 | parse[Short]("15").must(be(15))
27 | }
28 | }
29 |
30 | class `An Int` {
31 | @Test def `generates a JSON int` = {
32 | generate(15).must(be("15"))
33 | }
34 |
35 | @Test def `is parsable from a JSON int` = {
36 | parse[Int]("15").must(be(15))
37 | }
38 | }
39 |
40 | class `A Long` {
41 | @Test def `generates a JSON int` = {
42 | generate(15L).must(be("15"))
43 | }
44 |
45 | @Test def `is parsable from a JSON int` = {
46 | parse[Long]("15").must(be(15L))
47 | }
48 | }
49 |
50 | class `A BigInt` {
51 | @Test def `generates a JSON int` = {
52 | generate(BigInt(15)).must(be("15"))
53 | }
54 |
55 | @Test def `is parsable from a JSON int` = {
56 | parse[BigInt]("15").must(be(BigInt(15)))
57 | }
58 |
59 | @Test def `is parsable from a JSON string` = {
60 | parse[BigInt]("\"15\"").must(be(BigInt(15)))
61 | }
62 | }
63 |
64 | class `A Float` {
65 | @Test def `generates a JSON float` = {
66 | generate(15.1F).must(be("15.1"))
67 | }
68 |
69 | @Test def `is parsable from a JSON float` = {
70 | parse[Float]("15.1").must(be(15.1F))
71 | }
72 | }
73 |
74 | class `A Double` {
75 | @Test def `generates a JSON float` = {
76 | generate(15.1).must(be("15.1"))
77 | }
78 |
79 | @Test def `is parsable from a JSON float` = {
80 | parse[Double]("15.1").must(be(15.1D))
81 | }
82 | }
83 |
84 | class `A BigDecimal` {
85 | @Test def `generates a JSON float` = {
86 | generate(BigDecimal(15.5)).must(be("15.5"))
87 | }
88 |
89 | @Test def `is parsable from a JSON float` = {
90 | parse[BigDecimal]("15.5").must(be(BigDecimal(15.5)))
91 | }
92 |
93 | @Test def `is parsable from a JSON int` = {
94 | parse[BigDecimal]("15").must(be(BigDecimal(15.0)))
95 | }
96 | }
97 |
98 | class `A String` {
99 | @Test def `generates a JSON string` = {
100 | generate("woo").must(be("\"woo\""))
101 | }
102 |
103 | @Test def `is parsable from a JSON string` = {
104 | parse[String]("\"woo\"").must(be("woo"))
105 | }
106 | }
107 |
108 | class `A StringBuilder` {
109 | @Test def `generates a JSON string` = {
110 | generate(new StringBuilder("foo")).must(be("\"foo\""))
111 | }
112 |
113 | @Test def `is parsable from a JSON string` = {
114 | parse[StringBuilder]("\"foo\"").toString().must(be("foo"))
115 | }
116 | }
117 |
118 | class `A null Object` {
119 | @Test def `generates a JSON null` = {
120 | generate[Object](null).must(be("null"))
121 | }
122 |
123 | @Test def `is parsable from a JSON null` = {
124 | parse[Object]("null").must(be(not(notNull)))
125 | }
126 | }
127 |
128 | class `A Boolean` {
129 | @Test def `generates a JSON true` = {
130 | generate(true).must(be("true"))
131 | }
132 |
133 | @Test def `generates a JSON false` = {
134 | generate(false).must(be("false"))
135 | }
136 |
137 | @Test def `is parsable from a JSON true` = {
138 | parse[Boolean]("true").must(be(true))
139 | }
140 |
141 | @Test def `is parsable from a JSON false` = {
142 | parse[Boolean]("false").must(be(false))
143 | }
144 | }
145 |
146 | class `A Some[Int]` {
147 | @Test def `generates a JSON int` = {
148 | generate(Some(12)).must(be("12"))
149 | }
150 |
151 | @Test def `is parsable from a JSON int as an Option[Int]` = {
152 | parse[Option[Int]]("12").must(be(Some(12)))
153 | }
154 | }
155 |
156 | class `A None` {
157 | @Test def `generates a JSON null` = {
158 | generate(None).must(be("null"))
159 | }
160 |
161 | @Test def `is parsable from a JSON null as an Option[Int]` = {
162 | parse[Option[Int]]("null").must(be(None))
163 | }
164 | }
165 |
166 | class `A Left[String]` {
167 | @Test def `generates a JSON string` = {
168 | generate(Left("woo")).must(be("\"woo\""))
169 | }
170 |
171 | @Test def `is parsable from a JSON string as an Either[String, Int]` = {
172 | parse[Either[String, Int]]("\"woo\"").must(be(Left("woo")))
173 | }
174 | }
175 |
176 | class `A Right[String]` {
177 | @Test def `generates a JSON string` = {
178 | generate(Right("woo")).must(be("\"woo\""))
179 | }
180 |
181 | @Test def `is parsable from a JSON string as an Either[Int, String]` = {
182 | parse[Either[Int, String]]("\"woo\"").must(be(Right("woo")))
183 | }
184 | }
185 |
186 | class `A JsonNode` {
187 | @Test def `generates whatever the JsonNode is` = {
188 | generate(new IntNode(2)).must(be("2"))
189 | }
190 |
191 | @Test def `is parsable from a JSON AST node` = {
192 | parse[JsonNode]("2").must(be(new IntNode(2)))
193 | }
194 |
195 | @Test def `is parsable from a JSON AST node as a specific type` = {
196 | parse[IntNode]("2").must(be(new IntNode(2)))
197 | }
198 |
199 | @Test def `is itself parsable` = {
200 | parse[Int](new IntNode(2)).must(be(2))
201 | }
202 | }
203 |
204 | class `An Array[Int]` {
205 | @Test def `generates a JSON array of ints` = {
206 | generate(Array(1, 2, 3)).must(be("[1,2,3]"))
207 | }
208 |
209 | @Test def `is parsable from a JSON array of ints` = {
210 | parse[Array[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
211 | }
212 |
213 | @Test def `is parsable from an empty JSON array` = {
214 | parse[Array[Int]]("[]").toList.must(be(List.empty))
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/CaseClassSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import com.codahale.simplespec.Spec
5 | import com.codahale.jerkson.ParsingException
6 | import com.fasterxml.jackson.databind.node.IntNode
7 | import org.junit.Test
8 |
9 | class CaseClassSupportSpec extends Spec {
10 | class `A basic case class` {
11 | @Test def `generates a JSON object with matching field values` = {
12 | generate(CaseClass(1, "Coda")).must(be("""{"id":1,"name":"Coda"}"""))
13 | }
14 |
15 | @Test def `is parsable from a JSON object with corresponding fields` = {
16 | parse[CaseClass]("""{"id":1,"name":"Coda"}""").must(be(CaseClass(1, "Coda")))
17 | }
18 |
19 | @Test def `is parsable from a JSON object with extra fields` = {
20 | parse[CaseClass]("""{"id":1,"name":"Coda","derp":100}""").must(be(CaseClass(1, "Coda")))
21 | }
22 |
23 | @Test def `is not parsable from an incomplete JSON object` = {
24 | evaluating {
25 | parse[CaseClass]("""{"id":1}""")
26 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [id, name], but found [id]."""))
27 | }
28 | }
29 |
30 | class `A case class with lazy fields` {
31 | @Test def `generates a JSON object with those fields evaluated` = {
32 | generate(CaseClassWithLazyVal(1)).must(be("""{"id":1,"woo":"yeah"}"""))
33 | }
34 |
35 | @Test def `is parsable from a JSON object without those fields` = {
36 | parse[CaseClassWithLazyVal]("""{"id":1}""").must(be(CaseClassWithLazyVal(1)))
37 | }
38 |
39 | @Test def `is not parsable from an incomplete JSON object` = {
40 | evaluating {
41 | parse[CaseClassWithLazyVal]("""{}""")
42 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found []."""))
43 | }
44 | }
45 |
46 | class `A case class with ignored members` {
47 | @Test def `generates a JSON object without those fields` = {
48 | generate(CaseClassWithIgnoredField(1)).must(be("""{"id":1}"""))
49 | generate(CaseClassWithIgnoredFields(1)).must(be("""{"id":1}"""))
50 | }
51 |
52 | @Test def `is parsable from a JSON object without those fields` = {
53 | parse[CaseClassWithIgnoredField]("""{"id":1}""").must(be(CaseClassWithIgnoredField(1)))
54 | parse[CaseClassWithIgnoredFields]("""{"id":1}""").must(be(CaseClassWithIgnoredFields(1)))
55 | }
56 |
57 | @Test def `is not parsable from an incomplete JSON object` = {
58 | evaluating {
59 | parse[CaseClassWithIgnoredField]("""{}""")
60 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found []."""))
61 |
62 | evaluating {
63 | parse[CaseClassWithIgnoredFields]("""{}""")
64 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found []."""))
65 | }
66 | }
67 |
68 | class `A case class with transient members` {
69 | @Test def `generates a JSON object without those fields` = {
70 | generate(CaseClassWithTransientField(1)).must(be("""{"id":1}"""))
71 | }
72 |
73 | @Test def `is parsable from a JSON object without those fields` = {
74 | parse[CaseClassWithTransientField]("""{"id":1}""").must(be(CaseClassWithTransientField(1)))
75 | }
76 |
77 | @Test def `is not parsable from an incomplete JSON object` = {
78 | evaluating {
79 | parse[CaseClassWithTransientField]("""{}""")
80 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [id], but found []."""))
81 | }
82 | }
83 |
84 | class `A case class with an overloaded field` {
85 | @Test def `generates a JSON object with the nullary version of that field` = {
86 | generate(CaseClassWithOverloadedField(1)).must(be("""{"id":1}"""))
87 | }
88 | }
89 |
90 | class `A case class with an Option[String] member` {
91 | @Test def `generates a field if the member is Some` = {
92 | generate(CaseClassWithOption(Some("what"))).must(be("""{"value":"what"}"""))
93 | }
94 |
95 | @Test def `is parsable from a JSON object with that field` = {
96 | parse[CaseClassWithOption]("""{"value":"what"}""").must(be(CaseClassWithOption(Some("what"))))
97 | }
98 |
99 | @Test def `doesn't generate a field if the member is None` = {
100 | generate(CaseClassWithOption(None)).must(be("""{}"""))
101 | }
102 |
103 | @Test def `is parsable from a JSON object without that field` = {
104 | parse[CaseClassWithOption]("""{}""").must(be(CaseClassWithOption(None)))
105 | }
106 |
107 | @Test def `is parsable from a JSON object with a null value for that field` = {
108 | parse[CaseClassWithOption]("""{"value":null}""").must(be(CaseClassWithOption(None)))
109 | }
110 | }
111 |
112 | class `A case class with a JsonNode member` {
113 | @Test def `generates a field of the given type` = {
114 | generate(CaseClassWithJsonNode(new IntNode(2))).must(be("""{"value":2}"""))
115 | }
116 | }
117 |
118 | class `A case class with members of all ScalaSig types` {
119 | val json = """
120 | {
121 | "map": {
122 | "one": "two"
123 | },
124 | "set": [1, 2, 3],
125 | "string": "woo",
126 | "list": [4, 5, 6],
127 | "seq": [7, 8, 9],
128 | "sequence": [10, 11, 12],
129 | "collection": [13, 14, 15],
130 | "indexedSeq": [16, 17, 18],
131 | "randomAccessSeq": [19, 20, 21],
132 | "vector": [22, 23, 24],
133 | "bigDecimal": 12.0,
134 | "bigInt": 13,
135 | "int": 1,
136 | "long": 2,
137 | "char": "x",
138 | "bool": false,
139 | "short": 14,
140 | "byte": 15,
141 | "float": 34.5,
142 | "double": 44.9,
143 | "any": true,
144 | "anyRef": "wah",
145 | "intMap": {
146 | "1": "1"
147 | },
148 | "longMap": {
149 | "2": 2
150 | }
151 | }
152 | """
153 |
154 |
155 | @Test def `is parsable from a JSON object with those fields` = {
156 | parse[CaseClassWithAllTypes](json).must(be(
157 | CaseClassWithAllTypes(
158 | map = Map("one" -> "two"),
159 | set = Set(1, 2, 3),
160 | string = "woo",
161 | list = List(4, 5, 6),
162 | seq = Seq(7, 8, 9),
163 | indexedSeq = IndexedSeq(16, 17, 18),
164 | vector = Vector(22, 23, 24),
165 | bigDecimal = BigDecimal("12.0"),
166 | bigInt = BigInt("13"),
167 | int = 1,
168 | long = 2L,
169 | char = 'x',
170 | bool = false,
171 | short = 14,
172 | byte = 15,
173 | float = 34.5f,
174 | double = 44.9d,
175 | any = true,
176 | anyRef = "wah",
177 | intMap = Map(1 -> 1),
178 | longMap = Map(2L -> 2L)
179 | )
180 | ))
181 | }
182 | }
183 |
184 | class `A case class nested inside of an object` {
185 | @Test def `is parsable from a JSON object` = {
186 | parse[OuterObject.NestedCaseClass]("""{"id": 1}""").must(be(OuterObject.NestedCaseClass(1)))
187 | }
188 | }
189 |
190 | class `A case class nested inside of an object nested inside of an object` {
191 | @Test def `is parsable from a JSON object` = {
192 | parse[OuterObject.InnerObject.SuperNestedCaseClass]("""{"id": 1}""").must(be(OuterObject.InnerObject.SuperNestedCaseClass(1)))
193 | }
194 | }
195 |
196 | class `A case class with two constructors` {
197 | @Test def `is parsable from a JSON object with the same parameters as the case accessor` = {
198 | parse[CaseClassWithTwoConstructors]("""{"id":1,"name":"Bert"}""").must(be(CaseClassWithTwoConstructors(1, "Bert")))
199 | }
200 |
201 | @Test def `is parsable from a JSON object which works with the second constructor` = {
202 | evaluating {
203 | parse[CaseClassWithTwoConstructors]("""{"id":1}""")
204 | }.must(throwA[ParsingException])
205 | }
206 | }
207 |
208 | class `A case class with snake-cased fields` {
209 | @Test def `is parsable from a snake-cased JSON object` = {
210 | parse[CaseClassWithSnakeCase]("""{"one_thing":"yes","two_thing":"good"}""").must(be(CaseClassWithSnakeCase("yes", "good")))
211 | }
212 |
213 | @Test def `generates a snake-cased JSON object` = {
214 | generate(CaseClassWithSnakeCase("yes", "good")).must(be("""{"one_thing":"yes","two_thing":"good"}"""))
215 | }
216 |
217 | @Test def `throws errors with the snake-cased field names present` = {
218 | evaluating {
219 | parse[CaseClassWithSnakeCase]("""{"one_thing":"yes"}""")
220 | }.must(throwA[ParsingException]("Invalid JSON. Needed [one_thing, two_thing], but found [one_thing]."))
221 | }
222 | }
223 |
224 | class `A case class with array members` {
225 | @Test def `is parsable from a JSON object` = {
226 | val c = parse[CaseClassWithArrays]("""{"one":"1","two":["a","b","c"],"three":[1,2,3]}""")
227 |
228 | c.one.must(be("1"))
229 | c.two.must(be(Array("a", "b", "c")))
230 | c.three.must(be(Array(1, 2, 3)))
231 | }
232 |
233 | @Test def `generates a JSON object` = {
234 | generate(CaseClassWithArrays("1", Array("a", "b", "c"), Array(1, 2, 3))).must(be(
235 | """{"one":"1","two":["a","b","c"],"three":[1,2,3]}"""
236 | ))
237 | }
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/CollectionSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import scala.collection._
4 | import com.codahale.jerkson.Json._
5 | import com.codahale.simplespec.Spec
6 | import org.junit.{Ignore, Test}
7 |
8 | class CollectionSupportSpec extends Spec {
9 | class `A collection.BitSet` {
10 | @Test def `generates a JSON array of ints` = {
11 | generate(BitSet(1)).must(be("[1]"))
12 | }
13 |
14 | @Test def `is parsable from a JSON array of ints` = {
15 | parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3)))
16 | }
17 | }
18 |
19 | class `A collection.Iterator[Int]` {
20 | @Test def `generates a JSON array of ints` = {
21 | generate(Seq(1, 2, 3).iterator).must(be("[1,2,3]"))
22 | }
23 |
24 | @Test def `is parsable from a JSON array of ints` = {
25 | parse[Iterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
26 | }
27 |
28 | @Test def `is parsable from an empty JSON array` = {
29 | parse[Iterator[Int]]("[]").toList.must(be(List.empty[Int]))
30 | }
31 | }
32 |
33 | class `A collection.Traversable[Int]` {
34 | @Test def `generates a JSON array of ints` = {
35 | generate(Seq(1, 2, 3).toTraversable).must(be("[1,2,3]"))
36 | }
37 |
38 | @Test def `is parsable from a JSON array of ints` = {
39 | parse[Traversable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
40 | }
41 |
42 | @Test def `is parsable from an empty JSON array` = {
43 | parse[Traversable[Int]]("[]").toList.must(be(List.empty[Int]))
44 | }
45 | }
46 |
47 | class `A collection.BufferedIterator[Int]` {
48 | @Test def `generates a JSON array of ints` = {
49 | generate(Seq(1, 2, 3).iterator.buffered).must(be("[1,2,3]"))
50 | }
51 |
52 | @Test def `is parsable from a JSON array of ints` = {
53 | parse[BufferedIterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
54 | }
55 |
56 | @Test def `is parsable from an empty JSON array` = {
57 | parse[BufferedIterator[Int]]("[]").toList.must(be(List.empty[Int]))
58 | }
59 | }
60 |
61 | class `A collection.Iterable[Int]` {
62 | @Test def `generates a JSON array of ints` = {
63 | generate(Seq(1, 2, 3).toIterable).must(be("[1,2,3]"))
64 | }
65 |
66 | @Test def `is parsable from a JSON array of ints` = {
67 | parse[Iterable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
68 | }
69 |
70 | @Test def `is parsable from an empty JSON array` = {
71 | parse[Iterable[Int]]("[]").toList.must(be(List.empty[Int]))
72 | }
73 | }
74 |
75 | class `A collection.Set[Int]` {
76 | @Test def `generates a JSON array of ints` = {
77 | generate(Set(1, 2, 3)).must(be("[1,2,3]"))
78 | }
79 |
80 | @Test def `is parsable from a JSON array of ints` = {
81 | parse[Set[Int]]("[1,2,3]").must(be(Set(1, 2, 3)))
82 | }
83 |
84 | @Test def `is parsable from an empty JSON array` = {
85 | parse[Set[Int]]("[]").must(be(Set.empty[Int]))
86 | }
87 | }
88 |
89 | class `A collection.Map[String, Int]` {
90 | @Test def `generates a JSON object with int field values` = {
91 | generate(Map("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}"""))
92 | }
93 |
94 | @Test def `is parsable from a JSON object with int field values` = {
95 | parse[Map[String, Int]]("""{"one":1,"two":2}""").must(be(Map("one" -> 1, "two" -> 2)))
96 | }
97 |
98 | @Test def `is parsable from an empty JSON object` = {
99 | parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int]))
100 | }
101 | }
102 |
103 | class `A collection.IndexedSeq[Int]` {
104 | @Test def `generates a JSON array of ints` = {
105 | generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]"))
106 | }
107 |
108 | @Test def `is parsable from a JSON array of ints` = {
109 | parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3)))
110 | }
111 |
112 | @Test def `is parsable from an empty JSON array` = {
113 | parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty))
114 | }
115 | }
116 |
117 | class `A collection.Seq[Int]` {
118 | @Test def `generates a JSON array of ints` = {
119 | generate(Seq(1, 2, 3)).must(be("[1,2,3]"))
120 | }
121 |
122 | @Test def `is parsable from a JSON array of ints` = {
123 | parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3)))
124 | }
125 |
126 | @Test def `is parsable from an empty JSON array` = {
127 | parse[Seq[Int]]("[]").must(be(Seq.empty[Int]))
128 | }
129 | }
130 |
131 | class `A collection.SortedMap[String, Int]` {
132 | @Test def `generates a JSON object with int field values` = {
133 | generate(SortedMap("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}"""))
134 | }
135 |
136 | // TODO: 6/1/11 -- figure out how to deserialize SortedMap instances
137 |
138 | /**
139 | * I think all this would take is a mapping from Class[_] to Ordering, which
140 | * would need to have hard-coded the various primitive types, and then add
141 | * support for Ordered and Comparable classes. Once we have the Ordering,
142 | * we can pass it in manually to a builder.
143 | */
144 |
145 | @Ignore @Test def `is parsable from a JSON object with int field values` = {
146 | parse[SortedMap[String, Int]]("""{"one":1,"two":2}""").must(be(SortedMap("one" -> 1, "two" -> 2)))
147 | }
148 |
149 | @Ignore @Test def `is parsable from an empty JSON object` = {
150 | parse[SortedMap[String, Int]]("{}").must(be(SortedMap.empty[String, Int]))
151 | }
152 | }
153 |
154 | class `A collection.SortedSet[Int]` {
155 | @Test def `generates a JSON array of ints` = {
156 | generate(SortedSet(1, 2, 3)).must(be("[1,2,3]"))
157 | }
158 |
159 | // TODO: 6/1/11 -- figure out how to deserialize SortedMap instances
160 |
161 | /**
162 | * I think all this would take is a mapping from Class[_] to Ordering, which
163 | * would need to have hard-coded the various primitive types, and then add
164 | * support for Ordered and Comparable classes. Once we have the Ordering,
165 | * we can pass it in manually to a builder.
166 | */
167 |
168 | @Ignore @Test def `is parsable from a JSON array of ints` = {
169 | parse[SortedSet[Int]]("[1,2,3]").must(be(SortedSet(1, 2, 3)))
170 |
171 | }
172 |
173 | @Ignore @Test def `is parsable from an empty JSON array` = {
174 | parse[SortedSet[Int]]("[]").must(be(SortedSet.empty[Int]))
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/DefaultCollectionSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import com.codahale.simplespec.Spec
5 | import com.codahale.jerkson.ParsingException
6 | import org.junit.{Ignore, Test}
7 |
8 | class DefaultCollectionSupportSpec extends Spec {
9 | class `A Range` {
10 | @Test def `generates a JSON object` = {
11 | generate(Range.inclusive(1, 4, 3)).must(be("""{"start":1,"end":4,"step":3,"inclusive":true}"""))
12 | }
13 |
14 | @Test def `generates a JSON object without the inclusive field if it's exclusive` = {
15 | generate(Range(1, 4, 3)).must(be("""{"start":1,"end":4,"step":3}"""))
16 | }
17 |
18 | @Test def `generates a JSON object without the step field if it's 1` = {
19 | generate(Range(1, 4)).must(be("""{"start":1,"end":4}"""))
20 | }
21 |
22 | @Test def `is parsable from a JSON object` = {
23 | parse[Range]("""{"start":1,"end":4,"step":3,"inclusive":true}""").must(be(Range.inclusive(1, 4, 3)))
24 | }
25 |
26 | @Test def `is parsable from a JSON object without the inclusive field` = {
27 | parse[Range]("""{"start":1,"end":4,"step":3}""").must(be(Range(1, 4, 3)))
28 | }
29 |
30 | @Test def `is parsable from a JSON object without the step field` = {
31 | parse[Range]("""{"start":1,"end":4}""").must(be(Range(1, 4)))
32 | }
33 |
34 | @Test def `is not parsable from a JSON object without the required fields` = {
35 | evaluating {
36 | parse[Range]("""{"start":1}""")
37 | }.must(throwA[ParsingException]("""Invalid JSON. Needed [start, end, , ], but found [start]."""))
38 | }
39 |
40 | }
41 |
42 | class `A Pair[Int]` {
43 | @Ignore @Test def `generates a two-element JSON array of ints` = {
44 | // TODO: 5/31/11 -- fix Pair serialization
45 | generate(Pair(1, 2)).must(be("[1,2]"))
46 | }
47 |
48 | @Ignore @Test def `is parsable from a two-element JSON array of ints` = {
49 | // TODO: 5/31/11 -- fix Pair deserialization
50 | parse[Pair[Int, Int]]("[1,2]").must(be(Pair(1, 2)))
51 | }
52 | }
53 |
54 | class `A Triple[Int]` {
55 | @Ignore @Test def `generates a three-element JSON array of ints` = {
56 | // TODO: 5/31/11 -- fix Triple serialization
57 | generate(Triple(1, 2, 3)).must(be("[1,2,3]"))
58 | }
59 |
60 | @Ignore @Test def `is parsable from a three-element JSON array of ints` = {
61 | // TODO: 5/31/11 -- fix Triple deserialization
62 | parse[Triple[Int, Int, Int]]("[1,2,3]").must(be(Triple(1, 2, 3)))
63 | }
64 | }
65 |
66 | class `A four-tuple` {
67 | @Ignore @Test def `generates a four-element JSON array` = {
68 | // TODO: 5/31/11 -- fix Tuple4 serialization
69 | generate((1, "2", 3, "4")).must(be("[1,\"2\",3,\"4\"]"))
70 | }
71 |
72 | @Ignore @Test def `is parsable from a three-element JSON array of ints` = {
73 | // TODO: 5/31/11 -- fix Tuple4 deserialization
74 | parse[(Int, String, Int, String)]("[1,\"2\",3,\"4\"]").must(be((1, "2", 3, "4")))
75 | }
76 | }
77 |
78 | // TODO: 6/1/11 -- add support for all Tuple1->TupleBillionty types
79 |
80 | class `A Seq[Int]` {
81 | @Test def `generates a JSON array of ints` = {
82 | generate(Seq(1, 2, 3)).must(be("[1,2,3]"))
83 | }
84 |
85 | @Test def `is parsable from a JSON array of ints` = {
86 | parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3)))
87 | }
88 |
89 | @Test def `is parsable from an empty JSON array` = {
90 | parse[Seq[Int]]("[]").must(be(Seq.empty[Int]))
91 | }
92 | }
93 |
94 | class `A List[Int]` {
95 | @Test def `generates a JSON array of ints` = {
96 | generate(List(1, 2, 3)).must(be("[1,2,3]"))
97 | }
98 |
99 | @Test def `is parsable from a JSON array of ints` = {
100 | parse[List[Int]]("[1,2,3]").must(be(List(1, 2, 3)))
101 | }
102 |
103 | @Test def `is parsable from an empty JSON array` = {
104 | parse[List[Int]]("[]").must(be(List.empty[Int]))
105 | }
106 | }
107 |
108 | class `An IndexedSeq[Int]` {
109 | @Test def `generates a JSON array of ints` = {
110 | generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]"))
111 | }
112 |
113 | @Test def `is parsable from a JSON array of ints` = {
114 | parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3)))
115 | }
116 |
117 | @Test def `is parsable from an empty JSON array` = {
118 | parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty[Int]))
119 | }
120 | }
121 |
122 | class `A Vector[Int]` {
123 | @Test def `generates a JSON array of ints` = {
124 | generate(Vector(1, 2, 3)).must(be("[1,2,3]"))
125 | }
126 |
127 | @Test def `is parsable from a JSON array of ints` = {
128 | parse[Vector[Int]]("[1,2,3]").must(be(Vector(1, 2, 3)))
129 | }
130 |
131 | @Test def `is parsable from an empty JSON array` = {
132 | parse[Vector[Int]]("[]").must(be(Vector.empty[Int]))
133 | }
134 | }
135 |
136 | class `A Set[Int]` {
137 | @Test def `generates a JSON array of ints` = {
138 | generate(Set(1, 2, 3)).must(be("[1,2,3]"))
139 | }
140 |
141 | @Test def `is parsable from a JSON array of ints` = {
142 | parse[Set[Int]]("[1,2,3]").must(be(Set(1, 2, 3)))
143 | }
144 |
145 | @Test def `is parsable from an empty JSON array` = {
146 | parse[Set[Int]]("[]").must(be(Set.empty[Int]))
147 | }
148 | }
149 |
150 | class `A Map[String, Int]` {
151 | @Test def `generates a JSON object with int field values` = {
152 | generate(Map("one" -> 1, "two" -> 2)).must(be("""{"one":1,"two":2}"""))
153 | }
154 |
155 | @Test def `is parsable from a JSON object with int field values` = {
156 | parse[Map[String, Int]]("""{"one":1,"two":2}""").must(be(Map("one" -> 1, "two" -> 2)))
157 | }
158 |
159 | @Test def `is parsable from an empty JSON object` = {
160 | parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int]))
161 | }
162 | }
163 |
164 | class `A Map[String, Any]` {
165 | @Test def `generates a JSON object with mixed field values` = {
166 | generate(Map("one" -> 1, "two" -> "2")).must(be("""{"one":1,"two":"2"}"""))
167 | }
168 |
169 | @Test def `is parsable from a JSON object with mixed field values` = {
170 | parse[Map[String, Any]]("""{"one":1,"two":"2"}""").must(be(Map[String, Any]("one" -> 1, "two" -> "2")))
171 | }
172 |
173 | @Test def `is parsable from an empty JSON object` = {
174 | parse[Map[String, Any]]("{}").must(be(Map.empty[String, Any]))
175 | }
176 | }
177 |
178 | class `A Stream[Int]` {
179 | @Test def `generates a JSON array` = {
180 | generate(Stream(1, 2, 3)).must(be("[1,2,3]"))
181 | }
182 |
183 | @Test def `is parsable from a JSON array of ints` = {
184 | parse[Stream[Int]]("[1,2,3]").must(be(Stream(1, 2, 3)))
185 | }
186 |
187 | @Test def `is parsable from an empty JSON array` = {
188 | parse[Stream[Int]]("[]").must(be(Stream.empty[Int]))
189 | }
190 | }
191 |
192 | class `An Iterator[Int]` {
193 | @Test def `generates a JSON array of ints` = {
194 | generate(Seq(1, 2, 3).iterator).must(be("[1,2,3]"))
195 | }
196 |
197 | @Test def `is parsable from a JSON array of ints` = {
198 | parse[Iterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
199 | }
200 |
201 | @Test def `is parsable from an empty JSON array` = {
202 | parse[Iterator[Int]]("[]").toList.must(be(List.empty[Int]))
203 | }
204 | }
205 |
206 | class `A Traversable[Int]` {
207 | @Test def `generates a JSON array of ints` = {
208 | generate(Seq(1, 2, 3).toTraversable).must(be("[1,2,3]"))
209 | }
210 |
211 | @Test def `is parsable from a JSON array of ints` = {
212 | parse[Traversable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
213 | }
214 |
215 | @Test def `is parsable from an empty JSON array` = {
216 | parse[Traversable[Int]]("[]").toList.must(be(List.empty[Int]))
217 | }
218 | }
219 |
220 | class `A BufferedIterator[Int]` {
221 | @Test def `generates a JSON array of ints` = {
222 | generate(Seq(1, 2, 3).iterator.buffered).must(be("[1,2,3]"))
223 | }
224 |
225 | @Test def `is parsable from a JSON array of ints` = {
226 | parse[BufferedIterator[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
227 | }
228 |
229 | @Test def `is parsable from an empty JSON array` = {
230 | parse[BufferedIterator[Int]]("[]").toList.must(be(List.empty[Int]))
231 | }
232 | }
233 |
234 | class `An Iterable[Int]` {
235 | @Test def `generates a JSON array of ints` = {
236 | generate(Seq(1, 2, 3).toIterable).must(be("[1,2,3]"))
237 | }
238 |
239 | @Test def `is parsable from a JSON array of ints` = {
240 | parse[Iterable[Int]]("[1,2,3]").toList.must(be(List(1, 2, 3)))
241 | }
242 |
243 | @Test def `is parsable from an empty JSON array` = {
244 | parse[Iterable[Int]]("[]").toList.must(be(List.empty[Int]))
245 | }
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/EdgeCaseSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import com.codahale.simplespec.Spec
5 | import com.codahale.jerkson.ParsingException
6 | import java.io.ByteArrayInputStream
7 | import org.junit.Test
8 |
9 | class EdgeCaseSpec extends Spec {
10 | class `Deserializing lists` {
11 | @Test def `doesn't cache Seq builders` = {
12 | parse[List[Int]]("[1,2,3,4]").must(be(List(1, 2, 3, 4)))
13 | parse[List[Int]]("[1,2,3,4]").must(be(List(1, 2, 3, 4)))
14 | }
15 | }
16 |
17 | class `Parsing a JSON array of ints with nulls` {
18 | @Test def `should be readable as a List[Option[Int]]` = {
19 | parse[List[Option[Int]]]("[1,2,null,4]").must(be(List(Some(1), Some(2), None, Some(4))))
20 | }
21 | }
22 |
23 | class `Deserializing maps` {
24 | @Test def `doesn't cache Map builders` = {
25 | parse[Map[String, Int]](""" {"one":1, "two": 2} """).must(be(Map("one" -> 1, "two" -> 2)))
26 | parse[Map[String, Int]](""" {"one":1, "two": 2} """).must(be(Map("one" -> 1, "two" -> 2)))
27 | }
28 | }
29 |
30 | class `Parsing malformed JSON` {
31 | @Test def `should throw a ParsingException with an informative message` = {
32 | evaluating {
33 | parse[Boolean]("jjf8;09")
34 | }.must(throwA[ParsingException](
35 | "Malformed JSON. Unexpected character ('j' (code 106)): expected a " +
36 | "valid value (number, String, array, object, 'true', 'false' " +
37 | "or 'null') at character offset 0."))
38 |
39 | evaluating {
40 | parse[CaseClass]("{\"ye\":1")
41 | }.must(throwA[ParsingException](
42 | "Malformed JSON. Unexpected end-of-input: expected close marker for " +
43 | "OBJECT at character offset 20."))
44 | }
45 | }
46 |
47 | class `Parsing invalid JSON` {
48 | @Test def `should throw a ParsingException with an informative message` = {
49 | evaluating {
50 | parse[CaseClass]("900")
51 | }.must(throwA[ParsingException](
52 | ("""Can not deserialize instance of com.codahale.jerkson.tests.CaseClass out of VALUE_NUMBER_INT token\n""" +
53 | """ at \[Source: java.io.StringReader@[0-9a-f]+; line: 1, column: 1\]""").r))
54 |
55 | evaluating {
56 | parse[CaseClass]("{\"woo\": 1}")
57 | }.must(throwA[ParsingException]("Invalid JSON. Needed [id, name], but found [woo]."))
58 | }
59 | }
60 |
61 | class `Parsing an empty document` {
62 | @Test def `should throw a ParsingException with an informative message` = {
63 | val input = new ByteArrayInputStream(Array.empty)
64 | evaluating {
65 | parse[CaseClass](input)
66 | }.must(throwA[ParsingException]("""No content to map due to end\-of\-input""".r))
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/ExampleCaseClasses.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.fasterxml.jackson.databind.JsonNode
4 | import com.fasterxml.jackson.annotation.{JsonIgnoreProperties, JsonIgnore}
5 | import com.codahale.jerkson.JsonSnakeCase
6 |
7 | case class CaseClass(id: Long, name: String)
8 |
9 | case class CaseClassWithLazyVal(id: Long) {
10 | lazy val woo = "yeah"
11 | }
12 |
13 | case class CaseClassWithIgnoredField(id: Long) {
14 | @JsonIgnore
15 | val uncomfortable = "Bad Touch"
16 | }
17 |
18 | @JsonIgnoreProperties(Array("uncomfortable", "unpleasant"))
19 | case class CaseClassWithIgnoredFields(id: Long) {
20 | val uncomfortable = "Bad Touch"
21 | val unpleasant = "The Creeps"
22 | }
23 |
24 | case class CaseClassWithTransientField(id: Long) {
25 | @transient
26 | val lol = "I'm sure it's just a phase."
27 | }
28 |
29 | case class CaseClassWithOverloadedField(id: Long) {
30 | def id(prefix: String): String = prefix + id
31 | }
32 |
33 | case class CaseClassWithOption(value: Option[String])
34 |
35 | case class CaseClassWithJsonNode(value: JsonNode)
36 |
37 | case class CaseClassWithAllTypes(map: Map[String, String],
38 | set: Set[Int],
39 | string: String,
40 | list: List[Int],
41 | seq: Seq[Int],
42 | indexedSeq: IndexedSeq[Int],
43 | vector: Vector[Int],
44 | bigDecimal: BigDecimal,
45 | bigInt: BigInt,
46 | int: Int,
47 | long: Long,
48 | char: Char,
49 | bool: Boolean,
50 | short: Short,
51 | byte: Byte,
52 | float: Float,
53 | double: Double,
54 | any: Any,
55 | anyRef: AnyRef,
56 | intMap: Map[Int, Int],
57 | longMap: Map[Long, Long])
58 |
59 | object OuterObject {
60 | case class NestedCaseClass(id: Long)
61 |
62 | object InnerObject {
63 | case class SuperNestedCaseClass(id: Long)
64 | }
65 | }
66 |
67 | case class CaseClassWithTwoConstructors(id: Long, name: String) {
68 | def this(id: Long) = this(id, "New User")
69 | }
70 |
71 | @JsonSnakeCase
72 | case class CaseClassWithSnakeCase(oneThing: String, twoThing: String)
73 |
74 | case class CaseClassWithArrays(one: String, two: Array[String], three: Array[Int])
75 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/FancyTypeSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import java.net.URI
4 | import com.codahale.simplespec.Spec
5 | import org.junit.Test
6 | import com.codahale.jerkson.Json._
7 | import java.util.UUID
8 |
9 | class FancyTypeSupportSpec extends Spec {
10 | class `A URI` {
11 | @Test def `generates a JSON string` = {
12 | generate(new URI("http://example.com/resource?query=yes")).must(be("\"http://example.com/resource?query=yes\""))
13 | }
14 |
15 | @Test def `is parsable from a JSON string` = {
16 | parse[URI]("\"http://example.com/resource?query=yes\"").must(be(new URI("http://example.com/resource?query=yes")))
17 | }
18 | }
19 |
20 | class `A UUID` {
21 | val uuid = UUID.fromString("a62047e4-bfb5-4d71-aad7-1a6b338eee63")
22 |
23 | @Test def `generates a JSON string` = {
24 | generate(uuid).must(be("\"a62047e4-bfb5-4d71-aad7-1a6b338eee63\""))
25 | }
26 |
27 | @Test def `is parsable from a JSON string` = {
28 | parse[UUID]("\"a62047e4-bfb5-4d71-aad7-1a6b338eee63\"").must(be(uuid))
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/ImmutableCollectionSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.simplespec.Spec
4 | import com.codahale.jerkson.Json._
5 | import scala.collection.immutable._
6 | import com.codahale.jerkson.ParsingException
7 | import org.junit.{Ignore, Test}
8 |
9 | class ImmutableCollectionSupportSpec extends Spec {
10 | class `An immutable.Seq[Int]` {
11 | @Test def `generates a JSON array of ints` = {
12 | generate(Seq(1, 2, 3)).must(be("[1,2,3]"))
13 | }
14 |
15 | @Test def `is parsable from a JSON array of ints` = {
16 | parse[Seq[Int]]("[1,2,3]").must(be(Seq(1, 2, 3)))
17 | }
18 |
19 | @Test def `is parsable from an empty JSON array` = {
20 | parse[Seq[Int]]("[]").must(be(Seq.empty[Int]))
21 | }
22 | }
23 |
24 | class `An immutable.List[Int]` {
25 | @Test def `generates a JSON array of ints` = {
26 | generate(List(1, 2, 3)).must(be("[1,2,3]"))
27 | }
28 |
29 | @Test def `is parsable from a JSON array of ints` = {
30 | parse[List[Int]]("[1,2,3]").must(be(List(1, 2, 3)))
31 | }
32 |
33 | @Test def `is parsable from an empty JSON array` = {
34 | parse[List[Int]]("[]").must(be(List.empty[Int]))
35 | }
36 | }
37 |
38 | class `An immutable.IndexedSeq[Int]` {
39 | @Test def `generates a JSON array of ints` = {
40 | generate(IndexedSeq(1, 2, 3)).must(be("[1,2,3]"))
41 | }
42 |
43 | @Test def `is parsable from a JSON array of ints` = {
44 | parse[IndexedSeq[Int]]("[1,2,3]").must(be(IndexedSeq(1, 2, 3)))
45 | }
46 |
47 | @Test def `is parsable from an empty JSON array` = {
48 | parse[IndexedSeq[Int]]("[]").must(be(IndexedSeq.empty[Int]))
49 | }
50 | }
51 |
52 | class `An immutable.TreeSet[Int]` {
53 | @Test def `generates a JSON array` = {
54 | generate(TreeSet(1)).must(be("[1]"))
55 | }
56 |
57 | // TODO: 6/1/11 -- figure out how to deserialize TreeSet instances
58 |
59 | /**
60 | * I think all this would take is a mapping from Class[_] to Ordering, which
61 | * would need to have hard-coded the various primitive types, and then add
62 | * support for Ordered and Comparable classes. Once we have the Ordering,
63 | * we can pass it in manually to a builder.
64 | */
65 |
66 | @Ignore @Test def `is parsable from a JSON array of ints` = {
67 | parse[TreeSet[Int]]("[1,2,3]").must(be(TreeSet(1, 2, 3)))
68 | }
69 |
70 | @Ignore @Test def `is parsable from an empty JSON array` = {
71 | parse[TreeSet[Int]]("[]").must(be(TreeSet.empty[Int]))
72 | }
73 | }
74 |
75 | class `An immutable.HashSet[Int]` {
76 | @Test def `generates a JSON array` = {
77 | generate(HashSet(1)).must(be("[1]"))
78 | }
79 |
80 | @Test def `is parsable from a JSON array of ints` = {
81 | parse[HashSet[Int]]("[1,2,3]").must(be(HashSet(1, 2, 3)))
82 | }
83 |
84 | @Test def `is parsable from an empty JSON array` = {
85 | parse[HashSet[Int]]("[]").must(be(HashSet.empty[Int]))
86 | }
87 | }
88 |
89 | class `An immutable.BitSet` {
90 | @Test def `generates a JSON array` = {
91 | generate(BitSet(1)).must(be("[1]"))
92 | }
93 |
94 | @Test def `is parsable from a JSON array of ints` = {
95 | parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3)))
96 | }
97 |
98 | @Test def `is parsable from an empty JSON array` = {
99 | parse[BitSet]("[]").must(be(BitSet.empty))
100 | }
101 | }
102 |
103 | class `An immutable.TreeMap[String, Int]` {
104 | @Test def `generates a JSON object` = {
105 | generate(TreeMap("one" -> 1)).must(be("""{"one":1}"""))
106 | }
107 |
108 | // TODO: 6/1/11 -- figure out how to deserialize TreeMap instances
109 |
110 | /**
111 | * I think all this would take is a mapping from Class[_] to Ordering, which
112 | * would need to have hard-coded the various primitive types, and then add
113 | * support for Ordered and Comparable classes. Once we have the Ordering,
114 | * we can pass it in manually to a builder.
115 | */
116 |
117 | @Ignore @Test def `is parsable from a JSON object with int field values` = {
118 | parse[TreeMap[String, Int]]("""{"one":1}""").must(be(TreeMap("one" -> 1)))
119 | }
120 |
121 | @Ignore @Test def `is parsable from an empty JSON object` = {
122 | parse[TreeMap[String, Int]]("{}").must(be(TreeMap.empty[String, Int]))
123 | }
124 | }
125 |
126 | class `An immutable.HashMap[String, Int]` {
127 | @Test def `generates a JSON object` = {
128 | generate(HashMap("one" -> 1)).must(be("""{"one":1}"""))
129 | }
130 |
131 | @Test def `is parsable from a JSON object with int field values` = {
132 | parse[HashMap[String, Int]]("""{"one":1}""").must(be(HashMap("one" -> 1)))
133 | }
134 |
135 | @Test def `is parsable from an empty JSON object` = {
136 | parse[HashMap[String, Int]]("{}").must(be(HashMap.empty[String, Int]))
137 | }
138 | }
139 |
140 | class `An immutable.HashMap[String, Any]` {
141 | @Test def `generates a JSON object` = {
142 | generate(HashMap[String, Any]("one" -> 1)).must(be("""{"one":1}"""))
143 | }
144 |
145 | @Test def `is parsable from a JSON object with int field values` = {
146 | parse[HashMap[String, Any]]("""{"one":1}""").must(be(HashMap("one" -> 1)))
147 | }
148 |
149 | @Test def `is parsable from an empty JSON object` = {
150 | parse[HashMap[String, Any]]("{}").must(be(HashMap.empty[String, Any]))
151 | }
152 |
153 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
154 | evaluating {
155 | parse[HashMap[String, Any]]("[{}]")
156 | }.must(throwA[ParsingException])
157 | }
158 | }
159 |
160 | class `An immutable.Map[Int, String]` {
161 | @Test def `generates a JSON object` = {
162 | generate(Map(1 -> "one")).must(be("""{"1":"one"}"""))
163 | }
164 |
165 | @Test def `is parsable from a JSON object with decimal field names and string field values` = {
166 | parse[Map[Int, String]]("""{"1":"one"}""").must(be(Map(1 -> "one")))
167 | }
168 |
169 | @Test def `is not parsable from a JSON object with non-decimal field names` = {
170 | evaluating {
171 | parse[Map[Int, String]]("""{"one":"one"}""")
172 | }.must(throwA[ParsingException])
173 | }
174 |
175 | @Test def `is parsable from an empty JSON object` = {
176 | parse[Map[Int, String]]("{}").must(be(Map.empty[Int, String]))
177 | }
178 | }
179 |
180 | class `An immutable.Map[Int, Any]` {
181 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
182 | evaluating {
183 | parse[Map[Int, Any]]("[{}]")
184 | }.must(throwA[ParsingException])
185 | }
186 | }
187 |
188 | class `An immutable.IntMap[Any]` {
189 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
190 | evaluating {
191 | parse[IntMap[Any]]("[{}]")
192 | }.must(throwA[ParsingException])
193 | }
194 | }
195 |
196 | class `An immutable.LongMap[Any]` {
197 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
198 | evaluating {
199 | parse[LongMap[Any]]("[{}]")
200 | }.must(throwA[ParsingException])
201 | }
202 | }
203 |
204 | class `An immutable.Map[Long, Any]` {
205 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
206 | evaluating {
207 | parse[Map[Long, Any]]("[{}]")
208 | }.must(throwA[ParsingException])
209 | }
210 | }
211 |
212 | class `An immutable.Map[Long, String]` {
213 | @Test def `generates a JSON object` = {
214 | generate(Map(1L -> "one")).must(be("""{"1":"one"}"""))
215 | }
216 |
217 | @Test def `is parsable from a JSON object with decimal field names and string field values` = {
218 | parse[Map[Long, String]]("""{"1":"one"}""").must(be(Map(1L -> "one")))
219 | }
220 |
221 | @Test def `is not parsable from a JSON object with non-decimal field names` = {
222 | evaluating {
223 | parse[Map[Long, String]]("""{"one":"one"}""")
224 | }.must(throwA[ParsingException])
225 | }
226 |
227 | @Test def `is parsable from an empty JSON object` = {
228 | parse[Map[Long, String]]("{}").must(be(Map.empty[Long, String]))
229 | }
230 | }
231 |
232 | class `An immutable.IntMap[String]` {
233 | @Test def `generates a JSON object` = {
234 | generate(IntMap(1 -> "one")).must(be("""{"1":"one"}"""))
235 | }
236 |
237 | @Test def `is parsable from a JSON object with decimal field names and string field values` = {
238 | parse[IntMap[String]]("""{"1":"one"}""").must(be(IntMap(1 -> "one")))
239 | }
240 |
241 | @Test def `is not parsable from a JSON object with non-decimal field names` = {
242 | evaluating {
243 | parse[IntMap[String]]("""{"one":"one"}""")
244 | }.must(throwA[ParsingException])
245 | }
246 |
247 | @Test def `is parsable from an empty JSON object` = {
248 | parse[IntMap[String]]("{}").must(be(IntMap.empty[String]))
249 | }
250 | }
251 |
252 | class `An immutable.LongMap[String]` {
253 | @Test def `generates a JSON object` = {
254 | generate(LongMap(1L -> "one")).must(be("""{"1":"one"}"""))
255 | }
256 |
257 | @Test def `is parsable from a JSON object with int field names and string field values` = {
258 | parse[LongMap[String]]("""{"1":"one"}""").must(be(LongMap(1L -> "one")))
259 | }
260 |
261 | @Test def `is not parsable from a JSON object with non-decimal field names` = {
262 | evaluating {
263 | parse[LongMap[String]]("""{"one":"one"}""")
264 | }.must(throwA[ParsingException])
265 | }
266 |
267 | @Test def `is parsable from an empty JSON object` = {
268 | parse[LongMap[String]]("{}").must(be(LongMap.empty))
269 | }
270 | }
271 |
272 | class `An immutable.Queue[Int]` {
273 | @Test def `generates a JSON array` = {
274 | generate(Queue(1, 2, 3)).must(be("[1,2,3]"))
275 | }
276 |
277 | @Test def `is parsable from a JSON array of ints` = {
278 | parse[Queue[Int]]("[1,2,3]").must(be(Queue(1, 2, 3)))
279 | }
280 |
281 | @Test def `is parsable from an empty JSON array` = {
282 | parse[Queue[Int]]("[]").must(be(Queue.empty))
283 | }
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/JValueSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import com.codahale.jerkson.AST._
5 | import com.codahale.simplespec.Spec
6 | import org.junit.Test
7 |
8 | class JValueSpec extends Spec {
9 | class `Selecting single nodes` {
10 | @Test def `returns None with primitives` = {
11 | (parse[JValue]("8") \ "blah").must(be(JNull))
12 | }
13 |
14 | @Test def `returns None on nonexistent fields` = {
15 | (parse[JValue]("{\"one\": \"1\"}") \ "two").must(be(JNull))
16 | }
17 |
18 | @Test def `returns a JValue with an existing field` = {
19 | (parse[JValue]("{\"one\": \"1\"}") \ "one").must(be(JString("1")))
20 | }
21 | }
22 |
23 | class `Selecting array members` {
24 | @Test def `returns None with primitives` = {
25 | (parse[JValue]("\"derp\"").apply(0)).must(be(JNull))
26 | }
27 |
28 | @Test def `returns None on out of bounds` = {
29 | (parse[JValue]("[0, 1, 2, 3]").apply(4)).must(be(JNull))
30 | }
31 |
32 | @Test def `returns a JValue` = {
33 | (parse[JValue]("[0, 1, 2, 3]").apply(2)).must(be(JInt(2)))
34 | }
35 | }
36 |
37 | class `Deep selecting` {
38 | @Test def `returns Nil with primitives` = {
39 | (parse[JValue]("0.234") \\ "herp").must(be(empty))
40 | }
41 |
42 | @Test def `returns Nil on nothing found` = {
43 | (parse[JValue]("{\"one\": {\"two\" : \"three\"}}") \\ "four").must(be(empty))
44 | }
45 |
46 | @Test def `returns single leaf nodes` = {
47 | (parse[JValue]("{\"one\": {\"two\" : \"three\"}}") \\ "two").must(be(Seq(JString("three"))))
48 | }
49 |
50 | @Test def `should return multiple leaf nodes` = {
51 | (parse[JValue]("{\"one\": {\"two\" : \"three\"}, \"four\": {\"two\" : \"five\"}}") \\ "two").must(be(Seq(JString("three"),JString("five"))))
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/MutableCollectionSupportSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.simplespec.Spec
4 | import com.codahale.jerkson.Json._
5 | import scala.collection.mutable._
6 | import com.codahale.jerkson.ParsingException
7 | import org.junit.Test
8 |
9 | class MutableCollectionSupportSpec extends Spec {
10 | class `A mutable.ResizableArray[Int]` {
11 | @Test def `generates a JSON array of ints` = {
12 | generate(ResizableArray(1, 2, 3)).must(be("[1,2,3]"))
13 | }
14 |
15 | @Test def `is parsable from a JSON array of ints` = {
16 | parse[ResizableArray[Int]]("[1,2,3]").must(be(ResizableArray(1, 2, 3)))
17 | }
18 |
19 | @Test def `is parsable from an empty JSON array` = {
20 | parse[ResizableArray[Int]]("[]").must(be(ResizableArray.empty[Int]))
21 | }
22 | }
23 |
24 | class `A mutable.ArraySeq[Int]` {
25 | @Test def `generates a JSON array of ints` = {
26 | generate(ArraySeq(1, 2, 3)).must(be("[1,2,3]"))
27 | }
28 |
29 |
30 | @Test def `is parsable from a JSON array of ints` = {
31 | parse[ArraySeq[Int]]("[1,2,3]").must(be(ArraySeq(1, 2, 3)))
32 | }
33 |
34 | @Test def `is parsable from an empty JSON array` = {
35 | parse[ArraySeq[Int]]("[]").must(be(ArraySeq.empty[Int]))
36 | }
37 | }
38 |
39 | class `A mutable.MutableList[Int]` {
40 | private val xs = new MutableList[Int]
41 | xs ++= List(1, 2, 3)
42 |
43 | @Test def `generates a JSON array` = {
44 | generate(xs).must(be("[1,2,3]"))
45 | }
46 |
47 | @Test def `is parsable from a JSON array of ints` = {
48 | parse[MutableList[Int]]("[1,2,3]").must(be(xs))
49 | }
50 |
51 | @Test def `is parsable from an empty JSON array` = {
52 | parse[MutableList[Int]]("[]").must(be(new MutableList[Int]()))
53 | }
54 | }
55 |
56 | class `A mutable.Queue[Int]` {
57 | @Test def `generates a JSON array` = {
58 | generate(Queue(1, 2, 3)).must(be("[1,2,3]"))
59 | }
60 |
61 | @Test def `is parsable from a JSON array of ints` = {
62 | parse[Queue[Int]]("[1,2,3]").must(be(Queue(1, 2, 3)))
63 | }
64 |
65 | @Test def `is parsable from an empty JSON array` = {
66 | parse[Queue[Int]]("[]").must(be(new Queue[Int]()))
67 | }
68 | }
69 |
70 | class `A mutable.ListBuffer[Int]` {
71 | @Test def `generates a JSON array` = {
72 | generate(ListBuffer(1, 2, 3)).must(be("[1,2,3]"))
73 | }
74 |
75 | @Test def `is parsable from a JSON array of ints` = {
76 | parse[ListBuffer[Int]]("[1,2,3]").must(be(ListBuffer(1, 2, 3)))
77 | }
78 |
79 | @Test def `is parsable from an empty JSON array` = {
80 | parse[ListBuffer[Int]]("[]").must(be(ListBuffer.empty[Int]))
81 | }
82 | }
83 |
84 | class `A mutable.ArrayBuffer[Int]` {
85 | @Test def `generates a JSON array` = {
86 | generate(ArrayBuffer(1, 2, 3)).must(be("[1,2,3]"))
87 | }
88 |
89 | @Test def `is parsable from a JSON array of ints` = {
90 | parse[ArrayBuffer[Int]]("[1,2,3]").must(be(ArrayBuffer(1, 2, 3)))
91 | }
92 |
93 | @Test def `is parsable from an empty JSON array` = {
94 | parse[ArrayBuffer[Int]]("[]").must(be(ArrayBuffer.empty[Int]))
95 | }
96 | }
97 |
98 | class `A mutable.BitSet` {
99 | @Test def `generates a JSON array` = {
100 | generate(BitSet(1)).must(be("[1]"))
101 | }
102 |
103 | @Test def `is parsable from a JSON array of ints` = {
104 | parse[BitSet]("[1,2,3]").must(be(BitSet(1, 2, 3)))
105 | }
106 |
107 | @Test def `is parsable from an empty JSON array` = {
108 | parse[BitSet]("[]").must(be(BitSet.empty))
109 | }
110 | }
111 |
112 | class `A mutable.HashSet[Int]` {
113 | @Test def `generates a JSON array` = {
114 | generate(HashSet(1)).must(be("[1]"))
115 | }
116 |
117 | @Test def `is parsable from a JSON array of ints` = {
118 | parse[HashSet[Int]]("[1,2,3]").must(be(HashSet(1, 2, 3)))
119 | }
120 |
121 | @Test def `is parsable from an empty JSON array` = {
122 | parse[HashSet[Int]]("[]").must(be(HashSet.empty[Int]))
123 | }
124 | }
125 |
126 | class `A mutable.LinkedHashSet[Int]` {
127 | @Test def `generates a JSON array` = {
128 | generate(LinkedHashSet(1)).must(be("[1]"))
129 | }
130 |
131 | @Test def `is parsable from a JSON array of ints` = {
132 | parse[LinkedHashSet[Int]]("[1,2,3]").must(be(LinkedHashSet(1, 2, 3)))
133 | }
134 |
135 | @Test def `is parsable from an empty JSON array` = {
136 | parse[LinkedHashSet[Int]]("[]").must(be(LinkedHashSet.empty[Int]))
137 | }
138 | }
139 |
140 | class `A mutable.Map[String, Int]` {
141 | @Test def `generates a JSON object` = {
142 | generate(Map("one" -> 1)).must(be("""{"one":1}"""))
143 | }
144 |
145 | @Test def `is parsable from a JSON object with int field values` = {
146 | parse[Map[String, Int]]("""{"one":1}""").must(be(Map("one" -> 1)))
147 | }
148 |
149 | @Test def `is parsable from an empty JSON object` = {
150 | parse[Map[String, Int]]("{}").must(be(Map.empty[String, Int]))
151 | }
152 | }
153 |
154 | class `A mutable.Map[String, Any]` {
155 | @Test def `is not parsable from an empty JSON object in a JSON array` = {
156 | evaluating {
157 | parse[Map[String, Any]]("[{}]")
158 | }.must(throwA[ParsingException])
159 | }
160 | }
161 |
162 | class `A mutable.HashMap[String, Int]` {
163 | @Test def `generates a JSON object` = {
164 | generate(HashMap("one" -> 1)).must(be("""{"one":1}"""))
165 | }
166 |
167 | @Test def `is parsable from a JSON object with int field values` = {
168 | parse[HashMap[String, Int]]("""{"one":1}""").must(be(HashMap("one" -> 1)))
169 | }
170 |
171 | @Test def `is parsable from an empty JSON object` = {
172 | parse[HashMap[String, Int]]("{}").must(be(HashMap.empty[String, Int]))
173 | }
174 | }
175 |
176 | class `A mutable.LinkedHashMap[String, Int]` {
177 | @Test def `generates a JSON object` = {
178 | generate(LinkedHashMap("one" -> 1)).must(be("""{"one":1}"""))
179 | }
180 |
181 | @Test def `is parsable from a JSON object with int field values` = {
182 | parse[LinkedHashMap[String, Int]]("""{"one":1}""").must(be(LinkedHashMap("one" -> 1)))
183 | }
184 |
185 | @Test def `is parsable from an empty JSON object` = {
186 | parse[LinkedHashMap[String, Int]]("{}").must(be(LinkedHashMap.empty[String, Int]))
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/test/scala/com/codahale/jerkson/tests/StreamingSpec.scala:
--------------------------------------------------------------------------------
1 | package com.codahale.jerkson.tests
2 |
3 | import com.codahale.jerkson.Json._
4 | import java.io.ByteArrayInputStream
5 | import com.codahale.simplespec.Spec
6 | import org.junit.Test
7 |
8 | class StreamingSpec extends Spec {
9 | class `Parsing a stream of objects` {
10 | val json = """[
11 | {"id":1, "name": "Coda"},
12 | {"id":2, "name": "Niki"},
13 | {"id":3, "name": "Biscuit"},
14 | {"id":4, "name": "Louie"}
15 | ]"""
16 |
17 | @Test def `returns an iterator of stream elements` = {
18 | stream[CaseClass](new ByteArrayInputStream(json.getBytes)).toList
19 | .must(be(CaseClass(1, "Coda") :: CaseClass(2, "Niki") ::
20 | CaseClass(3, "Biscuit") :: CaseClass(4, "Louie") :: Nil))
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------