diff options
author | Jakob Odersky <jakob@odersky.com> | 2018-03-07 14:07:25 -0800 |
---|---|---|
committer | Jakob Odersky <jakob@odersky.com> | 2019-06-10 23:22:15 +0200 |
commit | a43a10a12fd5653e6050c652024764416b71ab54 (patch) | |
tree | 19824c4e4eb31f849d51520d57a484e4f4ca0640 | |
parent | da8ed26cfed5e958eeb29a3444ff882a46090459 (diff) | |
download | spray-json-a43a10a12fd5653e6050c652024764416b71ab54.tar.gz spray-json-a43a10a12fd5653e6050c652024764416b71ab54.tar.bz2 spray-json-a43a10a12fd5653e6050c652024764416b71ab54.zip |
Add support for ScalaJS and Scala Native
Binary compatibility with previous versions is maintained.
-rw-r--r-- | .travis.yml | 31 | ||||
-rw-r--r-- | CHANGELOG | 13 | ||||
-rw-r--r-- | README.markdown | 25 | ||||
-rw-r--r-- | build.sbt | 168 | ||||
-rw-r--r-- | js/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template | 39 | ||||
-rw-r--r-- | js/src/main/scala/spray/json/ProductFormats.scala | 80 | ||||
-rw-r--r-- | jvm/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template (renamed from src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template) | 0 | ||||
-rw-r--r-- | jvm/src/main/scala/spray/json/ProductFormats.scala (renamed from src/main/scala/spray/json/ProductFormats.scala) | 0 | ||||
-rw-r--r-- | jvm/src/test/scala/spray/json/JsonParserSpecJvm.scala | 62 | ||||
-rw-r--r-- | jvm/src/test/scala/spray/json/ProductFormatsSpec.scala (renamed from src/test/scala/spray/json/ProductFormatsSpec.scala) | 0 | ||||
-rw-r--r-- | jvm/src/test/scala/spray/json/ReadmeSpec.scala (renamed from src/test/scala/spray/json/ReadmeSpec.scala) | 0 | ||||
l--------- | native/src | 1 | ||||
-rw-r--r-- | project/plugins.sbt | 6 | ||||
-rw-r--r-- | publish.sbt | 27 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/AdditionalFormats.scala (renamed from src/main/scala/spray/json/AdditionalFormats.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/BasicFormats.scala (renamed from src/main/scala/spray/json/BasicFormats.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/CollectionFormats.scala (renamed from src/main/scala/spray/json/CollectionFormats.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/CompactPrinter.scala (renamed from src/main/scala/spray/json/CompactPrinter.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/DefaultJsonProtocol.scala (renamed from src/main/scala/spray/json/DefaultJsonProtocol.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/JsValue.scala (renamed from src/main/scala/spray/json/JsValue.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/JsonFormat.scala (renamed from src/main/scala/spray/json/JsonFormat.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/JsonParser.scala (renamed from src/main/scala/spray/json/JsonParser.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/JsonParserSettings.scala (renamed from src/main/scala/spray/json/JsonParserSettings.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/JsonPrinter.scala (renamed from src/main/scala/spray/json/JsonPrinter.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/PrettyPrinter.scala (renamed from src/main/scala/spray/json/PrettyPrinter.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/SortedPrinter.scala (renamed from src/main/scala/spray/json/SortedPrinter.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/StandardFormats.scala (renamed from src/main/scala/spray/json/StandardFormats.scala) | 0 | ||||
-rw-r--r-- | shared/src/main/scala/spray/json/package.scala (renamed from src/main/scala/spray/json/package.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/resources/test.json (renamed from src/test/resources/test.json) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala (renamed from src/test/scala/spray/json/AdditionalFormatsSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/BasicFormatsSpec.scala (renamed from src/test/scala/spray/json/BasicFormatsSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/CollectionFormatsSpec.scala (renamed from src/test/scala/spray/json/CollectionFormatsSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/CompactPrinterSpec.scala (renamed from src/test/scala/spray/json/CompactPrinterSpec.scala) | 3 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/CustomFormatSpec.scala (renamed from src/test/scala/spray/json/CustomFormatSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/HashCodeCollider.scala (renamed from src/test/scala/spray/json/HashCodeCollider.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/JsonParserSpec.scala (renamed from src/test/scala/spray/json/JsonParserSpec.scala) | 40 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/PrettyPrinterSpec.scala (renamed from src/test/scala/spray/json/PrettyPrinterSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/RoundTripSpecs.scala (renamed from src/test/scala/spray/json/RoundTripSpecs.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/SortedPrinterSpec.scala (renamed from src/test/scala/spray/json/SortedPrinterSpec.scala) | 0 | ||||
-rw-r--r-- | shared/src/test/scala/spray/json/StandardFormatsSpec.scala (renamed from src/test/scala/spray/json/StandardFormatsSpec.scala) | 0 |
40 files changed, 336 insertions, 159 deletions
diff --git a/.travis.yml b/.travis.yml index d501ae9..eb8d2d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,27 @@ +# sudo required for installing dependencies of Scala Native +sudo: required language: scala -scala: - - 2.10.7 - - 2.11.12 - - 2.12.8 - - 2.13.0-RC3 - jdk: - oraclejdk8 -matrix: - include: - - jdk: openjdk11 - scala: 2.12.8 +before_install: + - curl https://raw.githubusercontent.com/scala-native/scala-native/4c2ce46242f872f3e7879810565147c4233cd52e/scripts/travis_setup.sh | bash -x script: - - sbt "++ ${TRAVIS_SCALA_VERSION}!" test mimaReportBinaryIssues + - sbt +test +mimaReportBinaryIssues before_cache: - - find $HOME/.ivy2 -name "ivydata-*.properties" -print -delete - - find $HOME/.sbt -name "*.lock" -print -delete + - find $HOME/.sbt -name "*.lock" | xargs rm + - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm + - rm -rf $HOME/.ivy2/{cache,local}/io.spray/ cache: directories: - $HOME/.ivy2/cache - - $HOME/.sbt/boot + - $HOME/.sbt/boot/ -notifications: - email: - - johannes@spray.io - - mathias@spray.io +#notifications: +# email: +# - johannes@spray.io +# - mathias@spray.io @@ -1,3 +1,16 @@ +Version 1.3.6 (UNRELEASED) +-------------------------- +This version mainly updates the build and introduces support for +cross-compiling spray to ScalaJS and Scala Native, in addition to +traditional Scala on the JVM. + +Source and binary compatibility with the previous release is maintained +for the JVM project. However, ScalaJS and Scala Native versions do not +implement reflective product formats, meaning that there are no more +`jsonFormatX()` methods on these platforms. The overloaded method +`jsonFormat(<constructor>, <field_names>*)` is still available and may +be used as a workaround. + Version 1.3.5 (2017-10-24) -------------------------- diff --git a/README.markdown b/README.markdown index 7c040db..7494184 100644 --- a/README.markdown +++ b/README.markdown @@ -17,6 +17,14 @@ as depicted in this diagram: ![Spray-JSON conversions](images/Conversions.png "Conversions possible with Spray-JSON") +_spray-json_ is available for Scala on the JVM, ScalaJS and Scala Native<sup>1</sup>. + +<sup>1</sup>: *The versions for ScalaJS and Scala Native do not support generating +case class formats through introspection, meaning that there are no `jsonFormatX` +methods available on these platforms. See section +[Providing JsonFormats for Case Classes](#providing-jsonformats-for-case-classes) below +for an explanation and workaround.* + ### Installation _spray-json_ is available from maven central. @@ -42,26 +50,26 @@ import DefaultJsonProtocol._ // if you don't supply your own Protocol (see below and do one or more of the following: 1. Parse a JSON string into its Abstract Syntax Tree (AST) representation - + ```scala val source = """{ "some": "JSON source" }""" val jsonAst = source.parseJson // or JsonParser(source) ``` - + 2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter` - + ```scala val json = jsonAst.prettyPrint // or .compactPrint ``` - + 3. Convert any Scala object to a JSON AST using the `toJson` extension method - + ```scala val jsonAst = List(1, 2, 3).toJson ``` - + 4. Convert a JSON AST to a Scala object with the `convertTo` method - + ```scala val myObject = jsonAst.convertTo[MyObjectType] ``` @@ -130,6 +138,9 @@ method). The `jsonFormatX` methods try to extract the field names of your case c field names or if your JSON objects use member names that differ from the case class fields you can also use `jsonFormat` directly. +*Note that spray-json for ScalaJS or Scala Native does not support the `jsonFormatX` methods, and hence using +the `jsonFormat` overloads is required on these platforms.* + There is one additional quirk: If you explicitly declare the companion object for your case class the notation above will stop working. You'll have to explicitly refer to the companion objects `apply` method to fix this: @@ -1,94 +1,80 @@ +// shadow sbt-scalajs' crossProject and CrossType until Scala.js 1.0.0 is released +import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType} import com.typesafe.tools.mima.core.{ProblemFilters, ReversedMissingMethodProblem} -name := "spray-json" - -version := "1.3.6-SNAPSHOT" - -organization := "io.spray" - -organizationHomepage := Some(new URL("http://spray.io")) - -description := "A Scala library for easy and idiomatic JSON (de)serialization" - -homepage := Some(new URL("https://github.com/spray/spray-json")) - -startYear := Some(2011) - -licenses := Seq("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) - -scalacOptions ++= Seq("-feature", "-language:_", "-unchecked", "-deprecation", "-Xlint", "-encoding", "utf8") - -resolvers += Opts.resolver.sonatypeReleases - -libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 10)) => Seq( - "org.specs2" %% "specs2-core" % "3.10.0" % "test", - "org.specs2" %% "specs2-scalacheck" % "3.10.0" % "test", - "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" - ) - case Some((2, n)) if n >= 11 => Seq( - "org.specs2" %% "specs2-core" % "4.5.1" % "test", - "org.specs2" %% "specs2-scalacheck" % "4.5.1" % "test", - "org.scalacheck" %% "scalacheck" % "1.14.0" % "test" +lazy val scala210 = "2.10.7" +lazy val scala211 = "2.11.12" +lazy val scala212 = "2.12.8" +lazy val scala213 = "2.13.0" + +lazy val sprayJson = + crossProject(JVMPlatform, JSPlatform, NativePlatform) + .crossType(CrossType.Full) + .in(file(".")) + .settings( + name := "spray-json", + version := "1.3.6-SNAPSHOT", + scalaVersion := crossScalaVersions.value.head, + scalacOptions ++= Seq("-feature", "-language:_", "-unchecked", "-deprecation", "-Xlint", "-encoding", "utf8"), + (scalacOptions in doc) ++= Seq("-doc-title", name.value + " " + version.value), + scalaBinaryVersion := { + val sV = scalaVersion.value + if (CrossVersion.isScalaApiCompatible(sV)) + CrossVersion.binaryScalaVersion(sV) + else + sV + }, + // Workaround for "Shared resource directory is ignored" + // https://github.com/portable-scala/sbt-crossproject/issues/74 + unmanagedResourceDirectories in Test += (baseDirectory in ThisBuild).value / "shared/src/test/resources" + ) + .enablePlugins(spray.boilerplate.BoilerplatePlugin) + .platformsSettings(JVMPlatform, JSPlatform)( + libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 10)) => Seq( + "org.specs2" %%% "specs2-core" % "3.8.9" % "test", + "org.specs2" %%% "specs2-scalacheck" % "3.8.9" % "test", + "org.scalacheck" %%% "scalacheck" % "1.13.4" % "test" + ) + case Some((2, n)) if n >= 11 => Seq( + "org.specs2" %%% "specs2-core" % "4.5.1" % "test", + "org.specs2" %%% "specs2-scalacheck" % "4.5.1" % "test", + "org.scalacheck" %%% "scalacheck" % "1.14.0" % "test" + ) + case _ => Nil + }) + ) + .configurePlatforms(JVMPlatform)(_.enablePlugins(SbtOsgi)) + .jvmSettings( + crossScalaVersions := Seq(scala213, scala212, scala211, scala210), + OsgiKeys.exportPackage := Seq("""spray.json.*;version="${Bundle-Version}""""), + OsgiKeys.importPackage := Seq("""scala.*;version="$<range;[==,=+);%s>"""".format(scalaVersion.value)), + OsgiKeys.importPackage ++= Seq("""spray.json;version="${Bundle-Version}"""", "*"), + OsgiKeys.additionalHeaders := Map("-removeheaders" -> "Include-Resource,Private-Package"), + mimaPreviousArtifacts := (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 13)) => Set.empty + case _ => Set("io.spray" %% "spray-json" % "1.3.5") + }), + mimaBinaryIssueFilters := Seq( + ProblemFilters.exclude[ReversedMissingMethodProblem]("spray.json.PrettyPrinter.organiseMembers") + ) + ) + .jsSettings( + crossScalaVersions := Seq(scala212, scala211) + ) + .nativeSettings( + crossScalaVersions := Seq(scala211), + // Disable tests in Scala Native until testing frameworks for it become available + unmanagedSourceDirectories in Test := Seq.empty + ) + +lazy val sprayJsonJVM = sprayJson.jvm +lazy val sprayJsonJS = sprayJson.js +lazy val sprayJsonNative = sprayJson.native + +lazy val root = (project in file(".")) + .aggregate(sprayJsonJVM, sprayJsonJS, sprayJsonNative) + .settings( + publish := {}, + publishLocal := {} ) - case _ => Nil -}) - -(scalacOptions in doc) ++= Seq("-doc-title", name.value + " " + version.value) - -// generate boilerplate -enablePlugins(BoilerplatePlugin) - -// OSGi settings -enablePlugins(SbtOsgi) - -OsgiKeys.exportPackage := Seq("""spray.json.*;version="${Bundle-Version}"""") - -OsgiKeys.importPackage := Seq("""scala.*;version="$<range;[==,=+);%s>"""".format(scalaVersion.value)) - -OsgiKeys.importPackage ++= Seq("""spray.json;version="${Bundle-Version}"""", "*") - -OsgiKeys.additionalHeaders := Map("-removeheaders" -> "Include-Resource,Private-Package") - -// Migration Manager -mimaPreviousArtifacts := (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, 13)) => Set.empty - case _ => - Set("1.3.2", "1.3.3", "1.3.4").map { v => - "io.spray" %% "spray-json" % v - } -}) - -mimaBinaryIssueFilters := Seq( - ProblemFilters.exclude[ReversedMissingMethodProblem]("spray.json.PrettyPrinter.organiseMembers") -) - -/////////////// -// publishing -/////////////// - -crossScalaVersions := Seq("2.12.8", "2.10.7", "2.11.12", "2.13.0-RC3") - -publishMavenStyle := true - -useGpg := true - -publishTo := { - val nexus = "https://oss.sonatype.org/" - if (version.value.trim.endsWith("SNAPSHOT")) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") -} - -pomIncludeRepository := { _ => false } - -pomExtra := - <scm> - <url>git://github.com/spray/spray.git</url> - <connection>scm:git:git@github.com:spray/spray.git</connection> - </scm> - <developers> - <developer><id>sirthias</id><name>Mathias Doenitz</name></developer> - <developer><id>jrudolph</id><name>Johannes Rudolph</name></developer> - </developers> diff --git a/js/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template b/js/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template new file mode 100644 index 0000000..d6f83f7 --- /dev/null +++ b/js/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011,2012 Mathias Doenitz, Johannes Rudolph + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package spray.json + +trait ProductFormatsInstances { self: ProductFormats with StandardFormats => +[# // Case classes with 1 parameters + + def jsonFormat[[#P1 :JF#], T <: Product](construct: ([#P1#]) => T, [#fieldName1: String#]): RootJsonFormat[T] = new RootJsonFormat[T]{ + def write(p: T) = { + val fields = new collection.mutable.ListBuffer[(String, JsValue)] + fields.sizeHint(1 * 2) + [#fields ++= productElement##2Field[P1](fieldName1, p, 0)# + ] + JsObject(fields: _*) + } + def read(value: JsValue) = { + [#val p1V = fromField[P1](value, fieldName1)# + ] + construct([#p1V#]) + } + }# + + +] +} diff --git a/js/src/main/scala/spray/json/ProductFormats.scala b/js/src/main/scala/spray/json/ProductFormats.scala new file mode 100644 index 0000000..bc06eac --- /dev/null +++ b/js/src/main/scala/spray/json/ProductFormats.scala @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package spray.json + +/** + * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait + * (especially case classes) + */ +trait ProductFormats extends ProductFormatsInstances { + this: StandardFormats => + + def jsonFormat0[T](construct: () => T): RootJsonFormat[T] = + new RootJsonFormat[T] { + def write(p: T) = JsObject() + def read(value: JsValue) = value match { + case JsObject(_) => construct() + case _ => throw new DeserializationException("Object expected") + } + } + + // helpers + + protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField] = Nil) + (implicit writer: JsonWriter[T]): List[JsField] = { + val value = p.productElement(ix).asInstanceOf[T] + writer match { + case _: OptionFormat[_] if (value == None) => rest + case _ => (fieldName, writer.write(value)) :: rest + } + } + + protected def fromField[T](value: JsValue, fieldName: String) + (implicit reader: JsonReader[T]) = value match { + case x: JsObject if + (reader.isInstanceOf[OptionFormat[_]] & + !x.fields.contains(fieldName)) => + None.asInstanceOf[T] + case x: JsObject => + try reader.read(x.fields(fieldName)) + catch { + case e: NoSuchElementException => + deserializationError("Object is missing required member '" + fieldName + "'", e, fieldName :: Nil) + case DeserializationException(msg, cause, fieldNames) => + deserializationError(msg, cause, fieldName :: fieldNames) + } + case _ => deserializationError("Object expected in field '" + fieldName + "'", fieldNames = fieldName :: Nil) + } + +} + +/** + * This trait supplies an alternative rendering mode for optional case class members. + * Normally optional members that are undefined (`None`) are not rendered at all. + * By mixing in this trait into your custom JsonProtocol you can enforce the rendering of undefined members as `null`. + * (Note that this only affect JSON writing, spray-json will always read missing optional members as well as `null` + * optional members as `None`.) + */ +trait NullOptions extends ProductFormats { + this: StandardFormats => + + override protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField]) + (implicit writer: JsonWriter[T]) = { + val value = p.productElement(ix).asInstanceOf[T] + (fieldName, writer.write(value)) :: rest + } +} diff --git a/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template b/jvm/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template index fa0d875..fa0d875 100644 --- a/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template +++ b/jvm/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template diff --git a/src/main/scala/spray/json/ProductFormats.scala b/jvm/src/main/scala/spray/json/ProductFormats.scala index 81a48af..81a48af 100644 --- a/src/main/scala/spray/json/ProductFormats.scala +++ b/jvm/src/main/scala/spray/json/ProductFormats.scala diff --git a/jvm/src/test/scala/spray/json/JsonParserSpecJvm.scala b/jvm/src/test/scala/spray/json/JsonParserSpecJvm.scala new file mode 100644 index 0000000..c0345e1 --- /dev/null +++ b/jvm/src/test/scala/spray/json/JsonParserSpecJvm.scala @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package spray.json + +import org.specs2.mutable._ +import scala.util.control.NonFatal + +class JsonParserSpecJvm extends Specification { + + "The JsonParser (on the JVM)" should { + "be reentrant" in { + import scala.concurrent.{Await, Future} + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + + val largeJsonSource = scala.io.Source.fromInputStream(getClass.getResourceAsStream("/test.json")).mkString + val list = Await.result( + Future.traverse(List.fill(20)(largeJsonSource))(src => Future(JsonParser(src))), + 5.seconds + ) + list.map(_.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size) === List.fill(20)(100) + } + "fail gracefully for deeply nested structures" in { + val queue = new java.util.ArrayDeque[String]() + + // testing revealed that each recursion will need approx. 280 bytes of stack space + val depth = 1500 + val runnable = new Runnable { + override def run(): Unit = + try { + val nested = "[{\"key\":" * (depth / 2) + JsonParser(nested) + queue.push("didn't fail") + } catch { + case s: StackOverflowError => queue.push("stackoverflow") + case NonFatal(e) => + queue.push(s"nonfatal: ${e.getMessage}") + } + } + + val thread = new Thread(null, runnable, "parser-test", 655360) + thread.start() + thread.join() + queue.peek() === "nonfatal: JSON input nested too deeply:JSON input was nested more deeply than the configured limit of maxNesting = 1000" + } + } + +} diff --git a/src/test/scala/spray/json/ProductFormatsSpec.scala b/jvm/src/test/scala/spray/json/ProductFormatsSpec.scala index f42c46d..f42c46d 100644 --- a/src/test/scala/spray/json/ProductFormatsSpec.scala +++ b/jvm/src/test/scala/spray/json/ProductFormatsSpec.scala diff --git a/src/test/scala/spray/json/ReadmeSpec.scala b/jvm/src/test/scala/spray/json/ReadmeSpec.scala index 306b656..306b656 100644 --- a/src/test/scala/spray/json/ReadmeSpec.scala +++ b/jvm/src/test/scala/spray/json/ReadmeSpec.scala diff --git a/native/src b/native/src new file mode 120000 index 0000000..f2bb155 --- /dev/null +++ b/native/src @@ -0,0 +1 @@ +../js/src/
\ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 41badc2..3cdb1d8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,9 @@ addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.5") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") - addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.3.0") + +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.0") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.6.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.8") diff --git a/publish.sbt b/publish.sbt new file mode 100644 index 0000000..4dfcd29 --- /dev/null +++ b/publish.sbt @@ -0,0 +1,27 @@ +inThisBuild(Seq( + organization := "io.spray", + organizationHomepage := Some(new URL("http://spray.io")), + description := "A Scala library for easy and idiomatic JSON (de)serialization", + homepage := Some(new URL("https://github.com/spray/spray-json")), + startYear := Some(2011), + licenses := Seq("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")), + publishMavenStyle := true, + useGpg := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (version.value.trim.endsWith("SNAPSHOT")) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomIncludeRepository := { _ => false }, + pomExtra := + <scm> + <url>git://github.com/spray/spray.git</url> + <connection>scm:git:git@github.com:spray/spray.git</connection> + </scm> + <developers> + <developer><id>sirthias</id><name>Mathias Doenitz</name></developer> + <developer><id>jrudolph</id><name>Johannes Rudolph</name></developer> + </developers> +)) diff --git a/src/main/scala/spray/json/AdditionalFormats.scala b/shared/src/main/scala/spray/json/AdditionalFormats.scala index fbabb0b..fbabb0b 100644 --- a/src/main/scala/spray/json/AdditionalFormats.scala +++ b/shared/src/main/scala/spray/json/AdditionalFormats.scala diff --git a/src/main/scala/spray/json/BasicFormats.scala b/shared/src/main/scala/spray/json/BasicFormats.scala index 2e6342f..2e6342f 100644 --- a/src/main/scala/spray/json/BasicFormats.scala +++ b/shared/src/main/scala/spray/json/BasicFormats.scala diff --git a/src/main/scala/spray/json/CollectionFormats.scala b/shared/src/main/scala/spray/json/CollectionFormats.scala index ef94297..ef94297 100644 --- a/src/main/scala/spray/json/CollectionFormats.scala +++ b/shared/src/main/scala/spray/json/CollectionFormats.scala diff --git a/src/main/scala/spray/json/CompactPrinter.scala b/shared/src/main/scala/spray/json/CompactPrinter.scala index 7260c2f..7260c2f 100644 --- a/src/main/scala/spray/json/CompactPrinter.scala +++ b/shared/src/main/scala/spray/json/CompactPrinter.scala diff --git a/src/main/scala/spray/json/DefaultJsonProtocol.scala b/shared/src/main/scala/spray/json/DefaultJsonProtocol.scala index 4c93184..4c93184 100644 --- a/src/main/scala/spray/json/DefaultJsonProtocol.scala +++ b/shared/src/main/scala/spray/json/DefaultJsonProtocol.scala diff --git a/src/main/scala/spray/json/JsValue.scala b/shared/src/main/scala/spray/json/JsValue.scala index 9ed94da..9ed94da 100644 --- a/src/main/scala/spray/json/JsValue.scala +++ b/shared/src/main/scala/spray/json/JsValue.scala diff --git a/src/main/scala/spray/json/JsonFormat.scala b/shared/src/main/scala/spray/json/JsonFormat.scala index c4915cc..c4915cc 100644 --- a/src/main/scala/spray/json/JsonFormat.scala +++ b/shared/src/main/scala/spray/json/JsonFormat.scala diff --git a/src/main/scala/spray/json/JsonParser.scala b/shared/src/main/scala/spray/json/JsonParser.scala index 7dfae0a..7dfae0a 100644 --- a/src/main/scala/spray/json/JsonParser.scala +++ b/shared/src/main/scala/spray/json/JsonParser.scala diff --git a/src/main/scala/spray/json/JsonParserSettings.scala b/shared/src/main/scala/spray/json/JsonParserSettings.scala index 8c76b0a..8c76b0a 100644 --- a/src/main/scala/spray/json/JsonParserSettings.scala +++ b/shared/src/main/scala/spray/json/JsonParserSettings.scala diff --git a/src/main/scala/spray/json/JsonPrinter.scala b/shared/src/main/scala/spray/json/JsonPrinter.scala index 42cbab4..42cbab4 100644 --- a/src/main/scala/spray/json/JsonPrinter.scala +++ b/shared/src/main/scala/spray/json/JsonPrinter.scala diff --git a/src/main/scala/spray/json/PrettyPrinter.scala b/shared/src/main/scala/spray/json/PrettyPrinter.scala index 7526dab..7526dab 100644 --- a/src/main/scala/spray/json/PrettyPrinter.scala +++ b/shared/src/main/scala/spray/json/PrettyPrinter.scala diff --git a/src/main/scala/spray/json/SortedPrinter.scala b/shared/src/main/scala/spray/json/SortedPrinter.scala index 28db225..28db225 100644 --- a/src/main/scala/spray/json/SortedPrinter.scala +++ b/shared/src/main/scala/spray/json/SortedPrinter.scala diff --git a/src/main/scala/spray/json/StandardFormats.scala b/shared/src/main/scala/spray/json/StandardFormats.scala index e59de64..e59de64 100644 --- a/src/main/scala/spray/json/StandardFormats.scala +++ b/shared/src/main/scala/spray/json/StandardFormats.scala diff --git a/src/main/scala/spray/json/package.scala b/shared/src/main/scala/spray/json/package.scala index a8a42f0..a8a42f0 100644 --- a/src/main/scala/spray/json/package.scala +++ b/shared/src/main/scala/spray/json/package.scala diff --git a/src/test/resources/test.json b/shared/src/test/resources/test.json index 8308d37..8308d37 100644 --- a/src/test/resources/test.json +++ b/shared/src/test/resources/test.json diff --git a/src/test/scala/spray/json/AdditionalFormatsSpec.scala b/shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala index 01127e6..01127e6 100644 --- a/src/test/scala/spray/json/AdditionalFormatsSpec.scala +++ b/shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala diff --git a/src/test/scala/spray/json/BasicFormatsSpec.scala b/shared/src/test/scala/spray/json/BasicFormatsSpec.scala index 454e1cc..454e1cc 100644 --- a/src/test/scala/spray/json/BasicFormatsSpec.scala +++ b/shared/src/test/scala/spray/json/BasicFormatsSpec.scala diff --git a/src/test/scala/spray/json/CollectionFormatsSpec.scala b/shared/src/test/scala/spray/json/CollectionFormatsSpec.scala index 9d6970b..9d6970b 100644 --- a/src/test/scala/spray/json/CollectionFormatsSpec.scala +++ b/shared/src/test/scala/spray/json/CollectionFormatsSpec.scala diff --git a/src/test/scala/spray/json/CompactPrinterSpec.scala b/shared/src/test/scala/spray/json/CompactPrinterSpec.scala index 6a9560b..691daa9 100644 --- a/src/test/scala/spray/json/CompactPrinterSpec.scala +++ b/shared/src/test/scala/spray/json/CompactPrinterSpec.scala @@ -39,9 +39,6 @@ class CompactPrinterSpec extends Specification { "print JsNumber(1.23) to '1.23'" in { CompactPrinter(JsNumber(1.23)) mustEqual "1.23" } - "print JsNumber(-1E10) to '-1E10'" in { - CompactPrinter(JsNumber(-1E10)) mustEqual "-1.0E+10" - } "print JsNumber(12.34e-10) to '12.34e-10'" in { CompactPrinter(JsNumber(12.34e-10)) mustEqual "1.234E-9" } diff --git a/src/test/scala/spray/json/CustomFormatSpec.scala b/shared/src/test/scala/spray/json/CustomFormatSpec.scala index 2397abc..2397abc 100644 --- a/src/test/scala/spray/json/CustomFormatSpec.scala +++ b/shared/src/test/scala/spray/json/CustomFormatSpec.scala diff --git a/src/test/scala/spray/json/HashCodeCollider.scala b/shared/src/test/scala/spray/json/HashCodeCollider.scala index 57388b9..57388b9 100644 --- a/src/test/scala/spray/json/HashCodeCollider.scala +++ b/shared/src/test/scala/spray/json/HashCodeCollider.scala diff --git a/src/test/scala/spray/json/JsonParserSpec.scala b/shared/src/test/scala/spray/json/JsonParserSpec.scala index 1ca0ddc..0793e66 100644 --- a/src/test/scala/spray/json/JsonParserSpec.scala +++ b/shared/src/test/scala/spray/json/JsonParserSpec.scala @@ -18,8 +18,6 @@ package spray.json import org.specs2.mutable._ -import scala.util.control.NonFatal - class JsonParserSpec extends Specification { "The JsonParser" should { @@ -74,20 +72,8 @@ class JsonParserSpec extends Specification { val json = JsString("£0.99") JsonParser(json.prettyPrint.getBytes("UTF-8")) === json } - "be reentrant" in { - import scala.concurrent.{Await, Future} - import scala.concurrent.duration._ - import scala.concurrent.ExecutionContext.Implicits.global - - val largeJsonSource = scala.io.Source.fromInputStream(getClass.getResourceAsStream("/test.json")).mkString - val list = Await.result( - Future.traverse(List.fill(20)(largeJsonSource))(src => Future(JsonParser(src))), - 5.seconds - ) - list.map(_.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size) === List.fill(20)(100) - } "not show bad performance characteristics when object keys' hashCodes collide" in { - val numKeys = 10000 + val numKeys = 100000 val value = "null" val regularKeys = Iterator.from(1).map(i => s"key_$i").take(numKeys) @@ -153,30 +139,6 @@ class JsonParserSpec extends Specification { } } - "fail gracefully for deeply nested structures" in { - val queue = new java.util.ArrayDeque[String]() - - // testing revealed that each recursion will need approx. 280 bytes of stack space - val depth = 1500 - val runnable = new Runnable { - override def run(): Unit = - try { - val nested = "[{\"key\":" * (depth / 2) - JsonParser(nested) - queue.push("didn't fail") - } catch { - case s: StackOverflowError => queue.push("stackoverflow") - case NonFatal(e) => - queue.push(s"nonfatal: ${e.getMessage}") - } - } - - val thread = new Thread(null, runnable, "parser-test", 655360) - thread.start() - thread.join() - queue.peek() === "nonfatal: JSON input nested too deeply:JSON input was nested more deeply than the configured limit of maxNesting = 1000" - } - "parse multiple values when allowTrailingInput" in { val parser = new JsonParser("""{"key":1}{"key":2}""") parser.parseJsValue(true) === JsObject("key" -> JsNumber(1)) diff --git a/src/test/scala/spray/json/PrettyPrinterSpec.scala b/shared/src/test/scala/spray/json/PrettyPrinterSpec.scala index b547f59..b547f59 100644 --- a/src/test/scala/spray/json/PrettyPrinterSpec.scala +++ b/shared/src/test/scala/spray/json/PrettyPrinterSpec.scala diff --git a/src/test/scala/spray/json/RoundTripSpecs.scala b/shared/src/test/scala/spray/json/RoundTripSpecs.scala index 6bee7b4..6bee7b4 100644 --- a/src/test/scala/spray/json/RoundTripSpecs.scala +++ b/shared/src/test/scala/spray/json/RoundTripSpecs.scala diff --git a/src/test/scala/spray/json/SortedPrinterSpec.scala b/shared/src/test/scala/spray/json/SortedPrinterSpec.scala index f91640e..f91640e 100644 --- a/src/test/scala/spray/json/SortedPrinterSpec.scala +++ b/shared/src/test/scala/spray/json/SortedPrinterSpec.scala diff --git a/src/test/scala/spray/json/StandardFormatsSpec.scala b/shared/src/test/scala/spray/json/StandardFormatsSpec.scala index 833f06a..833f06a 100644 --- a/src/test/scala/spray/json/StandardFormatsSpec.scala +++ b/shared/src/test/scala/spray/json/StandardFormatsSpec.scala |