The covers the bulk of use cases. However, to deal with types whose JSON might take more than one form, you will need a custom reviving strategy.
We are going to walk through two examples: first, we will revive objects based on an enumerated field that will indicate how the object should be revived; second, we will revive objects based on their shape only, without any additional fields.
To deal with fields where several subclasses of the declared type would need to be revived, see the .
Example 1: revive based on an enumerated field
We are going to create a NumberCollection class which models either an M.StringMap or an M.List to parse JSON like this:
const {_, number, stringMap, list, anyOf} = M.metadata()
class NumberCollection extends M.Base {
getNumbers () {
const {collectionType, collection} = this
switch (collectionType()) {
case CollectionType.OBJECT():
// Note that .inner() creates a copy. For improved performance,
// [...collection()[M.symbols.innerOrigSymbol]().values()] is used
return [...collection()[M.symbols.innerOrigSymbol]().values()]
case CollectionType.ARRAY():
return [...collection()]
default:
throw TypeError(`Unsupported NumberCollection with type ${collectionType.toJSON()}`)
}
}
sum () {
return this.getNumbers().reduce((acc, x) => acc + x, 0)
}
static innerTypes () {
return Object.freeze({
collectionType: _(CollectionType),
collection: anyOf([
[stringMap(number()), CollectionType.OBJECT()],
[list(number()), CollectionType.ARRAY()]
], 'collectionType') // if omitted, the enumerated field is 'type'
})
}
}
Note that the value for each field in innerTypes can be either metadata or a metadata-returning function that will be passed the plain object being revived.
In this case, the serialisation side of things will work out of the box, since M.List, M.StringMap and our CollectionType implemented with an M.Enum, implement .toJSON() methods on their instances:
In this example, we are going to revive the same polymorphic JSON as the one above, but without an enumerated field to hint the type of the collection.
The end result is simpler, but less generic. It might require non-trivial updates to the logic that figures out which metadata to use. For example, if we start supporting map(number()), whose JSON representation is an array of pairs, Array.isArray will not be enough.
First, it is worth mentioning this is not always possible, as the shape of the JSON representation might be ambiguous (see example in ).