How to get String representation of ObjectId from response body? - mongodb

I am trying to create a simple API which works with MongoDB documents created from this class:
#Document()
data class Student(#Id val id: ObjectId?,
val firstName: String,
val secondName: String) {}
And I have a REST controller which returns me Student documents.
{
"id": {
"timestamp": 1657005140,
"date": "2022-07-05T07:12:20.000+00:00"
},
"firstName": "Test",
"secondName": "Test"
}
But I also need a controller which returns me documents by id. How can I put this JSON id with timestamp and date in a request param like /getByName?id= ? Maybe there is a way to get an ID in one-string representation?

The problem is that my ObjectId was serialised.
To get simple one-string value I needed just to add: #JsonSerialize(using = ToStringSerializer::class)
So my document looks like this:
#Document()
data class Student(
#Id
#JsonSerialize(using = ToStringSerializer::class)
val id: ObjectId?,
val firstName: String,
val secondName: String) {}

Related

How to optimize MongoDB find query?

I have a mongo read query which looks like following.
data class Resource( val assignmentId: String, val referrerId: String, val studentIds: Set<String>)
fun findSubmissions(resources: List<Resource>): Flux<Submission> {
val criteria = resources.map { (assignmentId, referrerId, students) ->
Criteria.where("assignmentId").`is`(assignmentId).and("referrerId").`is`(referrerId).and("studentId").`in`(students)
}.toTypedArray()
val query = Query().addCriteria(Criteria().orOperator(*criteria))
return template.find(query, Submission::class.java)
}
}
Document schema of Submission looks like this
{
assignmentId: "",
referrerId: "",
studentId: "",
status: ""
}
Looks like this query is affecting the performance and hogging the CPU, which could be because of the way OR clause has been written.
Any ideas how this query can be rewritten in a better way?

Quarkus Panache MongoDB: Serialize ObjectId as String

I'm following the tutorials https://quarkus.io/guides/rest-data-panache and https://quarkus.io/guides/mongodb-panache to implement a simple MongoDB entity and resource with Quarkus Panache MongoDB.
Here's what I have so far:
#MongoEntity(collection = "guests")
class GuestEntity(
var id: ObjectId? = null,
var name: String? = null
)
#ApplicationScoped
class GuestRepository: PanacheMongoRepository<GuestEntity>
interface GuestResource: PanacheMongoRepositoryResource<GuestRepository, GuestEntity, ObjectId>
When running this, I can create a document by calling
POST localhost:8080/guest
Content-Type: application/json
{
"name": "Foo"
}
The response contains the created entity
{
"id": {
"timestamp": 1618306409,
"date": 1618306409000
},
"name": "Foo"
}
Notice, how the id field is an object whereas I would like it to be a string.
It turns out that the application was using quarkus-resteasy instead of quarkus-resteasy-jackson.
Once the proper dependency was in place, everything worked as expected
To serialize the id field as a String, apply the following annotation to the id field:
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.quarkus.mongodb.panache.jackson.ObjectIdSerializer
#MongoEntity(collection = "guests")
class GuestEntity(
// important: apply the annotation to the field
#field:JsonSerialize(using = ObjectIdSerializer::class)
var id: ObjectId? = null,
var name: String
)
Now the response is
{
"id": "607567590ced4472ce95be23",
"name": "Foo"
}

Find entities containing a sublist of elements from an embedded array in MongoDB with Spring Data MongoDB

I have a MongoDB entity that has a list of tags:
#Document
#TypeAlias("project")
data class Project(#Id var id: String?,
val name: String,
val tags: MutableList<Tag>)
I would like to look for Project's containing a subset of the tags.
For example given these proyects:
projectService.save(Project(null, "someProject11",
mutableListOf(Tag("area","IT"), Tag("department","Architecture"))))
projectService.save(Project(null, "someProject12",
mutableListOf(Tag("area","IT"), Tag("department","HR"))))
If I define my repository as follows:
interface ProjectRepository : ReactiveCrudRepository<Project, String> {
fun findByTags(tags : List<Tag>): Flux<Project>
}
Looking for a subset doesn't work, e.g: projects with Tag("area","IT") should return two results but actually returns 0. The underlying mongo query is:
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"}]}
It only works passing the complete content of the list listOf(Tag("area","IT"), Tag("department","Architecture")):
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"},{"key": "department", "value": "Architecture"}]}
How can I query for entities containing a subset of the list?
Solved using Criteria and the $elemMatch operator:
interface CustomProjectRepository {
fun findByTags(tags: List<Tag>): Flux<Project>
}
class CustomProjectRepositoryImpl(private val mongoTemplate: ReactiveMongoTemplate) : CustomProjectRepository {
override fun findByTags(tags: List<Tag>): Flux<Project> {
val query = Query()
val criterias = mutableListOf<Criteria>()
tags.forEach {
criterias.add(Criteria.where("tags").elemMatch(Criteria.where("key").`is`(it.key).and("value").`is`(it.value)))
}
query.addCriteria(Criteria().andOperator(* criterias.toTypedArray()))
return mongoTemplate.find(query, Project::class.java)
}
}
interface ProjectRepository : ReactiveCrudRepository<Project, String>, CustomProjectRepository {
fun findByClientIdAndId(clientId: String, id: String): Mono<Project>
fun findByClientId(clientId: String): Flux<Project>
}

how to retrieve and inject sections from and to JSON

If I have an incoming JSON of following structure
[
{
"personId" : 12,
"name": "John Doe",
"age": 48,
"birthdate": "12/1/1954",
"relationships": [
{
"relationType":"parentOf",
"value" : "Johnny walker"
},
{
"relationType":"sonOf",
"value" : "Charles Smith"
}
]
},
{
"personId" : 13,
"name": "Merry Christmas",
"age": 28,
"birthdate": "12/1/1985",
"relationships": [
{
"relationType":"sisteOf",
"value" : "Will Smith"
},
{
"relationType":"cousinOf",
"value" : "Brad Pitt"
}
]
}
]
And requirement is that for each Person record controller will have to carve out relationships array and store each record from it in a separate relationship table with personId association while persisting this incoming JSON.
And subsequently when querying these persons records system will have to lookup relationships for each person from relationships table and inject them to form the same above looking JSON to give back to UI for rendering.
What's the best efficient way to perform this "carve out" and later "inject" operations using Play framework in Scala? (using Slick in persistent layer APIs) I have looked at this JSON transformation link and json.pickBranch in there but not quite sure if that'll be fully applicable here for "carve out" and "inject" use cases for preparing JSON shown in the example. are there any elegant ideas?
One way, which is pretty straightforward, is to use case classes along with Play Json inceptions
import play.api.libs.json.Json
case class Relationship(relationType: String, value: String)
object Relationship {
implicit val RelationshipFormatter = Json.format[Relationship]
}
case class Person(personId: String, name: String, age: Int, birthdate: String, relationships: Seq[Relationship]) {
def withRelationships(relationship: Seq[Relationship]) = copy(relationships = relationships ++ relationship)
}
object Person {
implicit val PersonFormatter = Json.format[Person]
}
Now you can convert a json value to Person by using the following code, provided that jsValue is a json value of type JsValue (which in play controllers you can get by request.body.asJson):
Json.fromJson[Person](jsValue)
In Play controllers, you can
For converting a Person to json you can use the following code provided that person is a value of type Person in your context:
Json.toJson(person)
The only remaining thing is your Slick schemas which is pretty straight forward.
One option is to use a simple schema for Person, without relations and one schema for Relation with a foreign key to Person table. Then you need to find all relations associated with a specific Person, namely person and then append them to that person by calling the withRelationships method which gives you a new Person which you can serve as json:
val person = .... // find person
val relations = .... // find relationships associated with this person
val json = Json.toJson(person.withRelationships(relations))

GORM get/find resource by ID using MongoDB in Grails

Grails makes it easy to get a domain object by ID (handy for building a REST API).
A controller to retrieve a resource can be as simple as:
MetricController.groovy
import grails.converters.JSON
class MetricController {
def index() {
def resource = Metric.get(params.id)
render resource as JSON
}
}
When using the Grails plugin for MongoDB GORM (compile ":mongodb:1.2.0"), the default id type of Long needs to be changed to type String or ObjectId.
Metric.groovy
import org.bson.types.ObjectId
class Metric {
static mapWith = "mongo"
ObjectId id
String title
}
However, doing a .get(1) will now result in:
Error 500: Internal Server Error
URI
/bow/rest/metric/1
Class
java.lang.IllegalArgumentException
Message
invalid ObjectId [1]
I took a guess and changed the controller to use findById:
def resource = Metric.findById(new ObjectId(new Date(), params.id.toInteger()))
That fixed the error, but it fails to find the object (always returns null).
For example, using the id of "-1387348672" does not find this test object:
{ "class" : "Metric",
"id" : { "class" : "org.bson.types.ObjectId",
"inc" : -1387348672,
"machine" : 805582250,
"new" : false,
"time" : 1371329632000,
"timeSecond" : 1371329632
},
"title" : "Test"
}
The ObjectId.inc field may not even be the correct field to use for the resource ID.
So, what is the simplest way to retrieve a domain object by ID when using MongoDB?
When a domain object is persisted in MongoDB, it is stored as a document with an ObjectId as a unique 12 bytes BSON primary key. For example, if you have a domain object Product like
import org.bson.types.ObjectId
class Product {
ObjectId id
String name
static mapWith = "mongo"
}
then the persisted entity in MongoDB would look like below if you save with a name as "TestProduct".
//db.product.find() in mongodb
{
"_id" : ObjectId("51bd047c892c8bf0b3a58b21"),
"name" : "TestProduct",
"version" : 0
}
The _id becomes your primary key for that document. To get this document you need the primary key ObjectId. Speaking from a RESTful context, you atleast need the 12 bytes hexcode 51bd047c892c8bf0b3a58b21 as part of the request.
So in the above case, you can fetch that particular document by doing something like
Product.get(new ObjectId("51bd047c892c8bf0b3a58b21"))
Product.findById(new ObjectId("51bd047c892c8bf0b3a58b21"))
Have a look at the API for ObjectId which would make clear how to retrieve a document.
When you retrieve the document as JSON, then it just shows the ObjectId class with its elements.
{
"class": "com.example.Product",
"id": {
"class": "org.bson.types.ObjectId",
"inc": -1280996575,
"machine": -1993569296,
"new": false,
"time": 1371341948000,
"timeSecond": 1371341948
},
"name": "TestProduct"
}
For completeness, here's the domain with a controller to get a resource by ID string (instead of ObjectID).
Example:
Metric.get("51bf8ccc30040460f5a05579")
Domain
import org.bson.types.ObjectId
class Metric {
ObjectId id
String title
static mapWith = "mongo"
def out() {
return [
id: id as String, //convert Mongo ObjectId to 12-byte hex BSON
title: title
]
}
}
The out() method is used to render the resource showing its ID string (not its ObjectID).
Controller
import grails.converters.JSON
class MetricController {
def index() {
def resource = Metric.read(params.id)
render resource.out() as JSON
}
}
Example JSON response for /rest/metric/51bf8ccc30040460f5a05579
{ "id": "51bf8ccc30040460f5a05579", "title": "Accounts" }