Skip to content

Commit

Permalink
Serialize undefined as null in collections, remove it in mappings
Browse files Browse the repository at this point in the history
fix #571
fix #325
  • Loading branch information
rlidwka committed Dec 18, 2020
1 parent 38528f7 commit 56d5616
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Line and column in exceptions are now formatted as `(X:Y)` instead of
`at line X, column Y` (also present in compact format), #332.
- Code snippet created in exceptions now contains multiple lines with line numbers.
- `dump()` now serializes `undefined` as `null` in collections and removes keys with
`undefined` in mappings, #571.
- `dump()` with `skipInvalid=true` now serializes invalid items in collections as null.

### Added
- Added `.mjs` (es modules) support.
Expand Down
24 changes: 16 additions & 8 deletions lib/dumper.js
Expand Up @@ -565,9 +565,12 @@ function writeFlowSequence(state, level, object) {
length;

for (index = 0, length = object.length; index < length; index += 1) {
// Write only valid elements.
if (writeNode(state, level, object[index], false, false)) {
if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : '');
// Write only valid elements, put null instead of invalid elements.
if (writeNode(state, level, object[index], false, false) ||
(typeof object[index] === 'undefined' &&
writeNode(state, level, null, false, false))) {

if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : '');
_result += state.dump;
}
}
Expand All @@ -583,9 +586,12 @@ function writeBlockSequence(state, level, object, compact) {
length;

for (index = 0, length = object.length; index < length; index += 1) {
// Write only valid elements.
if (writeNode(state, level + 1, object[index], true, true, false, true)) {
if (!compact || index !== 0) {
// Write only valid elements, put null instead of invalid elements.
if (writeNode(state, level + 1, object[index], true, true, false, true) ||
(typeof object[index] === 'undefined' &&
writeNode(state, level + 1, null, true, true, false, true))) {

if (!compact || _result !== '') {
_result += generateNextLine(state, level);
}

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

pairBuffer = '';
if (index !== 0) pairBuffer += ', ';
if (_result !== '') pairBuffer += ', ';

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

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

if (!compact || index !== 0) {
if (!compact || _result !== '') {
pairBuffer += generateNextLine(state, level);
}

Expand Down Expand Up @@ -823,6 +829,8 @@ function writeNode(state, level, object, block, compact, iskey, isblockseq) {
if (state.tag !== '?') {
writeScalar(state, state.dump, level, iskey, inblock);
}
} else if (type === '[object Undefined]') {
return false;
} else {
if (state.skipInvalid) return false;
throw new YAMLException('unacceptable kind of an object to dump ' + type);
Expand Down
114 changes: 114 additions & 0 deletions test/issues/0571.js
@@ -0,0 +1,114 @@
'use strict';


const assert = require('assert');
const yaml = require('../../');


describe('Undefined', function () {
let undef = new yaml.Type('!undefined', {
kind: 'scalar',
resolve: () => true,
construct: () => {},
predicate: object => typeof object === 'undefined',
represent: () => ''
});

let undef_schema = yaml.DEFAULT_SCHEMA.extend(undef);


it('Should replace undefined with null in collections', function () {
let str;

str = yaml.dump([ undefined, 1, undefined, null, 2 ], { flowLevel: 0 });
assert(str.match(/^\[/));
assert.deepStrictEqual(
yaml.load(str),
[ null, 1, null, null, 2 ]
);

str = yaml.dump([ undefined, 1, undefined, null, 2 ], { flowLevel: -1 });
assert(str.match(/^- /));
assert.deepStrictEqual(
yaml.load(str),
[ null, 1, null, null, 2 ]
);
});


it('Should remove keys with undefined in mappings', function () {
let str;

str = yaml.dump({ t: undefined, foo: 1, bar: undefined, baz: null }, { flowLevel: 0 });
assert(str.match(/^\{/));
assert.deepStrictEqual(
yaml.load(str),
{ foo: 1, baz: null }
);

str = yaml.dump({ t: undefined, foo: 1, bar: undefined, baz: null }, { flowLevel: -1 });
assert(str.match(/^foo:/));
assert.deepStrictEqual(
yaml.load(str),
{ foo: 1, baz: null }
);
});


it("Should serialize top-level undefined to ''", function () {
assert.strictEqual(yaml.dump(undefined), '');
});


it('Should serialize undefined if schema is available', function () {
assert.deepStrictEqual(
yaml.load(
yaml.dump([ 1, undefined, null, 2 ], { schema: undef_schema }),
{ schema: undef_schema }
),
[ 1, undefined, null, 2 ]
);

assert.deepStrictEqual(
yaml.load(
yaml.dump({ foo: 1, bar: undefined, baz: null }, { schema: undef_schema }),
{ schema: undef_schema }
),
{ foo: 1, bar: undefined, baz: null }
);
});


it('Should respect null formatting', function () {
assert.strictEqual(
yaml.dump([ undefined ], { styles: { '!!null': 'uppercase' } }),
'- NULL\n'
);
});


it('Should return an error if neither null nor undefined schemas are available', function () {
assert.throws(() => {
yaml.dump([ 'foo', undefined, 'bar' ], { schema: yaml.FAILSAFE_SCHEMA });
}, /unacceptable kind of an object to dump/);
});


it('Should skip leading values correctly', function () {
assert.strictEqual(
yaml.dump([ () => {}, 'a' ], { flowLevel: 0, skipInvalid: true }),
'[a]\n');

assert.strictEqual(
yaml.dump([ () => {}, 'a' ], { flowLevel: -1, skipInvalid: true }),
'- a\n');

assert.strictEqual(
yaml.dump({ a: () => {}, b: 'a' }, { flowLevel: 0, skipInvalid: true }),
'{b: a}\n');

assert.strictEqual(
yaml.dump({ a: () => {}, b: 'a' }, { flowLevel: -1, skipInvalid: true }),
'b: a\n');
});
});
1 change: 0 additions & 1 deletion test/units/skip-invalid.js
Expand Up @@ -7,7 +7,6 @@ var yaml = require('../../');

var sample = {
number: 42,
undef: undefined,
string: 'hello',
func: function (a, b) { return a + b; },
regexp: /^hel+o/,
Expand Down

0 comments on commit 56d5616

Please sign in to comment.