Skip to content

Commit 56d5616

Browse files
committed
Serialize undefined as null in collections, remove it in mappings
fix #571 fix #325
1 parent 38528f7 commit 56d5616

File tree

4 files changed

+133
-9
lines changed

4 files changed

+133
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Line and column in exceptions are now formatted as `(X:Y)` instead of
2424
`at line X, column Y` (also present in compact format), #332.
2525
- Code snippet created in exceptions now contains multiple lines with line numbers.
26+
- `dump()` now serializes `undefined` as `null` in collections and removes keys with
27+
`undefined` in mappings, #571.
28+
- `dump()` with `skipInvalid=true` now serializes invalid items in collections as null.
2629

2730
### Added
2831
- Added `.mjs` (es modules) support.

lib/dumper.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -565,9 +565,12 @@ function writeFlowSequence(state, level, object) {
565565
length;
566566

567567
for (index = 0, length = object.length; index < length; index += 1) {
568-
// Write only valid elements.
569-
if (writeNode(state, level, object[index], false, false)) {
570-
if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : '');
568+
// Write only valid elements, put null instead of invalid elements.
569+
if (writeNode(state, level, object[index], false, false) ||
570+
(typeof object[index] === 'undefined' &&
571+
writeNode(state, level, null, false, false))) {
572+
573+
if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : '');
571574
_result += state.dump;
572575
}
573576
}
@@ -583,9 +586,12 @@ function writeBlockSequence(state, level, object, compact) {
583586
length;
584587

585588
for (index = 0, length = object.length; index < length; index += 1) {
586-
// Write only valid elements.
587-
if (writeNode(state, level + 1, object[index], true, true, false, true)) {
588-
if (!compact || index !== 0) {
589+
// Write only valid elements, put null instead of invalid elements.
590+
if (writeNode(state, level + 1, object[index], true, true, false, true) ||
591+
(typeof object[index] === 'undefined' &&
592+
writeNode(state, level + 1, null, true, true, false, true))) {
593+
594+
if (!compact || _result !== '') {
589595
_result += generateNextLine(state, level);
590596
}
591597

@@ -616,7 +622,7 @@ function writeFlowMapping(state, level, object) {
616622
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
617623

618624
pairBuffer = '';
619-
if (index !== 0) pairBuffer += ', ';
625+
if (_result !== '') pairBuffer += ', ';
620626

621627
if (state.condenseFlow) pairBuffer += '"';
622628

@@ -671,7 +677,7 @@ function writeBlockMapping(state, level, object, compact) {
671677
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
672678
pairBuffer = '';
673679

674-
if (!compact || index !== 0) {
680+
if (!compact || _result !== '') {
675681
pairBuffer += generateNextLine(state, level);
676682
}
677683

@@ -823,6 +829,8 @@ function writeNode(state, level, object, block, compact, iskey, isblockseq) {
823829
if (state.tag !== '?') {
824830
writeScalar(state, state.dump, level, iskey, inblock);
825831
}
832+
} else if (type === '[object Undefined]') {
833+
return false;
826834
} else {
827835
if (state.skipInvalid) return false;
828836
throw new YAMLException('unacceptable kind of an object to dump ' + type);

test/issues/0571.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
3+
4+
const assert = require('assert');
5+
const yaml = require('../../');
6+
7+
8+
describe('Undefined', function () {
9+
let undef = new yaml.Type('!undefined', {
10+
kind: 'scalar',
11+
resolve: () => true,
12+
construct: () => {},
13+
predicate: object => typeof object === 'undefined',
14+
represent: () => ''
15+
});
16+
17+
let undef_schema = yaml.DEFAULT_SCHEMA.extend(undef);
18+
19+
20+
it('Should replace undefined with null in collections', function () {
21+
let str;
22+
23+
str = yaml.dump([ undefined, 1, undefined, null, 2 ], { flowLevel: 0 });
24+
assert(str.match(/^\[/));
25+
assert.deepStrictEqual(
26+
yaml.load(str),
27+
[ null, 1, null, null, 2 ]
28+
);
29+
30+
str = yaml.dump([ undefined, 1, undefined, null, 2 ], { flowLevel: -1 });
31+
assert(str.match(/^- /));
32+
assert.deepStrictEqual(
33+
yaml.load(str),
34+
[ null, 1, null, null, 2 ]
35+
);
36+
});
37+
38+
39+
it('Should remove keys with undefined in mappings', function () {
40+
let str;
41+
42+
str = yaml.dump({ t: undefined, foo: 1, bar: undefined, baz: null }, { flowLevel: 0 });
43+
assert(str.match(/^\{/));
44+
assert.deepStrictEqual(
45+
yaml.load(str),
46+
{ foo: 1, baz: null }
47+
);
48+
49+
str = yaml.dump({ t: undefined, foo: 1, bar: undefined, baz: null }, { flowLevel: -1 });
50+
assert(str.match(/^foo:/));
51+
assert.deepStrictEqual(
52+
yaml.load(str),
53+
{ foo: 1, baz: null }
54+
);
55+
});
56+
57+
58+
it("Should serialize top-level undefined to ''", function () {
59+
assert.strictEqual(yaml.dump(undefined), '');
60+
});
61+
62+
63+
it('Should serialize undefined if schema is available', function () {
64+
assert.deepStrictEqual(
65+
yaml.load(
66+
yaml.dump([ 1, undefined, null, 2 ], { schema: undef_schema }),
67+
{ schema: undef_schema }
68+
),
69+
[ 1, undefined, null, 2 ]
70+
);
71+
72+
assert.deepStrictEqual(
73+
yaml.load(
74+
yaml.dump({ foo: 1, bar: undefined, baz: null }, { schema: undef_schema }),
75+
{ schema: undef_schema }
76+
),
77+
{ foo: 1, bar: undefined, baz: null }
78+
);
79+
});
80+
81+
82+
it('Should respect null formatting', function () {
83+
assert.strictEqual(
84+
yaml.dump([ undefined ], { styles: { '!!null': 'uppercase' } }),
85+
'- NULL\n'
86+
);
87+
});
88+
89+
90+
it('Should return an error if neither null nor undefined schemas are available', function () {
91+
assert.throws(() => {
92+
yaml.dump([ 'foo', undefined, 'bar' ], { schema: yaml.FAILSAFE_SCHEMA });
93+
}, /unacceptable kind of an object to dump/);
94+
});
95+
96+
97+
it('Should skip leading values correctly', function () {
98+
assert.strictEqual(
99+
yaml.dump([ () => {}, 'a' ], { flowLevel: 0, skipInvalid: true }),
100+
'[a]\n');
101+
102+
assert.strictEqual(
103+
yaml.dump([ () => {}, 'a' ], { flowLevel: -1, skipInvalid: true }),
104+
'- a\n');
105+
106+
assert.strictEqual(
107+
yaml.dump({ a: () => {}, b: 'a' }, { flowLevel: 0, skipInvalid: true }),
108+
'{b: a}\n');
109+
110+
assert.strictEqual(
111+
yaml.dump({ a: () => {}, b: 'a' }, { flowLevel: -1, skipInvalid: true }),
112+
'b: a\n');
113+
});
114+
});

test/units/skip-invalid.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ var yaml = require('../../');
77

88
var sample = {
99
number: 42,
10-
undef: undefined,
1110
string: 'hello',
1211
func: function (a, b) { return a + b; },
1312
regexp: /^hel+o/,

0 commit comments

Comments
 (0)