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

Extension Circe JSON #22

Merged
merged 8 commits into from
Jul 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
6 changes: 3 additions & 3 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '17'
distribution: 'temurin'
cache: 'sbt'
- name: Run tests
Expand Down
5 changes: 4 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ rewrite.rules = [Imports]
rewrite.imports.sort = ascii
rewrite.imports.expand = true
rewrite.imports.groups = [
["io.github.greenleafoss\\..*"],
["io.github.greenleafoss.mongo.core\\..*"],
["io.github.greenleafoss.mongo.spray\\..*"],
["io.github.greenleafoss.mongo.play\\..*"],
["io.github.greenleafoss.mongo.circe\\..*"],
["org.mongodb\\..*"],
["org.bson\\..*"],
["java\\..*"],
Expand Down
112 changes: 96 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
[![green-leaf-mongo-core](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-core/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-core)
[![green-leaf-mongo-spray](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-spray/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-spray)
[![green-leaf-mongo-play](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-play/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-play)
[![green-leaf-mongo-circe](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-circe/latest-by-scala-version.svg)](https://index.scala-lang.org/greenleafoss/green-leaf-mongo/green-leaf-mongo-circe)


## Short description
This extension created on top of official [MongoDB Scala Driver](https://mongodb.github.io/mongo-scala-driver) and allows to fully utilize [Spray JSON](https://github.com/spray/spray-json) or [Play JSON](https://github.com/playframework/play-json) to represent bidirectional serialization for case classes in BSON, as well as flexible DSL for [MongoDB query operators](https://www.mongodb.com/docs/manual/reference/operator/query/), documents and collections.
This extension created on top of official [MongoDB Scala Driver](https://mongodb.github.io/mongo-scala-driver) and allows to fully utilize [Spray JSON](https://github.com/spray/spray-json), [Play JSON](https://github.com/playframework/play-json) or [Circe JSON](https://circe.github.io/circe/) to represent bidirectional serialization for case classes in BSON, as well as flexible DSL for [MongoDB query operators](https://www.mongodb.com/docs/manual/reference/operator/query/), documents and collections.

It was introduced in 2019 - [Andrii Lashchenko at #ScalaUA - Spray JSON and MongoDB Queries: Insights and Simple Tricks
](https://www.youtube.com/watch?v=NBgKkQtydAo)
Expand All @@ -25,45 +26,85 @@ Related slides available at https://www.slideshare.net/lashchenko/spray-json-and
// `green-leaf-mongo-core` can be used if you want to create your own extension

// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-spray
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-spray" % "3.0"
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-spray" % "3.1"

// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-play
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3.0"
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-play" % "3.1"

// https://mvnrepository.com/artifact/io.github.greenleafoss/green-leaf-mongo-circe
libraryDependencies += "io.github.greenleafoss" %% "green-leaf-mongo-circe" % "3.1"
```

## JSON and BSON protocols

`GreenLeafMongoJsonBasicFormats` based on DefaultJsonProtocol from Spray JSON and allows to override predefined JsonFormats to make possible use custom serialization in BSON format.
This trait also includes a few additional JsonFormats for _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_ and _UUID_.

`PlayJsonProtocol` is a related extension for the Play JSON library and `SprayJsonProtocol` for the Spray JSON library.
`CirceJsonProtocol` is a related extension for the Circe JSON library, `PlayJsonProtocol` is for the Play JSON library and `SprayJsonProtocol` for the Spray JSON library.

`SprayBsonProtocol`/`PlayBsonProtocol` extends related JsonProtocols and overrides _Int_, _Long_, _BigDecimal_, _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_, _UUID_ and _Regex_ JSON formats to represent them in related BSON (MongoDB Extended JSON V2) formats https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-extended-json-v2-usage.
`SprayBsonProtocol`/`PlayBsonProtocol`/`CirceBsonProtocol` extends related JsonProtocols and overrides _Int_, _Long_, _BigDecimal_, _LocalDate_, _LocalDateTime_, _ZonedDateTime_, _ObjectId_, _scala Enumeration_, _UUID_ and _Regex_ JSON formats to represent them in related BSON (MongoDB Extended JSON V2) formats https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-extended-json-v2-usage.

These base protocols allow to simply (de)serialize this instance to and from both JSON and BSON the same way as in Spray JSON:
```scala 3
// MODEL
case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime)
final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime)

// JSON
trait TestJsonProtocol extends SprayJsonProtocol:
given testJsonFormat = jsonFormat5(Test)
given testJsonFormat: JsonFormat[Test] = jsonFormat5(Test)

object TestJsonProtocol extends TestJsonProtocol

// BSON
object TestBsonProtocol extends TestJsonProtocol with SprayBsonProtocol
```
or Play JSON
```scala 3
// MODEL
final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime)

// JSON
trait TestJsonProtocol extends PlayJsonProtocol:
given testJsonFormat: Format[Test] = Json.format[Test]

object TestJsonProtocol extends TestJsonProtocol

// BSON
object TestBsonProtocol extends TestJsonProtocol with PlayBsonProtocol
```
or Circe
```scala 3
// MODEL
final case class Test(_id: ObjectId, i: Int, l: Long, b: Boolean, zdt: ZonedDateTime)

// JSON
trait TestJsonProtocol extends CirceJsonProtocol:
given testJsonFormat: Codec[Test] = deriveCodec

object TestJsonProtocol extends TestJsonProtocol

// BSON
object TestBsonProtocol extends TestJsonProtocol with CirceBsonProtocol
```

Once protocols defined, we can make instance of Test case class and use TestJsonProtocol to print related JSON:
```scala
val obj = Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01")
Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01T00:00:00Z".parseZonedDateTime)

// Spray JSON
import TestJsonProtocol.given
println(obj.toJson.prettyPrint)

// Play JSON
import TestJsonProtocol.given
println(Json.prettyPrint(Json.toJson(obj)))

// Circe JSON
import TestJsonProtocol.given
println(obj.asJson)
```
Output in this case will be:
```js
```json
{
"_id": "5c72b799306e355b83ef3c86",
"i": 1,
Expand All @@ -76,14 +117,23 @@ Output in this case will be:
Changing single line of import `TestJsonProtocol` to `TestBsonProtocol` allows us to (de)serialize this instance to and from BSON:

```scala
val obj = Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01")
Test(new ObjectId("5c72b799306e355b83ef3c86"), 1, 0x123456789L, true, "1970-01-01T00:00:00Z".parseZonedDateTime)

// Spray JSON
import TestBsonProtocol.given
println(obj.toJson.prettyPrint)

// Play JSON
import TestBsonProtocol.given
println(Json.prettyPrint(Json.toJson(obj)))

// Circe JSON
import TestBsonProtocol.given
println(obj.asJson)
```

Output in this case will be:
```js
```json
{
"_id": {
"$oid": "5c72b799306e355b83ef3c86"
Expand All @@ -103,27 +153,57 @@ Output in this case will be:
}
```

More examples available in implementation of **JsonProtocolSpec**/**BsonProtocolSpec** in Spray and Play project modules.
More examples available in implementation of **JsonProtocolSpec**/**BsonProtocolSpec** in Spray, Play and Circe project modules.

## GreenLeafMongoDsl
`GreenLeafMongoFilterOps` makes it possible to write queries with a syntax that is more close to real queries in MongoDB, as was implemented in [Casbah Query DSL](http://mongodb.github.io/casbah/3.1/reference/query_dsl/).

```scala
"size" $all ("S", "M", "L")
"size" $all Seq("S", "M", "L")
// {"size": {"$all": ["S", "M", "L"]}}

"price" $eq 10
// {"price": {"$eq": 10}}

"price" $gt 10
// {"price": {"$gt": 10}}

"price" $gte 10
"size" $in ("S", "M", "L")
// {"price": {"$gte": 10}}

"size" $in Seq("S", "M", "L")
// {"size": {"$in": ["S", "M", "L"]}}

"price" $lt 100
// {"price": {"$lt": 100}}

"price" $lte 100
// {"price": {"$lte": 100}}

"price" $ne 1000
"size" $nin ("S", "XXL")
// {"price": {"$ne": 1000}}

"size" $nin Seq("S", "XXL")
// {"size": {"$nin": ["S", "XXL"]}}

$or( "price" $lt 5, "price" $gt 1, "promotion" $eq true )
// {"$or": [{"price": {"$lt": 5}}, {"price": {"$gt": 1}}, {"promotion": {"$eq": true}}]}

$and( "price" $lt 5, "price" $gt 1, "stock" $gte 1 )
"price" $not { $gte (5.1) }
// {"$and": [{"price": {"$lt": 5}}, {"price": {"$gt": 1}}, {"stock": {"$gte": 1}}]}

"price" $not { $gte (5.1) }
// {"price": {"$not": {"$gte": 5.1}}}

$nor( "price" $eq 1.99 , "qty" $lt 20, "sale" $eq true )
// {"$nor": [{"price": {"$eq": 1.99}}, {"qty": {"$lt": 20}}, {"sale": {"$eq": true}}]}

"qty" $exists true
// {"qty": {"$exists": true}}

"results" $elemMatch $and("product" $eq "xyz", "score" $gte 8)
// {"results": {"$elemMatch": {"$and": [{"product": {"$eq": "xyz"}}, {"score": {"$gte": 8}}]}}}

// ...
```

Expand Down
26 changes: 17 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
// **************************************************

lazy val commonSettings = Seq(
version := "3.0",
version := "3.1",
description :=
"""
|This extension created on top of official MongoDB Scala Driver.
|It allows to fully utilize Spray JSON and represents bidirectional serialization for case classes in BSON,
|It allows to fully utilize Spray, Play or Circe JSON and represents bidirectional serialization for case classes in BSON,
|as well as flexible DSL for MongoDB query operators, documents and collections.
|""".stripMargin,
licenses := List("Apache 2" -> new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")),
Expand Down Expand Up @@ -50,11 +50,11 @@ lazy val commonSettings = Seq(
scalafmtOnCompile := true,
Test / parallelExecution := false,
Test / fork := true,
libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.7",
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.7" % Test,
libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.12",
libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.13" % Test,
libraryDependencies += "de.flapdoodle.embed" % "de.flapdoodle.embed.mongo" % "3.5.4" % Test,
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test,
libraryDependencies += "org.immutables" % "value" % "2.9.2" % Test
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.18" % Test,
libraryDependencies += "org.immutables" % "value" % "2.10.1" % Test
)

// **************************************************
Expand All @@ -64,7 +64,7 @@ lazy val commonSettings = Seq(
lazy val core = (project in file("core"))
.settings(name := "green-leaf-mongo-core")
.settings(commonSettings)
.settings(libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "4.9.0" cross CrossVersion.for3Use2_13)
.settings(libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "5.1.2" cross CrossVersion.for3Use2_13)

lazy val spray = (project in file("spray"))
.settings(name := "green-leaf-mongo-spray")
Expand All @@ -75,10 +75,18 @@ lazy val spray = (project in file("spray"))
lazy val play = (project in file("play"))
.settings(name := "green-leaf-mongo-play")
.settings(commonSettings)
.settings(libraryDependencies += "com.typesafe.play" %% "play-json" % "2.10.1")
.settings(libraryDependencies += "com.typesafe.play" %% "play-json" % "2.10.5")
.dependsOn(core % "compile->compile;test->test")

lazy val extensions: Seq[ProjectReference] = List[ProjectReference](spray, play)
lazy val circe = (project in file("circe"))
.settings(name := "green-leaf-mongo-circe")
.settings(commonSettings)
.settings(libraryDependencies += "io.circe" %% "circe-core" % "0.14.7")
.settings(libraryDependencies += "io.circe" %% "circe-generic" % "0.14.7")
.settings(libraryDependencies += "io.circe" %% "circe-parser" % "0.14.7")
.dependsOn(core % "compile->compile;test->test")

lazy val extensions: Seq[ProjectReference] = List[ProjectReference](spray, play, circe)
lazy val aggregated: Seq[ProjectReference] = List[ProjectReference](core) ++ extensions

lazy val root = (project in file("."))
Expand Down
Loading
Loading