diff --git a/NOTICE b/NOTICE index 16a6d69..9ab26cb 100644 --- a/NOTICE +++ b/NOTICE @@ -1,11 +1,13 @@ Scala-json is licensed under the conditions in the LICENSE document. The project includes -dependencies to other third-party projects that are obtained through SBT dependency management. +dependencies to other third-party projects that are obtained through SBT or other dependency management. These projects are licensed as follows: scala - (http://www.scala-lang.org/license.html) scala-js - (https://github.com/scala-js/scala-js/blob/master/LICENSE) +scala-native - (https://github.com/scala-native/scala-native/blob/master/LICENSE) jackson - (http://wiki.fasterxml.com/JacksonLicensing) +jansson - (https://github.com/akheron/jansson/blob/master/LICENSE) tut - (https://github.com/tpolecat/tut/blob/master/LICENSE) scalamacros.org paradise "macro paradise" - (https://github.com/scalamacros/paradise/blob/2.11.7/LICENSE.md) utest - unknown license (https://github.com/lihaoyi/utest) diff --git a/README.md b/README.md index 209f012..19bc5e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ res0: json.JObject = Features ----- Compile time JSON marshalling of primitive values, case-classes, basic collections, and whatever you can imagine -for [scala](https://github.com/scala/scala) and [scala-js](https://github.com/scala-js/scala-js). +for [scala](https://github.com/scala/scala), [scala-native](https://github.com/scala-native/scala-native) +and [scala-js](https://github.com/scala-js/scala-js). * Extensible accessor API. Serialize any type you want. * Provides a useful JS-like AST for intermediate JSON data. * Implicit [accessors](./docs/ACCESSORS.md) that chain to resolve Higher-Kind types (e.g. ```Option[T]```). @@ -29,12 +30,15 @@ for [scala](https://github.com/scala/scala) and [scala-js](https://github.com/sc * Enables use of normal looking scala structures with any previously existing JSON API. * Produces pretty and human readable JSON from normal scala types. * Supports [scala-js](https://github.com/scala-js/scala-js) so you can extend your models to the web. +* Supports [scala-native](https://github.com/scala-native/scala-native) so you can take your models everywhere else. + (requires [jansson](https://github.com/akheron/jansson)- available through apt, brew, etc) * Enables you to create readable APIs that match existing/specific class structure. * Exposes rich compile-time type info, more run-time type data than reflect could ever provide. * Uses existing scala collection CanBuildFrom factories to support buildable collections. * Provides support for unknown types (Any) via 'pickling' with a run-time class [registry](./docs/REGISTRY.md). * Support for scala 2.10.x, 2.11.x, 2.12.0-M3. * Support for scala-js 0.6.x. +* Support for scala-native 0.1.x. Docs @@ -57,7 +61,7 @@ resolvers += "mmreleases" at "https://artifactory.mediamath.com/artifactory/libs //scala libraryDependencies += "com.mediamath" %% "scala-json" % "1.0" -//or scala + scala-js +//or scala + scala-js/scala-native libraryDependencies += "com.mediamath" %%% "scala-json" % "1.0" //for @accessor annotation support @@ -70,9 +74,11 @@ Dependencies * [macro-paradise](http://docs.scala-lang.org/overviews/macros/paradise.html) 2.1.0+ required for @accessor annotation * [jackson](https://github.com/FasterXML/jackson) for JVM JSON string parsing +* [jansson](https://github.com/akheron/jansson) for Scala Native JSON string parsing * [µTest](https://github.com/lihaoyi/utest) for testing * [tut](https://github.com/tpolecat/tut) for doc building [Contributing](./docs/CONTRIBUTING.md) --- + diff --git a/build.sbt b/build.sbt index 69dba4a..4cc2f93 100644 --- a/build.sbt +++ b/build.sbt @@ -1,12 +1,19 @@ +//shadow sbt-scalajs' crossProject and CrossType until Scala.js 1.0.0 is released +import sbtcrossproject.{crossProject, CrossType} + version in ThisBuild := "1.0" -lazy val json = crossProject.in(file(".")) +lazy val json = crossProject(JSPlatform, JVMPlatform, NativePlatform) + .crossType(CrossType.Full) .settings(ScalaJSON.commonSettings: _*) .jvmSettings(ScalaJSON.jvmSettings: _*) .jsSettings(ScalaJSON.jsSettings: _*) + .nativeSettings(Nil) + .in(file(".")) lazy val jsonJVM = json.jvm lazy val jsonJS = json.js +lazy val jsonNative = json.native lazy val tutProject = project.in(file("docs")) .settings(ScalaJSON.tutSettings(jsonJVM): _*) diff --git a/docs/src/main/tut/README.md b/docs/src/main/tut/README.md index 1d11eb8..b8612b1 100644 --- a/docs/src/main/tut/README.md +++ b/docs/src/main/tut/README.md @@ -18,7 +18,8 @@ res0: json.JObject = Features ----- Compile time JSON marshalling of primitive values, case-classes, basic collections, and whatever you can imagine -for [scala](https://github.com/scala/scala) and [scala-js](https://github.com/scala-js/scala-js). +for [scala](https://github.com/scala/scala), [scala-native](https://github.com/scala-native/scala-native) +and [scala-js](https://github.com/scala-js/scala-js). * Extensible accessor API. Serialize any type you want. * Provides a useful JS-like AST for intermediate JSON data. * Implicit [accessors](./docs/ACCESSORS.md) that chain to resolve Higher-Kind types (e.g. ```Option[T]```). @@ -29,12 +30,15 @@ for [scala](https://github.com/scala/scala) and [scala-js](https://github.com/sc * Enables use of normal looking scala structures with any previously existing JSON API. * Produces pretty and human readable JSON from normal scala types. * Supports [scala-js](https://github.com/scala-js/scala-js) so you can extend your models to the web. +* Supports [scala-native](https://github.com/scala-native/scala-native) so you can take your models everywhere else. + (requires [jansson](https://github.com/akheron/jansson)- available through apt, brew, etc) * Enables you to create readable APIs that match existing/specific class structure. * Exposes rich compile-time type info, more run-time type data than reflect could ever provide. * Uses existing scala collection CanBuildFrom factories to support buildable collections. * Provides support for unknown types (Any) via 'pickling' with a run-time class [registry](./docs/REGISTRY.md). * Support for scala 2.10.x, 2.11.x, 2.12.0-M3. * Support for scala-js 0.6.x. +* Support for scala-native 0.1.x. Docs @@ -57,7 +61,7 @@ resolvers += "mmreleases" at "https://artifactory.mediamath.com/artifactory/libs //scala libraryDependencies += "com.mediamath" %% "scala-json" % "__VER__" -//or scala + scala-js +//or scala + scala-js/scala-native libraryDependencies += "com.mediamath" %%% "scala-json" % "__VER__" //for @accessor annotation support @@ -70,8 +74,10 @@ Dependencies * [macro-paradise](http://docs.scala-lang.org/overviews/macros/paradise.html) 2.1.0+ required for @accessor annotation * [jackson](https://github.com/FasterXML/jackson) for JVM JSON string parsing +* [jansson](https://github.com/akheron/jansson) for Scala Native JSON string parsing * [µTest](https://github.com/lihaoyi/utest) for testing * [tut](https://github.com/tpolecat/tut) for doc building [Contributing](./docs/CONTRIBUTING.md) --- + diff --git a/native/src/main/scala/json/internal/JanssonDeserializer.scala b/native/src/main/scala/json/internal/JanssonDeserializer.scala new file mode 100644 index 0000000..fff91de --- /dev/null +++ b/native/src/main/scala/json/internal/JanssonDeserializer.scala @@ -0,0 +1,124 @@ +/* + * Copyright 2017 MediaMath, Inc + * + * 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 json.internal + +import scalanative.native + +import janssonConstants._ +import jansson._ +import native._ +import json._ + +object JanssonDeserializer { + def throwError(error: Ptr[json_error_t]): Unit = { + val line = !error._1 + val column = !error._2 + val position = !error._3 + val source = native fromCString error._4.cast[CString] + val text = native fromCString error._5.cast[CString] + + val errString = s"$text. line: $line col: $column pos: $position: $source" + + sys.error(errString) + } + + def parseString(jsonString: String): JValue = { + val error = stackalloc[json_error_t] + + val decoded = json_loads(native toCString jsonString, JSON_DECODE_ANY, error) + + if(decoded == null) + throwError(error) + + val output = nativeToJSON(decoded) + + json_delete(decoded) + + output + } + + def serializeString(str: String): String = { + val encoded = json_string(native toCString str) + + val outNative = json_dumps(encoded, JSON_ENCODE_ANY) + + json_delete(encoded) + + require(outNative != null) + + val out = native fromCString outNative + + native.stdlib.free(outNative) + + out + } + + def nativeToJSON(x: Ptr[json_t]): JValue = { + val typ: json_type = !x._1 + + typ match { + case JSON_OBJECT => + //TODO: is this messing with ordering? + JObject.empty ++ ObjectIterator(x) + case JSON_ARRAY => + JArray.empty ++ ArrayIterator(x) + case JSON_STRING => + JString(native fromCString json_string_value(x)) + case JSON_INTEGER => + JNumber(json_number_value(x)) + case JSON_REAL => + JNumber(json_number_value(x)) + case JSON_TRUE => + JTrue + case JSON_FALSE => + JFalse + case JSON_NULL => + JNull + } + } + + case class ObjectIterator(obj: Ptr[json_t]) extends Iterator[(String, JValue)] { + private var itr = json_object_iter(obj) + + def hasNext: Boolean = itr != null + + def next(): (String, JValue) = { + val key: CString = json_object_iter_key(itr) + val value: Ptr[json_t] = json_object_iter_value(itr) + + itr = json_object_iter_next(obj, itr) + + (native fromCString key, nativeToJSON(value)) + } + } + + case class ArrayIterator(arr: Ptr[json_t]) extends Iterator[JValue] { + private var idx = 0 + + override val size = json_array_size(arr).toInt + + def hasNext: Boolean = idx < size + + def next(): JValue = { + val res = nativeToJSON(json_array_get(arr, idx)) + + idx += 1 + + res + } + } +} diff --git a/native/src/main/scala/json/internal/jansson.scala b/native/src/main/scala/json/internal/jansson.scala new file mode 100644 index 0000000..28b3e1e --- /dev/null +++ b/native/src/main/scala/json/internal/jansson.scala @@ -0,0 +1,101 @@ +/* + * Copyright 2017 MediaMath, Inc + * + * 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 json.internal + +import scala.scalanative.native +import native._ +import Nat._ + +@native.link("jansson") +@native.extern +object jansson { + type JSON_ERROR_TEXT_LENGTH = Digit[_1, Digit[_6, _0]] + type JSON_ERROR_SOURCE_LENGTH = Digit[_8, _0] + + type json_type = CInt //enum + + type json_error_t = CStruct5[ + CInt, //line + CInt, //column + CInt, //position, + CArray[CChar, JSON_ERROR_SOURCE_LENGTH], //source + CArray[CChar, JSON_ERROR_TEXT_LENGTH] //text + ] + + type json_t = CStruct2[ + json_type, //type + CSize //refcount + ] + + type json_iter = Ptr[Byte] + + def json_loads(input: CString, flags: CSize, error: Ptr[json_error_t]): Ptr[json_t] = extern + + def json_dumps(json: Ptr[json_t], flags: CSize): CString = extern + + def json_delete(json: Ptr[json_t]): Unit = extern + + def json_array_size(json: Ptr[json_t]): CSize = extern + + def json_array_get(json: Ptr[json_t], index: CSize): Ptr[json_t] = extern + + def json_object_iter(obj: Ptr[json_t]): json_iter = extern + + def json_object_iter_at(obj: Ptr[json_t], key: CString): json_iter = extern + + def json_object_iter_next(obj: Ptr[json_t], itr: json_iter): json_iter = extern + + def json_object_iter_key(itr: json_iter): CString = extern + + def json_object_iter_value(itr: json_iter): Ptr[json_t] = extern + + def json_number_value(num: Ptr[json_t]): Double = extern + + def json_string(value: CString): Ptr[json_t] = extern + + def json_string_value(str: Ptr[json_t]): CString = extern +} + +object janssonConstants { + //json_type + val JSON_OBJECT = 0 + val JSON_ARRAY = 1 + val JSON_STRING = 2 + val JSON_INTEGER = 3 + val JSON_REAL = 4 + val JSON_TRUE = 5 + val JSON_FALSE = 6 + val JSON_NULL = 7 + + //flags + val JSON_REJECT_DUPLICATES = 0x1 + val JSON_DISABLE_EOF_CHECK = 0x2 + val JSON_DECODE_ANY = 0x4 + val JSON_DECODE_INT_AS_REAL = 0x8 + val JSON_ALLOW_NUL = 0x10 + + val JSON_MAX_INDENT = 0x1F + def JSON_INDENT(n: Int) = ((n) & JSON_MAX_INDENT) + val JSON_COMPACT = 0x20 + val JSON_ENSURE_ASCII = 0x40 + val JSON_SORT_KEYS = 0x80 + val JSON_PRESERVE_ORDER = 0x100 + val JSON_ENCODE_ANY = 0x200 + val JSON_ESCAPE_SLASH = 0x400 + def JSON_REAL_PRECISION(n: Int) = (((n) & 0x1F) << 11) + val JSON_EMBED = 0x10000 +} \ No newline at end of file diff --git a/native/src/main/scala/json/shadow/VMContext.scala b/native/src/main/scala/json/shadow/VMContext.scala new file mode 100644 index 0000000..db73c46 --- /dev/null +++ b/native/src/main/scala/json/shadow/VMContext.scala @@ -0,0 +1,94 @@ +/* + * Copyright 2017 MediaMath, Inc + * + * 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 json.shadow + +import json._ +import json.internal.DefaultVMContext.PrimitiveArray +import json.internal.PrimitiveJArray.Builder +import json.internal.{JanssonDeserializer, PrimitiveJArray, SimpleStringBuilder, BaseVMContext} + +import scala.collection.immutable.StringOps +import scala.collection.mutable +import scala.reflect.ClassTag + +object VMContext extends BaseVMContext { + def newVMStringBuilder: SimpleStringBuilder = new SimpleStringBuilder { + val builder = new StringBuilder(128) + + def append(str: String): internal.SimpleStringBuilder = { + builder append str + this + } + + def append(char: Char): SimpleStringBuilder = { + builder.append(char) + this + } + + def ensureCapacity(cap: Int): Unit = builder.ensureCapacity(cap) + + def result(): String = builder.result() + } + + //TODO: do these need to be specialized? + def createPrimitiveArray[/*@specialized */T: ClassTag](length: Int): PrimitiveArray[T] = + wrapPrimitiveArray(new Array[T](length)) + + def wrapPrimitiveArray[/*@specialized */T: ClassTag](from: Array[T]): PrimitiveArray[T] = from + + def fromString(str: String): JValue = { + JanssonDeserializer.parseString(str) + } + + def fromAny(value: Any): JValue = JValue.fromAnyInternal(value) + + final def quoteJSONString(string: String, sb: SimpleStringBuilder): SimpleStringBuilder = { + require(string != null) + + sb.ensureCapacity(string.length) + + sb.append(JanssonDeserializer.serializeString(string)) + + sb + } + + def newJValueFromArray(arr: Array[_]): JArray = { + import json.accessors._ + + arr match { + case x: Array[Byte] => new PrimitiveJArray[Byte](wrapPrimitiveArray(x)) + case x: Array[Short] => new PrimitiveJArray[Short](wrapPrimitiveArray(x)) + case x: Array[Int] => new PrimitiveJArray[Int](wrapPrimitiveArray(x)) + case x: Array[Long] => new PrimitiveJArray[Long](wrapPrimitiveArray(x)) + case x: Array[Double] => new PrimitiveJArray[Double](wrapPrimitiveArray(x)) + case x: Array[Float] => new PrimitiveJArray[Float](wrapPrimitiveArray(x)) + case x: Array[Boolean] => new PrimitiveJArray[Boolean](wrapPrimitiveArray(x)) + } + } + + def extractPrimitiveJArray[T: ClassTag: PrimitiveJArray.Builder](x: Iterable[T]): Option[JArray] = { + val builder = implicitly[PrimitiveJArray.Builder[T]] + + x match { + case x: mutable.WrappedArray[T] => Some(newJValueFromArray(x.array)) + case x: IndexedSeq[T] => Some(builder.createFrom(x)) + case _ => None + } + } +} + + diff --git a/project/ScalaJSON.scala b/project/ScalaJSON.scala index 6cdd5e2..252a39b 100644 --- a/project/ScalaJSON.scala +++ b/project/ScalaJSON.scala @@ -45,7 +45,7 @@ object ScalaJSON { val genDocsTaskReal = TaskKey[Seq[File]]("gen-docs-real") val genDocsTaskNil = TaskKey[Seq[File]]("gen-docs-nil") - val targetScalaVer = "2.11.7" + val targetScalaVer = "2.11.8" val baseSettings = Seq( scalaVersion := targetScalaVer, diff --git a/project/build.properties b/project/build.properties index 054fb97..571e0b7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -14,5 +14,5 @@ # limitations under the License. # -sbt.version=0.13.7 +sbt.version=0.13.13 diff --git a/project/plugins.sbt b/project/plugins.sbt index 74c3eda..ca8a66f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,4 +5,7 @@ resolvers += Resolver.url( addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.3.2") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.7") \ No newline at end of file +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.14") +addSbtPlugin("org.scala-native" % "sbt-crossproject" % "0.1.0") // (1) +addSbtPlugin("org.scala-native" % "sbt-scalajs-crossproject" % "0.1.0") // (2) +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.1.0") // (3) \ No newline at end of file