diff --git a/docs/05_content_nodes.md b/docs/05_content_nodes.md index 132a9bf2..e24c6d04 100644 --- a/docs/05_content_nodes.md +++ b/docs/05_content_nodes.md @@ -64,13 +64,13 @@ The `yaml-1.1` schema includes [additional collections](https://yaml.org/type/in All of the collections provide the following accessor methods: -| Method | Returns | Description | -| --------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| add(value) | `void` | Adds a value to the collection. For `!!map` and `!!omap` the value must be a Pair instance or a `{ key, value }` object, which may not have a key that already exists in the map. | -| delete(key) | `boolean` | Removes a value from the collection. Returns `true` if the item was found and removed. | -| get(key, [keepScalar]) | `any` | Returns item at `key`, or `undefined` if not found. By default unwraps scalar values from their surrounding node; to disable set `keepScalar` to `true` (collections are always returned intact). | -| has(key) | `boolean` | Checks if the collection includes a value with the key `key`. | -| set(key, value) | `any` | Sets a value in this collection. For `!!set`, `value` needs to be a boolean to add/remove the item from the set. When overwriting a `Scalar` value with a scalar, the original node is retained. | +| Method | Returns | Description | +| ----------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| add(value), addIn(path, value) | `void` | Adds a value to the collection. For `!!map` and `!!omap` the value must be a Pair instance or a `{ key, value }` object, which may not have a key that already exists in the map. | +| delete(key), deleteIn(path) | `boolean` | Removes a value from the collection. Returns `true` if the item was found and removed. | +| get(key, [keep]), getIn(path, [keep]) | `any` | Returns value at `key`, or `undefined` if not found. By default unwraps scalar values from their surrounding node; to disable set `keep` to `true` (collections are always returned intact). | +| has(key), hasIn(path) | `boolean` | Checks if the collection includes a value with the key `key`. | +| set(key, value), setIn(path, value) | `any` | Sets a value in this collection. For `!!set`, `value` needs to be a boolean to add/remove the item from the set. When overwriting a `Scalar` value with a scalar, the original node is retained. | ```js @@ -92,7 +92,7 @@ doc.hasIn(['b', '0']) // true For all of these methods, the keys may be nodes or their wrapped scalar values (i.e. `42` will match `Scalar { value: 42 }`) . Keys for `!!seq` should be positive integers, or their string representations. `add()` and `set()` do not automatically call `doc.createNode()` to wrap the value. -Each of the methods also has a variant that requires an iterable as the first parameter, and allows fetching or modifying deeper collections: `addIn(path, value)`, `deleteIn(path)`, `getIn(path, keepScalar)`, `hasIn(path)`, `setIn(path, value)`. If any intermediate node in `path` is a scalar rather than a collection, an error will be thrown. If any of the intermediate collections is not found: +Each of the methods also has a variant that requires an iterable as the first parameter, and allows fetching or modifying deeper collections. If any intermediate node in `path` is a scalar rather than a collection, an error will be thrown. If any of the intermediate collections is not found: - `getIn` and `hasIn` will return `undefined` or `false` (respectively) - `addIn` and `setIn` will create missing collections; non-negative integer keys will create sequences, all other keys create maps diff --git a/src/ast/Collection.js b/src/ast/Collection.js index 6965cec5..9e40973d 100644 --- a/src/ast/Collection.js +++ b/src/ast/Collection.js @@ -4,7 +4,7 @@ import { createNode } from '../doc/createNode.js' import { Node } from './Node.js' import { Scalar } from './Scalar.js' -function collectionFromPath(schema, path, value) { +export function collectionFromPath(schema, path, value) { let v = value for (let i = path.length - 1; i >= 0; --i) { const k = path[i] diff --git a/src/ast/index.js b/src/ast/index.js index 25886c67..fada7c24 100644 --- a/src/ast/index.js +++ b/src/ast/index.js @@ -1,5 +1,5 @@ export { Alias } from './Alias.js' -export { Collection, isEmptyPath } from './Collection.js' +export { Collection, collectionFromPath, isEmptyPath } from './Collection.js' export { Merge } from './Merge.js' export { Node } from './Node.js' export { Pair } from './Pair.js' diff --git a/src/doc/Document.js b/src/doc/Document.js index 58815c3e..48a9a0ae 100644 --- a/src/doc/Document.js +++ b/src/doc/Document.js @@ -4,6 +4,7 @@ import { Node, Pair, Scalar, + collectionFromPath, isEmptyPath, toJSON } from '../ast/index.js' @@ -147,13 +148,21 @@ export class Document { } set(key, value) { - assertCollection(this.contents) - this.contents.set(key, value) + if (this.contents == null) { + this.setSchema() + this.contents = collectionFromPath(this.schema, [key], value) + } else { + assertCollection(this.contents) + this.contents.set(key, value) + } } setIn(path, value) { if (isEmptyPath(path)) this.contents = value - else { + else if (this.contents == null) { + this.setSchema() + this.contents = collectionFromPath(this.schema, path, value) + } else { assertCollection(this.contents) this.contents.setIn(path, value) } diff --git a/tests/doc/collection-access.js b/tests/doc/collection-access.js index e910c7de..95ab06a6 100644 --- a/tests/doc/collection-access.js +++ b/tests/doc/collection-access.js @@ -90,7 +90,7 @@ describe('Map', () => { test('set scalar node with anchor', () => { doc = YAML.parseDocument('a: &A value\nb: *A\n') doc.set('a', 'foo') - expect(doc.get('a',true)).toMatchObject({ value: 'foo' }) + expect(doc.get('a', true)).toMatchObject({ value: 'foo' }) expect(String(doc)).toBe('a: &A foo\nb: *A\n') }) }) @@ -464,6 +464,10 @@ describe('Document', () => { doc.contents = doc.createNode('s') expect(() => doc.set('a', 1)).toThrow(/document contents/) + + doc.contents = null + doc.set('a', 1) + expect(doc.get('a')).toBe(1) }) test('setIn', () => { @@ -485,5 +489,9 @@ describe('Document', () => { doc.contents = doc.createNode('s') expect(() => doc.setIn(['a'], 1)).toThrow(/document contents/) + + doc.contents = null + doc.setIn(['a', 2], 1) + expect(doc.get('a')).toMatchObject({ items: [null, null, 1] }) }) })