diff --git a/src/api/types.ts b/src/api/types.ts index fbe5d4e..8837332 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -11,7 +11,8 @@ export type AdditionalPropArgs = Pick< export type PropSerializer = ( sourcePropertyValue: any, key: string | number | symbol, - sourceObject: any + sourceObject: any, + jsonOutput: any ) => any | typeof SKIP export type PropDeserializer = ( jsonValue: any, diff --git a/src/core/serialize.ts b/src/core/serialize.ts index fae400c..ed1519c 100644 --- a/src/core/serialize.ts +++ b/src/core/serialize.ts @@ -55,7 +55,7 @@ function serializeWithSchema(schema: ModelSchema, obj: any): T { return } if (propDef === true) propDef = _defaultPrimitiveProp - const jsonValue = propDef.serializer(obj[key], key, obj) + const jsonValue = propDef.serializer(obj[key], key, obj, res) if (jsonValue === SKIP) { return } @@ -74,7 +74,7 @@ function serializeStarProps(schema: ModelSchema, propDef: PropDef, obj: any target[key] = value } } else { - const jsonValue = propDef.serializer(value, key, obj) + const jsonValue = propDef.serializer(value, key, obj, target) if (jsonValue === SKIP) { return } diff --git a/src/serializr.ts b/src/serializr.ts index 2235239..5565d1c 100644 --- a/src/serializr.ts +++ b/src/serializr.ts @@ -29,5 +29,6 @@ export { default as list } from "./types/list" export { default as map } from "./types/map" export { default as mapAsArray } from "./types/mapAsArray" export { default as raw } from "./types/raw" +export { default as embedded } from "./types/embedded" export { SKIP } from "./constants" diff --git a/src/types/embedded.ts b/src/types/embedded.ts new file mode 100644 index 0000000..93e0d60 --- /dev/null +++ b/src/types/embedded.ts @@ -0,0 +1,29 @@ +import deserialize from "../core/deserialize"; +import serialize from "../core/serialize"; +import { SKIP } from "../constants"; +import custom from "./custom"; +import { ClazzOrModelSchema } from "../api/types"; + +/** + * This allows to embed the property values in the resulting json output + * and vice-versa. + * + * @param type {ClazzOrModelSchema} Some class or model schema. + */ +export default function embedded(type: ClazzOrModelSchema) { + return custom( + (value, _key, _sourceObject, jsonOutput) => { + const serialized = serialize(value) + Object.assign(jsonOutput, serialized) + return SKIP + }, + (_, context) => { + return deserialize(type, context.json) + }, + { + beforeDeserialize(callback, jsonValue, jsonParentValue) { + callback(null, null) + } + } + ) +} diff --git a/src/types/map.ts b/src/types/map.ts index 4d3216f..ddfc0b8 100644 --- a/src/types/map.ts +++ b/src/types/map.ts @@ -27,13 +27,13 @@ export default function map( "provided prop is aliased, please put aliases first" ) let result: PropSchema = { - serializer: function (m: Map | { [key: string]: any }) { + serializer: function (m: Map | { [key: string]: any }, _, jsonOutput) { invariant(m && typeof m === "object", "expected object or Map") const result: { [key: string]: any } = {} if (isMapLike(m)) { - m.forEach((value, key) => (result[key] = propSchema.serializer(value, key, m))) + m.forEach((value, key) => (result[key] = propSchema.serializer(value, key, m, jsonOutput))) } else { - for (const key in m) result[key] = propSchema.serializer(m[key], key, m) + for (const key in m) result[key] = propSchema.serializer(m[key], key, m, jsonOutput) } return result }, diff --git a/src/types/mapAsArray.ts b/src/types/mapAsArray.ts index 732f5c7..e9c40c8 100644 --- a/src/types/mapAsArray.ts +++ b/src/types/mapAsArray.ts @@ -24,14 +24,14 @@ export default function mapAsArray( invariant(isPropSchema(propSchema), "expected prop schema as first argument") invariant(!!keyPropertyName, "expected key property name as second argument") let result: PropSchema = { - serializer: function (m) { + serializer: function (m, _, jsonOutput) { invariant(m && typeof m === "object", "expected object or Map") const result = [] // eslint-disable-next-line no-unused-vars if (isMapLike(m)) { - m.forEach((value, key) => result.push(propSchema.serializer(value, key, m))) + m.forEach((value, key) => result.push(propSchema.serializer(value, key, m, jsonOutput))) } else { - for (let key in m) result.push(propSchema.serializer(m[key], key, m)) + for (let key in m) result.push(propSchema.serializer(m[key], key, m, jsonOutput)) } return result }, diff --git a/src/types/optional.ts b/src/types/optional.ts index d81046b..4bdd726 100644 --- a/src/types/optional.ts +++ b/src/types/optional.ts @@ -28,8 +28,8 @@ export default function optional(propSchema?: PropSchema | boolean): PropSchema typeof propSerializer === "function", "expected prop schema to have a callable serializer" ) - const serializer: PropSchema["serializer"] = (sourcePropertyValue, key, sourceObject) => { - const result = propSerializer(sourcePropertyValue, key, sourceObject) + const serializer: PropSchema["serializer"] = (sourcePropertyValue, key, sourceObject, jsonOutput) => { + const result = propSerializer(sourcePropertyValue, key, sourceObject, jsonOutput) if (result === undefined) { return SKIP } diff --git a/test/typescript/ts.ts b/test/typescript/ts.ts index 5e86934..f225e5f 100644 --- a/test/typescript/ts.ts +++ b/test/typescript/ts.ts @@ -2,6 +2,7 @@ import { serializable, alias, date, + embedded, list, map, mapAsArray, @@ -18,6 +19,7 @@ import { custom, AdditionalPropArgs, SKIP, + createModelSchema, } from "../../" import { observable, autorun } from "mobx" @@ -715,3 +717,44 @@ test("list(custom(...)) with SKIP", (t) => { t.end() }) + +test("embedded(type)", (t) => { + class PhoneNumber { + constructor( + public number: string, + public extension?: string) { + + } + } + + class Company { + constructor( + public name: string, + public phone: PhoneNumber) { + + } + } + + createModelSchema(PhoneNumber, { + number: primitive(), + extension: optional(primitive()) + }); + + createModelSchema(Company, { + name: primitive(), + phone: embedded(PhoneNumber) + }) + + const person = new Company('The Company', new PhoneNumber('+55123456789')) + const serialized = serialize(person) + + t.deepEqual(serialized, { + name: person.name, + number: person.phone.number + }) + + const deserialized = deserialize(Company, serialized) + + t.deepEqual(deserialized, person) + t.end() +})