jsonPointer
Builds the JSON Pointer (RFC 6901) that identifies property in T's serialized form, in the URI fragment notation RFC 9457's own example uses: #/age.
Preferred over writing the pointer as a string, because renaming the property is then a compile error rather than a pointer that silently names a member no longer there.
The property's own name is the wire name only when nothing renames it. If the class carries @SerialName on that property, or the application's Json applies a JsonNamingStrategy, this function cannot see it — the mapping from a Kotlin property to a serial name is not reachable without kotlin-reflect, which this library does not use. @SerialName is at least detected: the name is checked against T's serial descriptor and an unknown one throws. A naming strategy is not detectable at all, and a project using one should build its pointers from strings instead.
Throws
if T's serialized form has no member under the property's name.
Samples
install(RequestValidation) {
validate<Customer> { customer ->
val errors =
buildList {
if (customer.age <= 0) {
add(jsonPointer(Customer::age) to "must be a positive integer")
}
// No property reference reaches a nested member, but every segment of the path is
// still checked against the descriptor it belongs to.
if (customer.profile.color !in setOf("green", "red", "blue")) {
add(jsonPointer<Customer>("profile", "color") to "must be 'green', 'red' or 'blue'")
}
// `@SerialName` renames this one, so the pointer has to name what the wire
// carries. `jsonPointer(Customer::emailAddress)` would throw rather than guess.
if ("@" !in customer.emailAddress) {
add(jsonPointer<Customer>("email_address") to "must be an email address")
}
}
if (errors.isEmpty()) ValidationResult.Valid else invalidFields(errors)
}
}
install(StatusPages) {
problemDetails {
requestValidation(ValidationError)
}
}
routing {
post("/customers") {
call.respond(call.receive<Customer>())
}
}Builds the JSON Pointer (RFC 6901) for path in T's serialized form, in the URI fragment notation RFC 9457's own example uses: jsonPointer<Customer>("profile", "color") gives #/profile/color.
Every segment is checked against the serial descriptor it belongs to, so the whole path has to exist. Use this form for a nested member, for a member renamed by @SerialName — the segments are serial names, which is what the wire carries — and wherever the path is assembled rather than written out. Segments are escaped for you: ~ and / per RFC 6901 §3, then whatever a URI fragment does not admit is percent-encoded.
A list segment is its index, or - for the position past the end, as RFC 6901 §4 defines. Under a map, any segment is accepted, since map keys are not known statically. Under a polymorphic or contextual member the shape is unknown, so checking stops there and the rest of the path is taken on trust.
Throws
if path is empty, or if a segment names nothing in the descriptor it is resolved against.
Samples
install(RequestValidation) {
validate<Customer> { customer ->
val errors =
buildList {
if (customer.age <= 0) {
add(jsonPointer(Customer::age) to "must be a positive integer")
}
// No property reference reaches a nested member, but every segment of the path is
// still checked against the descriptor it belongs to.
if (customer.profile.color !in setOf("green", "red", "blue")) {
add(jsonPointer<Customer>("profile", "color") to "must be 'green', 'red' or 'blue'")
}
// `@SerialName` renames this one, so the pointer has to name what the wire
// carries. `jsonPointer(Customer::emailAddress)` would throw rather than guess.
if ("@" !in customer.emailAddress) {
add(jsonPointer<Customer>("email_address") to "must be an email address")
}
}
if (errors.isEmpty()) ValidationResult.Valid else invalidFields(errors)
}
}
install(StatusPages) {
problemDetails {
requestValidation(ValidationError)
}
}
routing {
post("/customers") {
call.respond(call.receive<Customer>())
}
}