Skip to content

Commit

Permalink
Merge pull request #22 from DeepnessScience/EXTENSION-CIRCE-JSON
Browse files Browse the repository at this point in the history
Extension Circe JSON
  • Loading branch information
lashchenko authored Jul 22, 2024
2 parents bfcc304 + 100a8a5 commit 4e16f04
Show file tree
Hide file tree
Showing 34 changed files with 680 additions and 46 deletions.
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

0 comments on commit 4e16f04

Please sign in to comment.