Jsentric
Json contract patterns, validation, lenses and query
Install / Use
/learn @HigherState/JsentricREADME
jsentric
Json contract patterns, validation, lenses and query
"io.higherstate" %% "jsentric" % "1.0.0"
resolvers ++= Seq(
"Sonatype releases" at "http://oss.sonatype.org/content/repositories/releases/",
)
jsentric is built upon argonaut and is designed to facilitate the use of the basic json datatypes in cases where we have partially dynamic data or are regularly moving through bounded context and may not wish to constantly serialize/deserialize from class objects.
jsentric works by describing a singleton contract which represents data we might wish to extract from the json data structure. By doing so, we get easy validation, lenses and even a type safe mongo db query generator.
/*define a contract,
\ \? \! expected, optional, default properties
\: \:? \:! expected, optional, default array properties
\\ \\? expected, option object properties
*/
object Order extends Contract {
val firstName = \[String]("firstName", nonEmptyOrWhiteSpace)
val lastName = \[String]("lastName", nonEmptyOrWhiteSpace)
val orderId = \?[Int]("orderId", reserved && immutable)
val email = new \\("email") {
val friendlyName = \?[String]("friendlyName")
val address = \[String]("address")
}
val status = \?[String]("status", in("pending", "processing", "sent") && reserved)
val notes = \?[String]("notes", internal)
val orderLines = \:[(String, Int)]("orderLines", forall(custom[(String, Int)](ol => ol._2 >= 0, "Cannot order negative items")))
import Composite._
//Combine properties to make a composite pattern matcher
lazy val fullName = firstName @: lastName
}
import argonaut._
//Create a new Json object
val newOrder = Order.$create{o =>
o.firstName.$set("John") ~
o.lastName.$set("Smith") ~
o.email.address.$set("johnSmith@test.com") ~
o.orderLines.$append("Socks" -> 3)
}
//validate a new json object
val validated = Order.$validate(newOrder)
//pattern match property values
newOrder match {
case Order.email.address(email) && Order.email.friendlyName(Some(name)) =>
println(s"$email <$name>")
case Order.email.address(email) && Order.fullName(firstName, lastName) =>
println(s"$email <$firstName $lastName>")
}
//make changes to the json object.
val pending =
Order{o =>
o.orderId.$set(123) ~
o.status.$set("pending") ~
o.notes.$modify(maybe => Some(maybe.foldLeft("Order now pending.")(_ + _)))
}(newOrder)
//strip out any properties marked internal
val sendToClient = Order.$sanitize(pending)
//generate query json
val relatedOrdersQuery = Order.orderId.$gt(56) && Order.status.$in("processing", "sent")
//experimental convert to postgres jsonb clause
val postgresQuery = QueryJsonb("data", relatedOrdersQuery)
import scalaz.{\/, \/-}
//create a dynamic property
val dynamic = Order.$dynamic[\/[String, Int]]("age")
sendToClient match {
case dynamic(Some(\/-(ageInt))) =>
println(ageInt)
case _ =>
}
val statusDelta = Order.$create(_.status.$set("processing"))
//validate against current state
Order.$validate(statusDelta, pending)
//apply delta to current state
val processing = pending.delta(statusDelta)
//Define subcontract for reusable or recursive structures
trait UserTimestamp extends SubContract {
val account = \[String]("account")
val timestamp = \[Long]("timestamp")
}
object Element extends Contract {
val created = new \\("created", immutable) with UserTimestamp
val modified = new \\("modified") with UserTimestamp
}
//try to force a match even if wrong type
import LooseCodecs._
Json("orderId" := "23628") match {
case Order.orderId(Some(id)) => id
}
*Auto generation of schema information is still a work in progress
*mongo query is not a full feature set.
Related Skills
node-connect
353.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
353.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
353.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
