Skip to content

Commit

Permalink
Merge pull request #401 from esarbe/main
Browse files Browse the repository at this point in the history
add ujson bridge
  • Loading branch information
satabin authored Jan 22, 2024
2 parents 175e5d8 + 721da18 commit c429203
Show file tree
Hide file tree
Showing 14 changed files with 474 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p circe/jvm/target playJson/native/target testkit/native/target testkit/js/target core/.native/target playJson/jvm/target core/.js/target circe/js/target core/.jvm/target circe/native/target playJson/js/target testkit/jvm/target sprayJson/.jvm/target project/target
run: mkdir -p ujson/jvm/target circe/jvm/target playJson/native/target testkit/native/target testkit/js/target core/.native/target playJson/jvm/target ujson/js/target core/.js/target circe/js/target core/.jvm/target circe/native/target ujson/native/target playJson/js/target testkit/jvm/target sprayJson/.jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar circe/jvm/target playJson/native/target testkit/native/target testkit/js/target core/.native/target playJson/jvm/target core/.js/target circe/js/target core/.jvm/target circe/native/target playJson/js/target testkit/jvm/target sprayJson/.jvm/target project/target
run: tar cf targets.tar ujson/jvm/target circe/jvm/target playJson/native/target testkit/native/target testkit/js/target core/.native/target playJson/jvm/target ujson/js/target core/.js/target circe/js/target core/.jvm/target circe/native/target ujson/native/target playJson/js/target testkit/jvm/target sprayJson/.jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
1 change: 1 addition & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ where `jsonLib` is either:
- `spray-json`
- `play-json`
- `circe`
- `ujson` for ujson/upickle

These versions are built for Scala 2.12, 2.13, and 3.

Expand Down
17 changes: 16 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ lazy val commonSettings = Seq(
homepage := Some(url("https://github.com/gnieh/diffson"))
)

lazy val diffson = tlCrossRootProject.aggregate(core, sprayJson, circe, playJson, testkit)
lazy val diffson = tlCrossRootProject.aggregate(core, sprayJson, circe, playJson, ujson, testkit)

lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
Expand Down Expand Up @@ -92,6 +92,21 @@ lazy val circe = crossProject(JSPlatform, JVMPlatform, NativePlatform)
)
.dependsOn(core, testkit % Test)

val ujsonVersion = "3.1.3"
lazy val ujson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Full)
.in(file("ujson"))
.settings(commonSettings: _*)
.settings(
name := "diffson-ujson",
libraryDependencies ++= Seq(
"com.lihaoyi" %%% "ujson" % ujsonVersion,
"com.lihaoyi" %%% "upickle" % ujsonVersion
),
tlVersionIntroduced := Map("3" -> "4.6.0", "2.13" -> "4.6.0", "2.12" -> "4.6.0")
)
.dependsOn(core, testkit % Test)

lazy val benchmarks = crossProject(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("benchmarks"))
Expand Down
20 changes: 20 additions & 0 deletions ujson/shared/src/main/scala/diffson/ujson/FieldMissing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

case class FieldMissing(fieldName: String) extends Exception(s"Expected field `$fieldName`, but it is missing")
190 changes: 190 additions & 0 deletions ujson/shared/src/main/scala/diffson/ujson/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson

import cats.implicits._
import diffson.jsonmergepatch.JsonMergePatch
import diffson.jsonpatch._
import diffson.jsonpointer.Pointer
import _root_.ujson.{Value, Arr, Obj, Str}
import upickle.default._

import scala.util.Try

package object ujson {

implicit val jsonyUjson: Jsony[Value] = new Jsony[Value] {

def Null: Value = _root_.ujson.Null

def array(json: Value): Option[Vector[Value]] =
json.arrOpt.map(_.toVector)

def fields(json: Value): Option[Map[String, Value]] =
json.objOpt.map(_.toMap)

def makeArray(values: Vector[Value]): Value =
Arr.from(values)

def makeObject(fields: Map[String, Value]): Value =
Obj.from(fields)

def show(json: Value): String =
json.render()

def eqv(json1: Value, json2: Value): Boolean = json1 == json2
}

implicit val pointerEncoder: Writer[Pointer] =
implicitly[Writer[String]].comap(a => a.show)

implicit val pointerDecoder: Reader[Pointer] =
implicitly[Reader[String]].map { str =>
Pointer.parse[Try](str).fold(throw _, identity)
}

private val operationAsJson: Operation[Value] => Value = {
case Add(path, value) =>
Obj(
"op" -> Str("add"),
"path" -> Str(path.show),
"value" -> value
)
case Remove(path, Some(old)) =>
Obj(
"op" -> Str("remove"),
"path" -> Str(path.show),
"old" -> old
)
case Remove(path, None) =>
Obj(
"op" -> Str("remove"),
"path" -> Str(path.show)
)
case Replace(path, value, Some(old)) =>
Obj(
"op" -> Str("replace"),
"path" -> Str(path.show),
"value" -> value,
"old" -> old
)
case Replace(path, value, None) =>
Obj(
"op" -> Str("replace"),
"path" -> Str(path.show),
"value" -> value
)
case Move(from, path) =>
Obj(
"op" -> Str("move"),
"from" -> Str(from.show),
"path" -> Str(path.show)
)
case Copy(from, path) =>
Obj(
"op" -> Str("copy"),
"from" -> Str(from.show),
"path" -> Str(path.show)
)
case Test(path, value) =>
Obj(
"op" -> Str("test"),
"path" -> Str(path.show),
"value" -> value
)
}

implicit val operationEncoder: Writer[Operation[Value]] =
implicitly[Writer[Value]].comap(operationAsJson)

private def decodeOperation(value: Value): Operation[Value] = {
def readPointer(value: Value, path: String = "path"): Pointer = {
val serializedPointer =
value.objOpt
.flatMap(_.get(path))
.getOrElse(throw FieldMissing(path))

read[Pointer](serializedPointer)
}

val op =
value.objOpt
.flatMap(_.get("op").flatMap(_.strOpt))
.getOrElse(throw FieldMissing("op"))

def getField(key: String) = {
value.objOpt
.flatMap(_.get(key))
.getOrElse(throw FieldMissing(key))
}

op match {
case "add" =>
val path = readPointer(value)
val v = getField("value")
Add(path, v)

case "remove" =>
val path = readPointer(value)
val old = value.objOpt.flatMap(_.get("old"))
Remove(path, old)
case "replace" =>
val path = readPointer(value)
val payload = getField("value")
val old = value.objOpt.flatMap(_.get("old"))
Replace(path, payload, old)
case "move" =>
val path = readPointer(value)
val from = readPointer(value, "from")
Move(from, path)
case "copy" =>
val path = readPointer(value)
val from = readPointer(value, "from")
Copy(from, path)
case "test" =>
val path = readPointer(value)
val payload = getField("value")
Test(path, payload)
case other =>
throw new Exception(s"Expected operation `$other` but it is missing")
}
}

implicit val operationDecoder: Reader[Operation[Value]] =
implicitly[Reader[Value]].map(decodeOperation)

implicit val jsonPatchEncoder: Writer[JsonPatch[Value]] = {
implicitly[Writer[List[Value]]].comap(_.ops.map(operationAsJson))
}

implicit val jsonPatchDecoder: Reader[JsonPatch[Value]] = {
implicitly[Reader[List[Value]]].map(q => JsonPatch(q.map(decodeOperation)))
}

implicit val jsonMergePatchEncoder: Writer[JsonMergePatch[Value]] = {
implicitly[Writer[Value]].comap(_.toJson)
}

implicit val jsonMergePatchDecoder: Reader[JsonMergePatch[Value]] = {
implicitly[Reader[Value]]
.map { value =>
value.objOpt
.map(obj => JsonMergePatch.Object(obj.toMap))
.getOrElse(JsonMergePatch.Value(value))
}
}
}
23 changes: 23 additions & 0 deletions ujson/shared/src/test/scala/diffson/ujson/UjsonJsonDiff.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

import jsonpatch._
import _root_.ujson._

class UjsonJsonDiff extends TestJsonDiff[Value] with UjsonTestProtocol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

import _root_.ujson._
import jsonmergepatch._

class UjsonJsonJsonMergeDiff extends TestJsonMergeDiff[Value] with UjsonTestProtocol
23 changes: 23 additions & 0 deletions ujson/shared/src/test/scala/diffson/ujson/UjsonSimpleDiff.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

import jsonpatch._
import _root_.ujson._

class UjsonSimpleDiff extends TestSimpleDiff[Value] with UjsonTestProtocol
23 changes: 23 additions & 0 deletions ujson/shared/src/test/scala/diffson/ujson/UjsonTestArrayDiff.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

import jsonpatch._
import _root_.ujson.Value

class UjsonTestArrayDiff extends TestArrayDiff[Value] with UjsonTestProtocol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Lucas Satabin
*
* 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 diffson
package ujson

import jsonmergepatch._
import _root_.ujson._

class UjsonTestJsonMergePatch extends TestJsonMergePatch[Value] with UjsonTestProtocol
Loading

0 comments on commit c429203

Please sign in to comment.