Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for embedded types #140

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/core/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function serializeWithSchema<T>(schema: ModelSchema<T>, 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
}
Expand All @@ -74,7 +74,7 @@ function serializeStarProps(schema: ModelSchema<any>, 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
}
Expand Down
1 change: 1 addition & 0 deletions src/serializr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
29 changes: 29 additions & 0 deletions src/types/embedded.ts
Original file line number Diff line number Diff line change
@@ -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<T>} Some class or model schema.
*/
Comment on lines +7 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an example of the usage here. See the other built in types for how to format it so that it shows up properly in the README.

export default function embedded<T>(type: ClazzOrModelSchema<T>) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other built-in types all accept an additionalArgs?: AdditionalPropArgs argument. We should do the same here.

For this type to be flexible, we probably need a "prefix" option.

return custom(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other built-in types don't use custom, rather they build a PropSchema manually. Unless there is a specific reason, we should do the same here.

(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)
}
Comment on lines +24 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? It doesn't seem to do anything.

}
)
}
6 changes: 3 additions & 3 deletions src/types/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ export default function map(
"provided prop is aliased, please put aliases first"
)
let result: PropSchema = {
serializer: function (m: Map<any, any> | { [key: string]: any }) {
serializer: function (m: Map<any, any> | { [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
},
Expand Down
6 changes: 3 additions & 3 deletions src/types/mapAsArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
4 changes: 2 additions & 2 deletions src/types/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
43 changes: 43 additions & 0 deletions test/typescript/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
serializable,
alias,
date,
embedded,
list,
map,
mapAsArray,
Expand All @@ -18,6 +19,7 @@ import {
custom,
AdditionalPropArgs,
SKIP,
createModelSchema,
} from "../../"

import { observable, autorun } from "mobx"
Expand Down Expand Up @@ -715,3 +717,44 @@ test("list(custom(...)) with SKIP", (t) => {

t.end()
})

test("embedded(type)", (t) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other typescript tests all use decorators. Can we move these to simple.js?

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()
})