Unknown input is not a book yet
JSON.parse proves syntax, not your application’s data contract. An external value might be null, a number, an array, or a record with wrongly typed fields. Check the container before accessing its contents and check a field’s type before calling string methods.
typeof null is “object”, and arrays are objects too. An object check therefore needs a null check and, when arrays are excluded, Array.isArray. The decoder below accepts an array of records with nonblank string ids and titles. It trims both fields, rejects duplicate cleaned ids, and returns fresh records containing only the accepted fields. A duplicate is an error here, rather than silently overwriting someone’s data.
Rejecting the entire batch is an all-or-nothing policy. It makes failure visible and keeps an existing plan intact. A partial-import policy can also be valid, but it needs a report of rejected rows. Avoid silently filtering away bad data and presenting the reduced list as a complete success.
This is intentionally a small schema. Real service data may contain optional fields, pagination, and versioning. Add those requirements explicitly when needed. Treat validated internal records differently from unknown external values so every inner calculation does not have to repeat the same checks.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
function decode(value) {
if (!Array.isArray(value)) {
throw new Error("Expected a list");
}
const ids = new Set();
return value.map(item => {
if (
item === null ||
typeof item !== "object" ||
Array.isArray(item) ||
typeof item.id !== "string" ||
!item.id.trim() ||
typeof item.title !== "string" ||
!item.title.trim()
) {
throw new Error("Invalid book");
}
const id = item.id.trim();
if (ids.has(id)) {
throw new Error("Duplicate id");
}
ids.add(id);
return { id, title: item.title.trim() };
});
}
console.log(decode([{ id: " js ", title: " Notes " }])[0].title);
try {
decode([null]);
} catch (error) {
console.log(error.message);
}Check your prediction
Notes
Invalid bookThe type checks short-circuit before trim. The Set is local to one decoding call, so ids from an earlier batch cannot leak into a later one.
Normalize before checking identity
Validation establishes both individual field rules and relationships across records. Checking a raw id for duplicates before trimming can allow two inputs that later normalize to the same id. Perform identity comparisons on the form the rest of the application will actually use.
A boundary also decides which fields become trusted data. Returning the whole source record retains unrelated fields; constructing a clean record makes the projection explicit. Neither successful property reads nor a plausible title proves the full list is valid.
const seen = new Set();
const raw = ["a", " a "];
for (const text of raw) {
const id = text.trim();
console.log(seen.has(id));
seen.add(id);
}
console.log(seen.size);Check your prediction
false
true
1 The second raw spelling is not a distinct identity after normalization. If the application requires unique ids, this is a rejection case rather than a second record to accept.
const input = { id: " a ", title: " Notes ", secret: "private" };
const clean = { id: input.id.trim(), title: input.title.trim() };
console.log(Object.keys(clean).join(","));
console.log(input.id === " a ");Check your prediction
id,title
true Projection controls output fields and avoids mutating the input. It does not replace the checks that prove each field is a string before calling trim.
Try the rule on new inputs
Add a bad first row, a bad last row, normalized duplicates and an empty list to the decoder tests. State whether a single bad row rejects everything or allows partial data; do not let that policy emerge accidentally.
Build it yourself
Data Validation: practise
Write cleanId(value), accepting only a nonblank string and returning the trimmed id. Throw Error(“Invalid id”) otherwise. Print the cleaned id and a caught failure for a number.
// Write cleanId; check " js " and 42.Hint 1: the idea
Validate type before using trim; do not coerce numbers into ids accidentally.
Hint 2: a first step
A short-circuit condition can reject a non-string or blank cleaned value.
Show one solution
function cleanId(value) {
if (typeof value !== "string" || value.trim() === "") {
throw new Error("Invalid id");
}
return value.trim();
}
console.log(cleanId(" js "));
try {
cleanId(42);
} catch (error) {
console.log(error.message);
}js
Invalid idCheck null, undefined, empty text, and spaces. Then decide whether uppercase ids should remain distinct; normalization must match the product rule.
Test a changed requirement
Check null, undefined, empty text, and spaces. Then decide whether uppercase ids should remain distinct; normalization must match the product rule.
Explain before moving on
What does normalization change? Why should duplicate detection happen after trimming?
Completion is saved in this browser only. It does not measure mastery.