├── .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 | * 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 ~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield new ~(a, b) 90 | 91 | def ~-![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = for (a <- this; b <- next orError) yield a 92 | 93 | def -~![Out2, B, X2 >: X](next: => Rule[Out, Out2, B, X2]) = 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 | --------------------------------------------------------------------------------