Created
June 26, 2025 13:13
-
-
Save mhewedy/44a3c60ed1a7c58ffd8866a271444914 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.mongodb.expressions | |
import kotlinx.serialization.Serializable | |
import kotlinx.serialization.json.* | |
import java.time.LocalDate | |
import java.time.LocalDateTime | |
/** | |
* MongoDB Expression Query Builder for Kotlin Multiplatform | |
* | |
* This builder creates MongoDB-style JSON queries compatible with | |
* spring-data-jpa-mongodb-expressions library. | |
* | |
* Usage example: | |
* ```kotlin | |
* val query = mongoQuery { | |
* or { | |
* "lastName" eq "ibrahim" | |
* and { | |
* "firstName" eq "mostafa" | |
* "birthDate" gt "1990-01-01" | |
* } | |
* } | |
* } | |
* | |
* // Converts to: {"$or": [{"lastName": "ibrahim"}, {"$and": [{"firstName": "mostafa"}, {"birthDate": {"$gt": "1990-01-01"}}]}]} | |
* ``` | |
*/ | |
@Serializable | |
data class MongoExpression( | |
val expression: JsonElement | |
) { | |
fun toJsonString(): String = Json.encodeToString(JsonElement.serializer(), expression) | |
fun toMap(): Map<String, Any?> = expression.jsonObject.toMap().mapValues { (_, value) -> | |
when (value) { | |
is JsonPrimitive -> value.contentOrNull ?: value.boolean | |
is JsonObject -> value.jsonObject.toMap() | |
is JsonArray -> value.jsonArray.map { it.toString() } | |
} | |
} | |
} | |
class MongoQueryBuilder { | |
private val conditions = mutableListOf<JsonElement>() | |
// Equality operators | |
infix fun String.eq(value: Any?): JsonElement { | |
val jsonValue = when (value) { | |
null -> JsonNull | |
is String -> JsonPrimitive(value) | |
is Number -> JsonPrimitive(value) | |
is Boolean -> JsonPrimitive(value) | |
is LocalDate -> JsonPrimitive(value.toString()) | |
is LocalDateTime -> JsonPrimitive(value.toString()) | |
else -> JsonPrimitive(value.toString()) | |
} | |
return buildJsonObject { | |
put(this@eq, jsonValue) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.ne(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@ne, buildJsonObject { | |
put("\$ne", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.ieq(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@ieq, buildJsonObject { | |
put("\$ne", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
// Comparison operators | |
infix fun String.gt(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@gt, buildJsonObject { | |
put("\$gt", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.gte(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@gte, buildJsonObject { | |
put("\$gte", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.lt(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@lt, buildJsonObject { | |
put("\$lt", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.lte(value: Any?): JsonElement { | |
return buildJsonObject { | |
put(this@lte, buildJsonObject { | |
put("\$lte", JsonPrimitive(value.toString())) | |
}) | |
}.also { conditions.add(it) } | |
} | |
// Array operators | |
infix fun String.`in`(values: List<Any?>): JsonElement { | |
return buildJsonObject { | |
put(this@`in`, buildJsonObject { | |
put("\$in", JsonArray(values.map { | |
when (it) { | |
null -> JsonNull | |
is String -> JsonPrimitive(it) | |
is Number -> JsonPrimitive(it) | |
is Boolean -> JsonPrimitive(it) | |
else -> JsonPrimitive(it.toString()) | |
} | |
})) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.nin(values: List<Any?>): JsonElement { | |
return buildJsonObject { | |
put(this@nin, buildJsonObject { | |
put("\$nin", JsonArray(values.map { | |
when (it) { | |
null -> JsonNull | |
is String -> JsonPrimitive(it) | |
is Number -> JsonPrimitive(it) | |
is Boolean -> JsonPrimitive(it) | |
else -> JsonPrimitive(it.toString()) | |
} | |
})) | |
}) | |
}.also { conditions.add(it) } | |
} | |
// Pattern matching | |
infix fun String.start(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@start, buildJsonObject { | |
put("\$start", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.istart(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@istart, buildJsonObject { | |
put("\$istart", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.end(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@end, buildJsonObject { | |
put("\$end", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.iend(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@iend, buildJsonObject { | |
put("\$iend", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.contains(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@contains, buildJsonObject { | |
put("\$contains", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
infix fun String.icontains(pattern: String): JsonElement { | |
return buildJsonObject { | |
put(this@icontains, buildJsonObject { | |
put("\$icontains", JsonPrimitive(pattern)) | |
}) | |
}.also { conditions.add(it) } | |
} | |
// Logical operators | |
fun and(block: MongoQueryBuilder.() -> Unit): JsonElement { | |
val builder = MongoQueryBuilder() | |
builder.block() | |
return buildJsonObject { | |
put("\$and", JsonArray(builder.conditions)) | |
}.also { conditions.add(it) } | |
} | |
fun or(block: MongoQueryBuilder.() -> Unit): JsonElement { | |
val builder = MongoQueryBuilder() | |
builder.block() | |
return buildJsonObject { | |
put("\$or", JsonArray(builder.conditions)) | |
}.also { conditions.add(it) } | |
} | |
// Build the final expression | |
internal fun build(): JsonElement { | |
return when (conditions.size) { | |
0 -> buildJsonObject { } | |
1 -> conditions.first() | |
else -> buildJsonObject { | |
put("\$and", JsonArray(conditions)) | |
} | |
} | |
} | |
} | |
// DSL function to create MongoDB expressions | |
fun mongoQuery(builder: MongoQueryBuilder.() -> Unit): MongoExpression { | |
val queryBuilder = MongoQueryBuilder() | |
queryBuilder.builder() | |
return MongoExpression(queryBuilder.build()) | |
} | |
fun main() { | |
println(mongoQuery { | |
"firstName" eq "John" | |
"age" gt 25 | |
}.toJsonString()) | |
println(mongoQuery { | |
or { | |
"lastName" eq "ibrahim" | |
and { | |
"firstName" icontains "mostafa" | |
"birthDate" eq "1990-01-01" | |
} | |
} | |
}.toJsonString()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment