eemeli / yaml

YAML parser and stringifier for JavaScript
https://eemeli.org/yaml
ISC License
1.26k stars 106 forks source link

Getting a value from a collection after setting the entire collection returns 'undefined' #434

Open ccaroon opened 1 year ago

ccaroon commented 1 year ago

Describe the bug Getting a value from a collection after setting the entire collection returns 'undefined'

To Reproduce See nodeJS script below.

Expected behaviour Getting a value from a collection after setting the entire collection should return the correct/actual value, not undefined.

Versions (please complete the following information):

Additional context See script below.

// ----------------------------------------------------------------------------
// Using: 
//   - yaml@2.2.1
//   - nodeJS v16.16.0
// 
// Bug: Getting a value from a collection after setting the entire collection
//      returns 'undefined'
// 
// Summary of Steps:
// 1. Parse a document with a Seq collection
// 2. Get (getIn) the value of an entry in the Seq collection ... OK
// 3. Set the entire collection using 'set' or 'setIn'
// 4. Get (getIn) the value of an entry in the Seq collection again ... NOK (undefined)
// ----------------------------------------------------------------------------
const { parseDocument } = require('yaml')

const content = `
---
name: simulacrum
path: /opt/simulacrum

splunk:
  - log_path: /var/log/simulacrum/access.log
    index: apps_nonprod
    sourcetype: generic_singleline
    source: simulacrum-access
  - log_path: /var/log/simulacrum/error.log
    index: apps_nonprod
    sourcetype: _json
    source: simulacrum-error
`

// Parse the YAML
const doc = parseDocument(content)
// Print it out ... GOOD!
console.log(doc.toString())

// Get the splunk/0/log_path value
let logPath = doc.getIn(["splunk", 0, "log_path"])
// Print it out ... GOOD!
console.log(`==> OK logPath: ${JSON.stringify(logPath)} <== OK`)

// Use `set` or `setIn` to set the entire `splunk` collection
const newValue = [
  {
    log_path: "fish",
    index: "fry",
    sourcetype: "never",
    source: "more"
  }
]
// doc.set("splunk", newValue)
doc.setIn(["splunk"], newValue)
// Print it out ... GOOD!
console.log(doc.toString())

// Get the `splunk` section
const splunk = doc.get("splunk")
// Print it out ... GOOD
console.log(`splunk: ${JSON.stringify(splunk)}`)

// Get the splunk/0/log_path value (same as above)
logPath = doc.getIn(["splunk", 0, "log_path"])
// Print it out ... BAD! 
// The value of `logPath` is `undefined`
// ...`logPath` should be `fish`
console.log(`==> NOK logPath: ${JSON.stringify(logPath)} <== NOK, undefined`)
ccaroon commented 1 year ago

Not sure is this is a work-around or intended behavior, but if I create newValue using the createNode method everything works as expected:

const newValue = doc.createNode(
  [
    {
      log_path: "fish",
      index: "fry",
      sourcetype: "never",
      source: "more"
    }
  ]
)

If setting a collection using a "raw" Object/Array is NOT suppose to work, then you have my apologies and can close this issue.

eemeli commented 1 year ago

This currently counts as intended behaviour. The get/set methods were added mostly as sugar for the direct access modification of the node tree, which supports having plain JS values embedded in it. This means that the setIn() doesn't apply doc.createNode() on the value that's set, and that getIn() doesn't navigate through non-node objects.

Changing the former would be a breaking change, but the I think the latter could be extended as a non-breaking change, along with #386.