Sometimes, you receive external data that are not well designed, like shown in the following JSON file with comments :
{
"firstName": "Bob",
"numberOfHobbies": "3", // 👈 should be a number
"birthDate": "21/10/1998", // 👈 formatted Date
"hobbies": "cooking,skiing,programming" // 👈 not JSON-friendly
}
But you prefer a clean target structure ; let's use Typescript to describe it :
interface Person { // 👈 Target with clean types
firstName: string
numberOfHobbies: number
birthDate: Date
hobbies: string[]
}
How to parse with JSON.parse()
the incoming data to the expected target ?
Just use Jsonizer's revivers to fix anything :
npm install @badcafe/jsonizer
import { Jsonizer } from '@badcafe/jsonizer';
Describe the source shape as it is :
interface PersonDTO { // 👈 Source with bad types
firstName: string
numberOfHobbies: string
birthDate: string
hobbies: string
}
DTO stands for Data Transfer Object
Then define the mappings for every field to fix ; in Jsonizer, a mapping is just a plain object that contains an entry for each field to map :
// Target Source
// 👇 👇
const personReviver = Jsonizer.reviver<Person, PersonDTO>({
numberOfHobbies: {
// 👇 fix the type
'.': n => parseInt(n)
},
birthDate: Date,
hobbies: {
// 👇 split CSV to array
'.': csv => csv.split(',')
}
})
Each entry is bound to its reviver that can be a class such as Date
, or a nested mapping for hierarchic structures, or nothing to left the field as-is, such as for the firstName
.
The special mapping entry '.'
stands for the familiar 'self' reference ; it is bound to a function that returns the expected data. Jsonizer also supply the '*'
mapping that stands for the familiar 'any' item (object field or array item) and it is also possible to use Regexp matchers and range matchers for arrays.
However, there is a mapping that doesn't work ; let's try it with the incoming data :
new Date('21/10/1998')
// Invalid Date
Since the birthDay
input field is such a formatted date, we have to rewrite the mapping for it :
// Target Source
// 👇 👇
const personReviver = Jsonizer.reviver<Person, PersonDTO>({
numberOfHobbies: {
// 👇 fix the type
'.': n => parseInt(n)
},
birthDate: {
// 👇 fix the Date
'.': date => {
const [day, month, year] = date.split('/')
.map(part => parseInt(part));
return new Date(Date.UTC(year, month - 1, day));
}
},
hobbies: {
// 👇 split CSV to array
'.': csv => csv.split(',')
}
})
Notes:
- don't use
new Date(year, month - 1, day)
because it may shift due to the local time zone.- Javascript use month indexes, so we use
month - 1
.
Finally, parse the data :
const personJson = await read('person.json');
const person = JSON.parse(personJson, personReviver);
Since this exemple is somewhat simple with a flat structure, you might be tempted to write your own reviver function, but for nested structures it will become harder than you think.
With Jsonizer, you'll be able to define mappings for classes, plain objects, nested structures... and more.
See also :
Top comments (0)