🔍 fix: correct the last three findings from the page review
Of 26 findings raised across five auditors, 23 were refuted on a second
pass. These three survived.
**A renamed property's old key is still writable.** The README, the landing
page and two doc comments all said that once a property carries
@JsonProperty its original name "is no longer accepted on input". It is no
longer *mapped* — but it is not rejected either. Unlike @JsonReadOnly,
whose JSON name goes into the blocked set, the old key falls through to the
unknown-key policy, and the default `allow` copies it onto the instance
untouched. Reproduced against dist:
@JsonProperty('home_address') @JsonType(() => Addr) @ValidateNested()
address!: Addr;
toInstanceSync(Order, { address: { city: 'Paris' } })
-> address is a plain object, instanceof Addr === false
-> validateSync() returns [] <- nothing complains
-> round-trips out as home_address <- silently accepted
Blocking the old key would fix it, but would also swallow the
`unknownKeys: 'error'` report a strict caller gets today, which is arguably
the more useful signal. That is a judgement call the library has not made,
so this commit states the behaviour accurately everywhere it was stated
wrongly and pins it with five tests covering the default, `strip`, `error`
and the @JsonAlias fix — so it cannot drift either way while the question
is open.
**@IsNotEmpty and @IsEmpty are not complements.** `[]` and `{}` pass BOTH:
isNotEmpty checks only null/undefined/'' while isEmpty also treats empty
arrays and objects as empty. Listed one line apart as "must not be empty" /
"must be empty", they invited exactly the wrong inference.
**unknownKeys is deserialization-only**, in a group whose blurb says these
apply per call or via configure().
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
This commit is contained in:
+7
-2
@@ -813,8 +813,13 @@ npm install ../cereale/cereale-0.3.0.tgz</code></pre>
|
||||
<div class="notes">
|
||||
<div class="note-item">
|
||||
<h3>Renaming is not backwards-compatible by itself</h3>
|
||||
<p>Once a property carries <code class="inline-code">@JsonProperty</code>, its original name is
|
||||
no longer accepted on input. Add <code class="inline-code">@JsonAlias</code> to keep older clients working.</p>
|
||||
<p>Once a property carries <code class="inline-code">@JsonProperty</code>, its original name
|
||||
no longer <em>maps</em> to it — but under the default
|
||||
<code class="inline-code">unknownKeys: 'allow'</code> it is not rejected either. It is
|
||||
copied onto the instance raw, skipping any
|
||||
<code class="inline-code">@JsonType</code> or <code class="inline-code">@JsonDeserialize</code>
|
||||
conversion declared for that field. Add <code class="inline-code">@JsonAlias</code> to keep
|
||||
older clients working, or <code class="inline-code">unknownKeys: 'strip'</code> to drop them.</p>
|
||||
</div>
|
||||
<div class="note-item">
|
||||
<h3><code class="inline-code">abstract</code> and <code class="inline-code">accessor</code> fields cannot be decorated</h3>
|
||||
|
||||
+3
-3
@@ -132,8 +132,8 @@
|
||||
["@IsDate()", 'must be a valid Date object'],
|
||||
["@IsObject()", 'must be an object'],
|
||||
["@IsDefined()", 'must not be null or undefined'],
|
||||
["@IsNotEmpty()", 'must not be empty'],
|
||||
["@IsEmpty()", 'must be empty']
|
||||
["@IsNotEmpty()", 'must not be null, undefined or an empty string — [] and {} pass'],
|
||||
["@IsEmpty()", 'must be null, undefined, an empty string, [] or {}']
|
||||
]],
|
||||
['Numbers', 'Constraints on number fields.', [
|
||||
["@Min(n)", 'must be at least n'],
|
||||
@@ -218,7 +218,7 @@
|
||||
['Configuration', 'Per call, or once via configure().', [
|
||||
["validate: boolean", 'validate while mapping — default true'],
|
||||
["namingStrategy: strategy", 'identity (default), camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, kebab-case, or your own function'],
|
||||
["unknownKeys: policy", 'allow (default), strip, or error'],
|
||||
["unknownKeys: policy", 'allow (default), strip, or error — deserialization only'],
|
||||
["maxDepth: number", 'nesting limit before a JsonMappingError — default 64'],
|
||||
["configure(options)", 'sets the library-wide defaults'],
|
||||
["getConfig()", 'reads the defaults currently in force'],
|
||||
|
||||
Reference in New Issue
Block a user