-
-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
671 additions
and
696 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { DEFAULT_DISCRIMINATOR_ATTR } from "../constants"; | ||
import { Clazz, ClazzOrModelSchema, DiscriminatorSpec } from "../serializr"; | ||
import invariant from "../utils/invariant"; | ||
import { getOrCreateSchema } from "../utils/schemas"; | ||
|
||
/** | ||
* Sometimes, when working with schema hierarchies, we may want to deserialize an object to | ||
* a specific sub-schema. The `subSchema` decorator is used to handle such scenario. | ||
* What schema is picked among those available is decided using a "discriminator". The | ||
* discriminator can be a string (which is added to the serialized object) or a object | ||
* containing callbacks allowing for more complex behaviour. | ||
* | ||
* | ||
* @example | ||
* ```ts | ||
* class Todo { | ||
* @serializable | ||
* id: string; | ||
* | ||
* @serializable | ||
* text: string; | ||
* } | ||
* | ||
* @subSchema("picture") | ||
* class PictureTodo extends Todo { | ||
* @serializable | ||
* pictureUrl: string; | ||
* } | ||
* | ||
* const ser = serialize(Object.assign(new PictureTodo(), { | ||
* id: "pic1", | ||
* text: "Lorem Ipsum", | ||
* pictureUrl:"foobar", | ||
* })); | ||
* // ser now holds an object like the following result | ||
* // { | ||
* // id: "pic1", | ||
* // _type: "picture" | ||
* // text: "Lorem Ipsum", | ||
* // pictureUrl:"foobar", | ||
* // } | ||
* const deser = deserialize(Todo, ser); | ||
* console.log(deser instanceof PictureTodo); // true | ||
* ``` | ||
* | ||
* @example | ||
* Using the `parent` argument it's possible to specify the subschema parent instead | ||
* of relying on auto-detention. | ||
* ```ts | ||
* class Todo { | ||
* @serializable | ||
* id: string; | ||
* | ||
* @serializable | ||
* text: string; | ||
* } | ||
* | ||
* @subSchema("picture") | ||
* class PictureTodo extends Todo { | ||
* @serializable | ||
* pictureUrl: string; | ||
* } | ||
* | ||
* @subSchema("betterPicture", Todo) | ||
* class BetterPictureTodo extends PictureTodo { | ||
* @serializable | ||
* altText: string; | ||
* } | ||
* | ||
* | ||
* const ser = serialize(Object.assign(new BetterPictureTodo(), { | ||
* id: "pic1", | ||
* text: "Lorem Ipsum", | ||
* pictureUrl:"foobar", | ||
* altText: "Alt text", | ||
* })); | ||
* // ser now holds an object like the following result | ||
* // { | ||
* // id: "pic1", | ||
* // _type: "betterPicture" | ||
* // text: "Lorem Ipsum", | ||
* // pictureUrl:"foobar", | ||
* // altText: "Alt text", | ||
* // } | ||
* const deser = deserialize(Todo, ser); | ||
* console.log(deser instanceof BetterPictureTodo); // true | ||
* console.log(deser instanceof PictureTodo); // true | ||
* | ||
* const ser2 = serialize(Object.assign(new PictureTodo(), { | ||
* id: "pic2", | ||
* text: "Lorem Ipsum", | ||
* pictureUrl:"foobar", | ||
* })); | ||
* // ser2 now holds an object like the following result | ||
* // { | ||
* // id: "pic2", | ||
* // _type: "picture" | ||
* // text: "Lorem Ipsum", | ||
* // pictureUrl:"foobar", | ||
* // } | ||
* const deser2 = deserialize(Todo, ser2); | ||
* console.log(deser2 instanceof BetterPictureTodo); // false | ||
* console.log(deser2 instanceof PictureTodo); // true | ||
* ``` | ||
* | ||
* @param discriminator An object providing the discriminator logic or a string/number | ||
* that will be stored into the `_type` attribute. | ||
* @param parent When there are multiple levels of hierarchy involved you may provide this | ||
* argument to indicate the main schema used for deserialization. When not give the parent | ||
* schema is inferred as the direct parent (the class/schema that is extended). | ||
* | ||
* @returns | ||
*/ | ||
export default function subSchema( | ||
discriminator: DiscriminatorSpec | string | number, | ||
parent?: ClazzOrModelSchema<any> | ||
): (clazz: Clazz<any>) => Clazz<any> { | ||
return (target: Clazz<any>): Clazz<any> => { | ||
const childSchema = getOrCreateSchema(target); | ||
invariant( | ||
childSchema?.extends, | ||
"Can not apply subSchema on a schema not extending another one." | ||
); | ||
|
||
const parentSchema = getOrCreateSchema(parent || childSchema.extends); | ||
parentSchema.subSchemas = parentSchema.subSchemas ?? []; | ||
parentSchema.subSchemas.push(childSchema); | ||
|
||
if (typeof discriminator === "object") { | ||
childSchema.discriminator = discriminator; | ||
} else { | ||
childSchema.discriminator = { | ||
isActualType(src) { | ||
return src[DEFAULT_DISCRIMINATOR_ATTR] === discriminator; | ||
}, | ||
storeDiscriminator(result) { | ||
result[DEFAULT_DISCRIMINATOR_ATTR] = discriminator; | ||
}, | ||
}; | ||
} | ||
return target; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import createModelSchema from "../api/createModelSchema"; | ||
import getDefaultModelSchema from "../api/getDefaultModelSchema"; | ||
import { ClazzOrModelSchema, ModelSchema } from "../api/types"; | ||
import { isModelSchema } from "./utils"; | ||
|
||
/** | ||
* A simple util that retrieve the existing schema or create a default one. | ||
* @param src | ||
* @returns | ||
*/ | ||
export const getOrCreateSchema = <T extends object>(src: ClazzOrModelSchema<T>): ModelSchema<T> => { | ||
if (isModelSchema(src)) { | ||
return src; | ||
} else { | ||
let schema = getDefaultModelSchema<T>(src); | ||
if (!schema) { | ||
schema = createModelSchema(src, {}); | ||
} | ||
return schema; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.