A library whose headline feature is "JSON mapping" could not map a name: there
was no way to read {"first_name": ...} into firstName, no way to keep a password
out of the response, and no way to parse a payload without validating it.
Name mapping
- @JsonProperty(name) renames a property in both directions
- @JsonAlias(...names) accepts extra names on input only, so a field can be
renamed without breaking older clients
- naming strategies (snake_case, kebab-case, SCREAMING_SNAKE_CASE, PascalCase,
camelCase, or your own function) for properties with no explicit name.
Acronyms split where a reader expects: parseHTTPResponse -> parse_http_response
Access control
- @JsonIgnore() excluded both ways
- @JsonWriteOnly() accepted from input, never echoed back (passwords)
- @JsonReadOnly() serialized, never settable by a client (server-owned ids)
Blocked names are dropped explicitly rather than falling through to the unknown
key path, which would otherwise have copied a rejected id straight back on under
the default policy.
Transform options, per call or globally via configure()
- validate: false to map without validating, for lenient parsing
- unknownKeys: 'allow' | 'strip' | 'error'
- namingStrategy
Error ergonomics — the nested ValidationError tree was hard to turn into an HTTP
400 body. flattenErrors() yields {"items[0].qty": ["qty must be at least 1"]},
plus formatErrors() and collectErrorMessages(). Adds validateOrReject().
All defaults preserve existing behaviour; the 68 prior tests pass unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import type { ValidationError } from './utils.js';
|
|
|
|
/**
|
|
* Flattens the nested {@link ValidationError} tree into a flat map of dotted paths to
|
|
* messages — the shape you actually want when turning a failure into an HTTP 400 body.
|
|
*
|
|
* ```ts
|
|
* flattenErrors(errors);
|
|
* // {
|
|
* // "name": ["name must be a string"],
|
|
* // "items[0].qty": ["qty must be at least 1"]
|
|
* // }
|
|
* ```
|
|
*/
|
|
export function flattenErrors(errors: ValidationError[]): Record<string, string[]> {
|
|
const flat: Record<string, string[]> = {};
|
|
|
|
const walk = (nodes: ValidationError[], prefix: string) => {
|
|
for (const node of nodes) {
|
|
// Array indices read as `items[0]`, named properties as `order.total`.
|
|
const path = node.property.startsWith('[')
|
|
? `${prefix}${node.property}`
|
|
: prefix ? `${prefix}.${node.property}` : node.property;
|
|
|
|
const messages = Object.values(node.constraints);
|
|
if (messages.length > 0) {
|
|
(flat[path] ??= []).push(...messages);
|
|
}
|
|
|
|
if (node.children?.length) {
|
|
walk(node.children, path);
|
|
}
|
|
}
|
|
};
|
|
|
|
walk(errors, '');
|
|
return flat;
|
|
}
|
|
|
|
/**
|
|
* Renders the error tree as human-readable lines, one per failed rule.
|
|
*
|
|
* Intended for logs and CLI output; use {@link flattenErrors} when the destination is JSON.
|
|
*/
|
|
export function formatErrors(errors: ValidationError[]): string {
|
|
const flat = flattenErrors(errors);
|
|
return Object.entries(flat)
|
|
.flatMap(([path, messages]) => messages.map(message => `${path}: ${message}`))
|
|
.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Collects every message in the tree, discarding paths.
|
|
*/
|
|
export function collectErrorMessages(errors: ValidationError[]): string[] {
|
|
return Object.values(flattenErrors(errors)).flat();
|
|
}
|