Skip to content

Commit

Permalink
Merge pull request #104 from pyrogenic/skip_undefined
Browse files Browse the repository at this point in the history
Support optional properties
  • Loading branch information
pyrogenic authored Sep 27, 2019
2 parents 2022227 + 6ea3590 commit a9860b6
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 0 deletions.
1 change: 1 addition & 0 deletions serializr.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function identifier(additionalArgs: AdditionalPropArgs): PropSchema;
export function date(additionalArgs?: AdditionalPropArgs): PropSchema;

export function alias(jsonName: string, propSchema?: PropSchema | boolean): PropSchema;
export function optional(propSchema?: PropSchema | boolean): PropSchema;

export function child(modelschema: ClazzOrModelSchema<any>, additionalArgs?: AdditionalPropArgs): PropSchema;
export function object(modelschema: ClazzOrModelSchema<any>, additionalArgs?: AdditionalPropArgs): PropSchema;
Expand Down
1 change: 1 addition & 0 deletions src/serializr.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export { default as date } from "./types/date"
export { default as alias } from "./types/alias"
export { default as custom } from "./types/custom"
export { default as object } from "./types/object"
export { default as optional } from "./types/optional"
export { default as reference } from "./types/reference"
export { default as list } from "./types/list"
export { default as map } from "./types/map"
Expand Down
31 changes: 31 additions & 0 deletions src/types/optional.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { invariant, isPropSchema } from "../utils/utils"
import { _defaultPrimitiveProp, SKIP } from "../constants"

/**
* Optional indicates that this model property shouldn't be serialized if it isn't present.
*
* @example
* createModelSchema(Todo, {
* title: optional(primitive()),
* });
*
* console.dir(serialize(new Todo()));
* // {}
*
* @param {PropSchema} propSchema propSchema to (de)serialize the contents of this field
* @returns {PropSchema}
*/
export default function optional(name, propSchema) {
propSchema = (!propSchema || propSchema === true) ? _defaultPrimitiveProp : propSchema
invariant(isPropSchema(propSchema), "expected prop schema as second argument")
const propSerializer = propSchema.serializer
invariant(typeof propSerializer === "function", "expected prop schema to have a callable serializer")
function serializer(...args) {
const result = propSerializer(...args)
if (result === undefined) {
return SKIP
}
return result
}
return Object.assign({}, propSchema, {serializer})
}
60 changes: 60 additions & 0 deletions test/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var test = require("tape")
var _ = require("..")
var serialize = _.serialize
var deserialize = _.deserialize
var optional = _.optional
var primitive = _.primitive
var update = _.update

Expand Down Expand Up @@ -38,6 +39,32 @@ test("it should serialize simple object", t1 => {
t.end()
})

test("it should serialize all fields in the schema even if they're not defined on the object (existing behavior)", t => {
var a = { y: 1337 }
var s = serialize(schema, a)

t.deepEqual(s, { x: undefined })
// Note that this behavior is only one-way: it doesn't set props as undefined on the deserialized object.
t.deepEqual(deserialize(schema, s), {})

var d = { x: 1 }
update(schema, a, d)
t.deepEqual(a, {
y: 1337, x: 1
})

test("it should skip missing attrs", t3 => {
update(schema, a, {}, (err, res) => {
t3.ok(res === a)
t3.notOk(err)
t3.equal(res.x, 1)
t3.end()
})
})

t.end()
})

test("it should (de)serialize arrays", t => {
var data = [ { x: 1 }, { x: 2}]

Expand Down Expand Up @@ -132,6 +159,39 @@ test("it should not set values for custom serializers/deserializer that return S
t.end()
})

test("it should not serialize values for optional properties", t => {
var schema = {
factory: () => ({}),
props: {
optionalProp: optional(primitive()),
requiredProp: primitive()
}
}
var a = { y: 1337 }
var s = serialize(schema, a)

t.deepEqual(s, {requiredProp: undefined})
// Note that this behavior is only one-way: it doesn't set props as undefined on the deserialized object.
t.deepEqual(deserialize(schema, s), {})

var d = { optionalProp: 1 }
update(schema, a, d)
t.deepEqual(a, {
y: 1337, optionalProp: 1
})

test("it should skip missing attrs", t3 => {
update(schema, a, {}, (err, res) => {
t3.ok(res === a)
t3.notOk(err)
t3.equal(res.optionalProp, 1)
t3.end()
})
})

t.end()
})

test("it should pass key and object to custom schemas", t => {
var s = _.createSimpleSchema({
a: primitive(),
Expand Down
11 changes: 11 additions & 0 deletions test/typescript/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
map,
mapAsArray,
object,
optional,
identifier,
reference,
primitive,
Expand Down Expand Up @@ -74,6 +75,9 @@ test("typescript class with constructor params", t => {
@serializable(alias("identifier", identifier()))
public id: string;

@serializable(alias("desc", optional()))
public description?: string;

@serializable(alias("width", true))
public width: number

Expand All @@ -95,13 +99,20 @@ test("typescript class with constructor params", t => {
a.someNumber = 123;

let json = serialize(a);
t.equal(false, json.hasOwnProperty("desc"));
t.equal(false, json.hasOwnProperty("description"));
const b = deserialize(Rectangle, json);
t.equal(a.id, b.id);
t.equal(a.width, b.width);
t.equal(a.height, b.height);
t.equal(a.someNumber, b.someNumber);
t.equal(b.getArea(), 200);

a.description = "example";
json = serialize(a);
t.equal("example", json["desc"]);
t.equal(false, json.hasOwnProperty("description"));

t.end();
});

Expand Down

0 comments on commit a9860b6

Please sign in to comment.