Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ujson bridge #401

Merged
merged 8 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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