---
title: "Changelog"
url: https://recursiveai.org/oneyaml/go/docs/changelog/
description: "All notable changes are listed here, newest first. The format follows Keep a Changelog, and versions follow Semantic Versioning. Breaking changes are…"
version: "1.0.0"
source: https://github.com/recursive-ai-oss/oneyaml-go/blob/7a4c8e39f041b945ee5f121c055f03bc2d7d01d9/CHANGELOG.md
---

# Changelog

All notable changes are listed here, newest first. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/). Breaking changes are listed first, under Breaking, each with what to change in your code (releases up to 0.3.0 mark them **Breaking** instead). A new major version has a new module path (`oneyaml.dev/go/v2`), and the entry says so. What a release promises, and the supported Go versions, are under [Stability](https://recursiveai.org/oneyaml/go/docs/overview/#stability).

## [Unreleased]

## [1.0.0] - 2026-10-16

The first stable release. From 1.0.0 the API is stable under the promise in [Stability](https://recursiveai.org/oneyaml/go/docs/overview/#stability).

### Breaking

- `LimitError.Limit` is now of the new type `Limit` and names the `LimitSet` field of the bound (`LimitMaxDepth`, `LimitMaxAliasNodes` and so on). It used to be free text such as `"depth"`, `"alias expansion nodes"` or `"patch output size"`. The text is now in the new field `What`. Patch limits report the field that governs them: `MaxInputBytes` for the patch text and for the document a patch makes, and `MaxAliasBytes` for the text a patch copies. **Migrate:** compare `le.Limit` with the `Limit` constants, or match a sentinel such as `errors.Is(err, oneyaml.ErrMaxDepth)`, and read `le.What` where you printed `le.Limit`.
- `LimitError.Option` now has one form, `Limits (MaxDepth)`, which names the option and the `LimitSet` field that raise the bound. It used to be `Limits`, `Limits or NoLimits`, `Limits (MaxAliasNodes)` or `none: split the stream`, depending on where the limit was hit. It is empty for the three bounds no option changes: the nesting of a patch value (10,000 levels), the nesting of typed Go values (100,000 levels) and the 2 GiB size of a `Document`. It is also always empty in `compat/yamlv3` and `compat/k8syaml`, which take no limit options and sometimes suggested one. **Migrate:** switch on `le.Limit` rather than reading `Option`.
- `SyntaxError.Category` is now of the new type `Category`, and `CategorySyntax`, `CategoryEncoding` and `CategoryDuplicateKey` are typed constants. They used to be untyped strings. Comparisons, `switch` statements and printing still compile. **Migrate:** where you store a category in a `string` or pass it as one, write `string(se.Category)`.
- `TypeError.GoType` is now a `reflect.Type`. It used to be a `string`. It is nil when no type was involved, so two types with one name in different packages can be told apart and a type can be compared: `te.GoType == reflect.TypeFor[time.Duration]()`. Messages print the type as before. **Migrate:** where you used the string, call `te.GoType.String()` after checking for nil.
- `KnownFields`, `MergeKeys` and `YAML11Strings` are now `func(...bool) Option`, and `Document.Get`, `Document.Decode` and `Node.Decode` now take `...Option` (see Added). Every call that compiled before still compiles. A method value or a function-typed variable no longer matches, such as `var f func(string, any) error = doc.Get`, and neither does an interface that lists the old methods. **Migrate:** wrap the call in a function literal, such as `func(p string, v any) error { return doc.Get(p, v) }`.
- `Node.Find` now returns an error matching `ErrAmbiguousKey` for a key written more than once in its mapping, and `Node.Lookup` now returns `false` for it. Both used to return the first entry. `Document.Get` and every edit already refused a pointer through such a key. **Migrate:** read every entry with `Entries`, and use `Find` to learn why a lookup failed ([Reading while you edit](https://recursiveai.org/oneyaml/go/docs/editing/#reading-while-you-edit)).
- Under a `MaxInputBytes` bound (`Untrusted()` sets one), `Set`, `Rename`, `SetHeadComment` and `SetLineComment` now refuse an edit that would make the text longer than the bound. Before, only `ApplyPatch` and `ApplyMergePatch` were held to it. The error is an `*EditError`, and `errors.As` finds the `*LimitError` inside it. The document is unchanged, and an edit that makes the text shorter is always allowed. For a UTF-16 or UTF-32 document, these edits and the patches now count the size in the encoding the document is written in, byte order mark included; patches used to count the UTF-8 size. Even with no bound set, an edit or patch is refused when the text would pass 2 GiB, the most a `Document` can address. **Migrate:** pass a larger bound when you parse, with `Limits(oneyaml.LimitSet{MaxInputBytes: n})` after `Untrusted()` ([Limits](https://recursiveai.org/oneyaml/go/docs/limits/#each-limit)).
- `Parse`, `Unmarshal`, the `Decoder` and the compat packages now refuse two kinds of invalid YAML with a `*SyntaxError`. Version 0.3.0 loaded both:
  - Inside a flow collection, a flow collection that touches the `:` of a plain or empty key, as in `{a:{}}`, `[a:[]]` and `{:{0}}`. After a quoted key (`{"a":{}}`) it is still allowed.
  - A verbatim tag that is neither a local tag nor a URI that starts with a scheme, as in `!<!>`, `!<a>` and `!<$:?>`.

  **Migrate:** put a space after the colon (`{a: {}}`), and write such a tag as a local tag (`!<!name>`), a full URI (`!<tag:example.com,2026:name>`) or a shorthand tag (`!name`).
- `compat/yamlv3`: `Marshal` now returns an error naming the type and the method for a `MarshalYAML` method that is not `MarshalYAML() (interface{}, error)`. It used to skip the method and write the value field by field, as yaml.v3 does. The `oneyaml` package already refused such a method. **Migrate:** give the method the signature `MarshalYAML() (interface{}, error)`, or remove it.
- `compat/yamlv3`: `Marshal` now returns an error naming the type for a value of the `Node` type of `gopkg.in/yaml.v3` or `go.yaml.in/yaml/v3`. This covers such a value given directly, held in a field, or returned by a `MarshalYAML` method. The adapter used to write the node's Go fields (`kind: 8`, `style: 0`, ...), where yaml.v3 writes the YAML the node holds. **Migrate:** build the adapter's own `yaml.Node`, or write that value with yaml.v3 ([Migrating from yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/#check-after-the-swap)).
- `compat/yamlv3`: `Unmarshal` and `Decode` now return an error naming the type when the target is the `Node` type of `gopkg.in/yaml.v3` or `go.yaml.in/yaml/v3`, held directly, in a field, in a slice or map, or behind a pointer. They used to return no error and leave the node empty. A null value still leaves a pointer to it nil. **Migrate:** decode into the adapter's `yaml.Node`.

### Added

- Sentinel errors for every error kind, so a single `errors.Is` call tells kinds apart. `*SyntaxError` matches `ErrSyntax`, and also `ErrEncoding` or `ErrDuplicateKey` for those categories. `*LimitError` matches `ErrLimit`, and also the sentinel of its limit, named like the `LimitSet` field: `ErrMaxDepth`, `ErrMaxAliasNodes`, `ErrMaxAliasBytes`, `ErrMaxInputBytes`, `ErrMaxScalarBytes`, `ErrMaxDocuments`, `ErrMaxNumberDigits` and `ErrMaxPatchWorkRatio`, also through an `*EditError` that wraps it ([Limits](https://recursiveai.org/oneyaml/go/docs/limits/#each-limit)).
- The type `Limit` and its constants `LimitMaxDepth`, `LimitMaxAliasNodes`, `LimitMaxAliasBytes`, `LimitMaxInputBytes`, `LimitMaxScalarBytes`, `LimitMaxDocuments`, `LimitMaxNumberDigits` and `LimitMaxPatchWorkRatio`, for `LimitError.Limit`. Also the field `LimitError.What`, and the type `Category` for `SyntaxError.Category` (see Breaking).
- `oneyaml.Pointer(tokens...)` builds an RFC 6901 pointer from keys and indices, escaping `~` and `/` in each. For example, `Pointer("metadata", "labels", "app.kubernetes.io/name")` is `/metadata/labels/app.kubernetes.io~1name`. Use it instead of joining keys with `"/"`.
- `Document.Get`, `Document.Decode` and `Node.Decode` take options for one read, applied on top of the options the Document was parsed with. For example, `doc.Get("/spec", &spec, oneyaml.KnownFields())` reads one part strictly. `KnownFields`, `MergeKeys`, `Limits`, `Untrusted` and `NoLimits` apply to a read; other options are ignored. The options also hold for the decodes an `UnmarshalYAML` hook makes during that read. A bound given to a read of a Document replaces the Document's bound in both directions, so `NoLimits()` on a read of a Document parsed with `Untrusted()` lifts its bounds for that read. Pass such options only for input you trust ([Reading values back](https://recursiveai.org/oneyaml/go/docs/editing/#reading-values-back)). Inside an `UnmarshalYAML` hook it is different: options given to `Node.Decode` may only tighten the limits of the read that called the hook, never loosen them. For each bound the stricter of the caller's and the option's applies, so a type's hook cannot undo a caller's `Untrusted()`. `KnownFields` and `MergeKeys` given there apply as given.
- `KnownFields`, `MergeKeys` and `YAML11Strings` take an optional bool, so `KnownFields(false)` turns the option off again, and a later option overrides an earlier one. Calls without an argument behave as before.
- `Node` is now documented as comparable: two Nodes are equal when they are the same node of the same state of the same Document. A Node read before an edit is never equal to the one read after it.

### Changed

- `LimitError.Path` of a limit hit by a decode inside an `UnmarshalYAML` hook is now the pointer in the whole document, like the path of a `*TypeError`. This holds when the hook wraps the error, as with `fmt.Errorf("...: %w", err)` or `errors.Join`, when hooks are nested and each one wraps it, and when an outer hook decoded a child of its `Node`. It used to be empty. The read does not change the error the hook returned: `errors.As` on the read's error finds a copy that carries the whole-document path. A hook may therefore keep that error and return it from later reads, even on several goroutines at once, and those reads return it as it is.
- A type error found by a decode inside an `UnmarshalYAML` hook (a `Node.Decode`, or the function a func-style hook is given) is now reported where the value is in the input, with every mismatch the hook's decode found, as for a read without the hook. Its path is the hook's path followed by the path inside the node the hook decoded, so a hook that decodes a child node reports that child's path. Before, only the first mismatch was reported, at the first key of the hooked mapping, and a child's path was cut off at the hook. A bad value on line 7 could be reported on line 5. This holds at any depth of nested hooks and on every read path.
- The `Unmarshaler` documentation now says how a `Node` that `UnmarshalYAML` keeps may be used after the method returns: later, from one goroutine at a time, but not from another goroutine at the moment the method returns. A kept Node goes on reading its own subtree, whichever read gave it. One kept from `Unmarshal` reads the byte slice you passed in, so leave that slice unchanged while you keep the Node. One kept from a `Document` read becomes stale when the `Document` is edited.
- An error that a hook returns from reading other text, such as an `Unmarshal` of its own input, is now reported at the hook's value with the hook's type as `GoType`. The position and path inside the other text stay in the message: `line 1, column 4: /a: line 8, column 4: /x: ...`. Before, its path was joined onto the hook's (`/a/x`) and its own line and column were lost.
- A type whose `UnmarshalYAML` method has a form that cannot be called, and a `Node` used as a decode target, are now collected like a value that does not fit. The value is skipped, the rest of the document is read, and the `*TypeError` lists every mismatch. The first such value used to stop the read.
- `LimitError.Path` is now set for limits that are hit at a value while it is read: alias expansion, scalar size, number digits and the fixed nesting bound of typed values. It was already set for patches. It stays empty for the limits `Parse` reports and for input size, document count and nesting that the parser stops at. `LimitError.Error` shortens a `Path` longer than 512 bytes, as `TypeError.Error` does.
- Limit error messages name the option in its new form, or none (see Breaking), and give the path when there is one. The document-count limit from `Parse` now says "documents per stream", as the `Decoder`'s does.

### Fixed

- An alias refers to the latest anchor of its name written before it, even when that anchor is inside an earlier node with the same anchor name. In `a: &b {s: &b 1, t: *b}`, `t` is `1`. This document used to be refused with "refers to a node that contains it". In `a: &b {se: &b 1}` followed by `copy: *b`, `copy` is `1`; it used to be `{se: 1}`. Every reader agrees, the compat packages included.
- An unknown field found under `KnownFields()` by a decode inside an `UnmarshalYAML` hook now matches `ErrUnknownField`. It did not.
- Edits to a later document of a stream that starts with a byte order mark: `Set` of a new key and `SetHeadComment` on its first entry or root work, and the mark stays at the start of the line. They used to fail with "edit could not be applied" or `ErrUnsupportedEdit`.
- `Set` of the empty value of an explicit key (`? key`) followed by a `: value` line that belongs to an outer mapping writes the value under the key. It used to fail with "edit could not be applied".
- `compat/yamlv3` reads comments onto the same nodes as yaml.v3 in more layouts:
  - A comment on a dash line with no value (`- # note`) goes to the first key or scalar of the item, or to a flow collection that comes first in it. It used to be moved above the item.
  - The comments of an empty last item (`- # note` at the end of the text) are the document's foot comment. They used to be dropped.
  - A foot comment of the item before such a dash line stays with that item. It used to be dropped.
  - A comment line just before the closing bracket of a flow collection is the foot comment of the last item or key. It used to be dropped.
  - The comments after a flow collection that ends on the first line of the text (`[a, b]` or `a: [1, 2]`, then `# note`) are the document's foot comment. They used to go to the collection, or to its key.
- `compat/yamlv3` writes text that reads back:
  - A folded scalar (`>`) always reads back as the value written. Some values used to read back with a line break turned into a space or an extra line break, such as a line followed by an indented line. In some of these cases yaml.v3's own text does not read back.
  - A foot comment held by a block collection that is an item of a sequence is written once, after that collection. It used to be dropped.
  - A blank line between the lines of a head comment is written without indentation, as yaml.v3 writes it.
- `compat/yamlv3` reads each comment line once, and tells one line from another by where it stands in the text, not by its words, so two comments with the same words are both kept. When several sequences start on one dash line (`- # a`, then `- # b`, then a mapping), the comment lines above the dashes were given to more than one node, and writing the result repeated them, adding a copy on every round trip. Comments at the end of a document are no longer dropped when the key that would take them has a foot comment already, and a comment block under `key:` followed by a blank line and another block heads the first key of the value, as in yaml.v3.

## [0.3.0] - 2026-10-16

### Added

- `Node.Find(key)` is the checked form of `Lookup`: it returns the value with a `nil` error, or an error that says why there is none. The error matches `ErrNotFound` for an absent key (or a node that is not a mapping), `ErrStaleNode` for a node read before an edit, and `ErrAmbiguousKey` when a merge brings the key in from two places of one source, and it is a `*LimitError` when a merge search was refused by `MaxAliasNodes`. `Lookup` reports all of these as `false`. See [Reading while you edit](https://recursiveai.org/oneyaml/go/docs/editing/#reading-while-you-edit).
- `Node.Err` says why a `Node` reads as zero values: it is `nil` for a node you can read, matches `ErrStaleNode` when the document was edited after the node was read, and matches `ErrNotFound` for the zero `Node`, such as the one a failed `Lookup` returns. `Value` of a stale node is `""`, the same as an empty scalar, so `Err` is the check to make. See [Reading while you edit](https://recursiveai.org/oneyaml/go/docs/editing/#reading-while-you-edit).
- With `MergeKeys()`, a pointer also finds the keys a `<<` merge brings into a mapping, with the precedence decoding gives them: `Document.Get`, the patch `test` operation, the `from` of a patch `copy`, `Node.Lookup` and `Node.Find` read them. A `copy` from such a key copies the merged value and leaves the merge source unchanged. `Set` on such a key adds an explicit key that overrides the merged one. Each merge source is searched once, and the keys read count against `MaxAliasNodes`. See [Merge keys](https://recursiveai.org/oneyaml/go/docs/editing/#merge-keys) and the Changed entry below.
- `ApplyMergePatch` also reads a merge patch written in YAML, with the same refusals as a YAML `ApplyPatch`. Text that starts with `{` or `[`, or is valid JSON, is still read as JSON. A mapping key that is not a string (`1: 5`, `true: 5`, an empty key) is refused at any depth with its line and column, because merge patch members are named by strings; quote the name to use it. See [Patches written in YAML](https://recursiveai.org/oneyaml/go/docs/patches/#patches-written-in-yaml).

### Changed

- **Breaking:** more repeated mapping keys are duplicate-key errors, so some input that used to load is refused:
  - repeated `.nan` keys, in any spelling (`.nan` and `.NaN` are one key), read into `any`, a `map[float64]T` or another map whose keys are not strings. A map with string keys still compares keys as text;
  - a repeated key that no field of a struct target has, and a key repeated inside a value the target ignores. `a: 1` twice into a struct with no field `a` used to load;
  - `compat/yamlv3` reports a key repeated at the top of a mapping its target ignores, as yaml.v3 does. This removes a listed gap.

  Fix such input by removing the repeated key. The rules are in the [`Unmarshal` reference](https://pkg.go.dev/oneyaml.dev/go#Unmarshal).
- **Breaking:** `Parse` refuses a `\u` or `\U` escape that is not a Unicode scalar value, such as a lone surrogate (`"\ud83d"`), a low surrogate before a high one, or a value past `U+10FFFF`, with a `*SyntaxError`. `Parse` used to accept such a document, and only `Get` or `Decode` of the value failed. `Unmarshal` already refused it. Write a valid escape instead: a character outside the Basic Multilingual Plane as an escaped surrogate pair (`"\ud83d\ude00"`), which is now read as one character (see Fixed), or as `\U0001F600`. A lone surrogate is not a character, so replace it, with `\uFFFD` for example, or remove it.
- **Breaking:** with `MergeKeys()`, an edit that would change a key only a merge source has is refused with `ErrAliasWrite`, because it would write into the source through an alias: `Remove`, the patch operations `remove`, `replace` and `move`, a merge patch member `null` that would delete it, and any write below such a key or through the merge key itself (`/use/<</x`). These used to fail with `ErrNotFound`, and the merge patch used to succeed without changing anything. Code that checks `errors.Is(err, oneyaml.ErrNotFound)` for these edits must check `ErrAliasWrite` too. To change such a key, edit it in the merge source, where the change reaches every mapping that merges it, or `Set` an explicit key in the mapping, which overrides the merged one. Without `MergeKeys()` nothing changes. See [Merge keys](https://recursiveai.org/oneyaml/go/docs/editing/#merge-keys).
- **Breaking:** a plain integer or float written as a mapping key in a part of the document the target ignores is held to `MaxNumberDigits`, as a key that is read is, because such keys are compared as numbers to find repeated keys. A key with more digits than the bound, such as a very long integer key in a mapping read into a struct that has no field for it, used to load and is now a `*LimitError`. Values the target ignores are not limited, and neither are quoted keys. If your input has such keys, quote them, or raise the bound with `Limits(oneyaml.LimitSet{MaxNumberDigits: n})`. See [Limits](https://recursiveai.org/oneyaml/go/docs/limits/#each-limit).
- **Breaking:** `compat/yamlv3` returns an error for a type whose `UnmarshalYAML` method it cannot call, such as a type from a module that still imports `gopkg.in/yaml.v3` and takes that package's `*yaml.Node`. The error names the type and the method. Such a type used to be filled field by field, which skipped whatever its method checked or converted, as yaml.v3 itself skips a method it does not recognise; the adapter now differs from yaml.v3 here on purpose. Read such a type through a wrapper type of your own whose `UnmarshalYAML` takes the adapter's own `*yaml.Node`, or decode that part with yaml.v3. `MarshalYAML() (interface{}, error)` has the same shape in both packages and keeps working. See [Migrating from yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/#check-after-the-swap).
- Under a `MaxInputBytes` limit (`Untrusted()` sets one), the text of a patch or merge patch may be at most an eighth of it, though never less than 64 KiB and never more than the limit. Any patch text used to be read in full, into a list of operations that takes far more memory than the text. See [Bounds](https://recursiveai.org/oneyaml/go/docs/patches/#bounds).
- With `MergeKeys()`, a search through merge sources that reads more keys than `MaxAliasNodes` allows fails with a `*LimitError`, whether it serves `Get`, `Node.Find`, a patch or an edit. `Node.Lookup` reports it as `false`, like an absent key; use `Node.Find` to tell the two apart. Adding a new key with `Set` does not search merge sources.
- A map key that does not fit the key type is collected like a value that does not fit: its entry is skipped and the read goes on, instead of stopping at the first such key.
- A `json.Unmarshaler` receives the numbers inside a mapping or sequence as written, as it already did for a number on its own, so `3.14159265358979323846` is no longer rounded to a `float64` on the way.
- The integer key `0x10` followed by `16` is a duplicate-key `*SyntaxError` that names both keys, as `16` followed by `0x10` already was. It used to be a `*TypeError` saying the two numbers round to the same `float64`.
- A syntax error after a value that does not fit its target, such as the stray `}` in `{a: x}}` read into an `int` field, is reported as the `*SyntaxError` it is. It used to be hidden behind the `*TypeError` for the value.
- A key written as an alias that repeats an earlier key is reported at the alias, and the message says so. It used to be reported at the anchor.
- The message for an unclosed `[` or `{` says where it was opened, as the message for an unclosed quoted scalar does, and the one for a quoted scalar that runs into the next line says which quote to add.
- `Rename`, `SetHeadComment` and `SetLineComment` parse again only the entries around the edit where they can, as `Set` does, instead of the whole document; inside a flow collection, for one, the whole document is still parsed again. A `range` loop that edits as it goes keeps its place instead of counting again from the first item, a run of `Set` calls through a wide mapping no longer slows down with every call, renames in one mapping keep its index of keys, and a removal of many items in one pass holds one copy of the document's index. These change the work and memory an edit takes, not its result. Benchmark results come in a later update.

### Fixed

- An escaped UTF-16 surrogate pair in a double-quoted scalar, as JSON writes characters outside the Basic Multilingual Plane, is one character: `"\ud83d\ude00"` reads as 😀. It used to be refused as an escape that is not a Unicode scalar value.
- `compat/yamlv3` places head, line and foot comments on the same nodes as yaml.v3 in many more layouts: a comment after `---`, after a lone dash, after an anchor or tag on a collection, or after an explicit key (`? k # c`), and comment lines between a key and its value. These comments used to be dropped. A value's head comment is written below its line, at the column of its key, as yaml.v3 writes it. See [Migrating from yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/#check-after-the-swap) for the layouts that still differ.
- `compat/yamlv3` writes the comments inside a flow collection back where yaml.v3 writes them, and keeps the foot comment of a root scalar or a root flow collection. Both used to be read and then dropped on writing. A comment line just before the closing bracket of a flow collection is still not read.
- Adding an entry after a block scalar whose last line starts with a tab, at the end of the input, no longer changes that scalar's value: the header now strips the final line break, so the scalar reads back as it was.
- `compat/yamlv3` writes an alias used as a mapping key with a space before the colon (`*a : 1`), so the output reads back. Without it, a YAML 1.2 reader takes `*a:` as an alias named `a:`.

## [0.2.0] - 2026-10-16

### Added

- `Document.Rename` changes the key of a mapping entry in place, keeping the value, its position and its comments. It refuses a key that already exists (`ErrKeyExists`), a pointer that is not a mapping entry (`ErrNotAnEntry`), and a key whose anchor an alias uses (`ErrAnchorInUse`). See [Renaming a key](https://recursiveai.org/oneyaml/go/docs/editing/#renaming-a-key).
- `Document.SetHeadComment` and `Document.SetLineComment` write, replace or remove the comment above an entry or after a value. Text that cannot be a comment is refused with `ErrInvalidComment`. See [Comments](https://recursiveai.org/oneyaml/go/docs/editing/#comments).
- `ApplyPatch` also reads a patch written as a YAML block list of operations. Text that starts with `[` or `{` is still read as JSON only. A YAML patch with aliases, tags outside the core schema (the non-specific `!` is allowed), keys that repeat by value, or more than one document is refused, and a YAML syntax error in a patch gives its line and column. `ApplyMergePatch` still reads JSON only. See [Patches written in YAML](https://recursiveai.org/oneyaml/go/docs/patches/#patches-written-in-yaml).
- `Document` and `Node` have a `String` method, so printing one prints its YAML.
- The `YAML11Strings` option for `Parse`. By default an edit judges whether a new string can be plain by the Core schema, so `Set("/a", "yes")` writes `a: yes`, a string to this package but a boolean to a program that reads YAML 1.1, such as Kubernetes tooling built on sigs.k8s.io/yaml or code on yaml.v2. With `YAML11Strings`, new strings written by `Set`, patches and merge patches, and keys written by `Rename`, are quoted when a YAML 1.1 reader would read them as something else (`yes`, `on`, `0755`, `12:30`), as `Marshal` does. Use it when such programs also read the file. See [When the style cannot hold the value](https://recursiveai.org/oneyaml/go/docs/editing/#when-the-style-cannot-hold-the-value).
- `TypeError.More` and `TypeError.All` report every value that did not fit (see Changed). `ErrUnknownField` matches an unknown key under `KnownFields`, even when it is one of several type errors.
- `CategorySyntax`, `CategoryEncoding` and `CategoryDuplicateKey` name the values of `SyntaxError.Category`. A duplicate-key error sets `SyntaxError.Key`, `FirstLine` and `FirstColumn` to the key and the position where it first appeared.

### Changed

- **Breaking:** decoding goes on past a value that does not fit its target. `Unmarshal`, `Decoder.Decode`, `Document.Decode` and `Document.Get` skip such a value and read the rest of the document. The target of a skipped value keeps what it held: a nil pointer stays nil, an element of a slice is left zero, and a map entry is not added. One `*TypeError` then describes the first mismatch, holds the others in `More`, and lists them all in its message. The read stops at the 100th mismatch, and fewer are reported when their paths together would pass the larger of the input size and 1 MiB, as with long keys reached through aliases. A key repeated before or after an entry that did not fit is still a duplicate-key error. Syntax errors, limit errors and errors from your own hooks still stop the read at once, and are returned without the mismatches collected before them. Code that checks `err` before it uses the target needs no change. Code that relied on fields after the first bad value staying untouched must not rely on that any more. See the [`TypeError` reference](https://pkg.go.dev/oneyaml.dev/go#TypeError).
- **Breaking:** `Node.HeadComment` and `Node.LineComment` return trimmed text, as yaml.v3 does: each line starts at its `#`, without indentation or trailing blanks, and there is no final line break. They also read the comments of the root and the comment after the colon or dash of a block collection, which used to read as `""`. If you compared or wrote back the raw text, compare with the trimmed form, or use `SetHeadComment` and `SetLineComment` to change a comment. See [Comments](https://recursiveai.org/oneyaml/go/docs/editing/#comments).
- **Breaking:** two keys that are different numbers but round to the same `float64`, such as `1e-400` and `2e-400`, or `1e400` and `.inf`, are a `*TypeError` that says so, whether you read into `any` or into a `map[float64]T`. They used to be a duplicate-key `*SyntaxError`. Keys that are the same number written differently, such as `1.0` and `1.00`, or `1e400` and `10e399`, or `1e5` and `1e00005`, are still a duplicate key. To read such keys, quote them so that they are strings, or read into a map with string keys (`map[string]T`), which keeps the text of each key. If you matched these errors with `*SyntaxError` or `CategoryDuplicateKey`, match `*TypeError` too; see the [`TypeError` reference](https://pkg.go.dev/oneyaml.dev/go#TypeError).
- Any number of `range` loops over `Node.Items` or `Node.Entries` can follow edits at once. A `Document` used to follow at most three, and a further loop ended at its first edit.
- Syntax error messages say what to fix, for example `expected ',' or ']' after a flow sequence entry (is a closing ']' missing?)`. A duplicate-key message names the key and where it first appeared. Messages are written for people and may change again; match errors by type, category and kind.

### Fixed

- A plain number passed to an `UnmarshalJSON` method keeps its digits as written. `12345678901234567890.5` used to arrive rounded to `12345678901234567000`. Inside a mapping or sequence passed to `UnmarshalJSON`, integers keep every digit and floats are still rounded to `float64`.
- `compat/yamlv3`: a duplicate-key error names the key and the line of its first occurrence, as yaml.v3 words it (`mapping key "a" already defined at line 1`).

## [0.1.1] - 2026-10-16

### Fixed

- A comment on a `---` marker that also carries a tag or anchor, as in `--- !!null # note`, now stays on the marker line when `Set` or `ApplyMergePatch` gives the empty document content. It used to move below the new content.
- Replacing an empty sequence item that has a comment after its dash (`- # note`) with a mapping or sequence now keeps the comment on the dash line and writes the new collection on the lines below, lined up with the text of the sibling items. The comment used to move to the end of the collection's last line.
- Removing an entry that follows a block scalar no longer returns an `*EditError` when lines of only spaces, indented deeper than the scalar's text, sit below the removed entry. Those lines are removed with the entry, because once it is gone they would become part of the scalar's text.
- `compat/yamlv3`: the comments of a document that has a `---` marker and no content, such as `--- # note`, become the document node's `FootComment`, as yaml.v3 reads them. They used to be lost.
- `compat/yamlv3`: a comment after a dash with no value (`- # note`) becomes the `HeadComment` of the next item, as yaml.v3 reads it, so writing the `Node` back keeps it, on its own line above that item. It used to be lost.

## [0.1.0] - 2026-10-15

The first release. It has no benchmark results; those come in a later update.

### Added

- YAML 1.2 reading with the Core schema by default: `Unmarshal`, `Marshal`, `NewDecoder`, `NewEncoder`, with `encoding/json` vocabulary and `yaml` struct tags (falling back to `json` tags). Reading UTF-8, UTF-16 and UTF-32.
- Hooks for types that read and write themselves, tried in this order when reading: `Unmarshaler` (`UnmarshalYAML(oneyaml.Node) error`), the older `UnmarshalYAML(func(any) error) error` form, `encoding.TextUnmarshaler` for scalars, then `json.Unmarshaler`. When writing: `Marshaler`, `encoding.TextMarshaler`, then `json.Marshaler`; a number that `MarshalJSON` returns is written with exactly its digits, bounded by `MaxNumberDigits`. Types written for `encoding/json` need no YAML code. An `UnmarshalYAML` or `MarshalYAML` method with any other signature, such as yaml.v3's `UnmarshalYAML(*yaml.Node) error`, is a `*TypeError` instead of being skipped. See [Migrating from yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/#hooks).
- Struct tag options `omitempty`, `omitzero`, `inline` and `flow`. `omitempty` leaves out a struct whose fields are all zero, or whose `IsZero` method returns true, as yaml.v3 does; `omitzero` leaves out zero values as `encoding/json` does.
- `Parse` and `Document`: a lossless document with `Get`, `Set`, `Remove`, `Decode`, `Bytes`, `WriteTo`, stream facts and a read-only `Node` view. Edits change only the value you set, in its own style; comments and formatting elsewhere are kept byte for byte.
- `Node.Pointer`, which gives the RFC 6901 pointer of a node so that you can collect places while reading and edit afterwards, and `Node.Stale`, which reports a node read before an edit. A stale node's `Decode` and `Pointer` return an error matching `ErrStaleNode`, `Marshal` of a stale node is an error instead of `null`. A `range` over `Node.Items` or `Node.Entries` may edit the document, as a loop over a map may change the map: after an edit it finds its collection again by pointer and goes on with fresh nodes, and it ends if the collection is gone, changed kind or has no pointer. Edits that add or remove entries may make the loop skip or repeat some; collect pointers first for those. An edit, patch or merge patch that fails is rolled back completely and leaves the nodes read before it valid. `ErrNoPointer` reports a node that no pointer addresses, such as a mapping key. See [Editing](https://recursiveai.org/oneyaml/go/docs/editing/#reading-while-you-edit).
- A `Node` is not a decode target: decoding into one is a `*TypeError` that points to `Document.Get(pointer, &node)`. `Marshal` writes a `Node` as the value it holds.
- RFC 6902 JSON Patch (`ApplyPatch`, `Apply`), with `test` comparing values by the YAML data model, so `1` and `1.0` differ ([details](https://recursiveai.org/oneyaml/go/docs/patches/#the-test-operation)); RFC 6901 pointers; and RFC 7396 merge patch (`ApplyMergePatch`). All are atomic. See [Patches](https://recursiveai.org/oneyaml/go/docs/patches/).
- Limits: depth, alias expansion, number digits and patch work are bounded by default; the `Untrusted` preset adds bounds on input size, scalar size and documents per stream; `NoLimits` lifts every bound. Limit options apply in order: `Limits` changes only the `LimitSet` fields you set and keeps the rest from the options before it, and `NoBound` lifts one bound, so `Untrusted(), Limits(LimitSet{MaxDepth: 50})` keeps every untrusted bound and tightens depth. See [Limits](https://recursiveai.org/oneyaml/go/docs/limits/).
- Typed errors: `SyntaxError`, `TypeError`, `LimitError`, `EditError` and the sentinel kinds. Methods on a nil `*Document` return `ErrNilDocument` or zero values instead of panicking. Messages carry the `oneyaml:` prefix once; match errors with `errors.Is` and `errors.As`, not by their text.
- `compat/yamlv3`, a compatibility adapter for `gopkg.in/yaml.v3` with the same names and signatures and listed differences. See [Migrating from yaml.v3](https://recursiveai.org/oneyaml/go/docs/migrate-yaml-v3/).
- `compat/k8syaml`, a compatibility adapter for `sigs.k8s.io/yaml` with the same names and signatures and listed differences. See [Migrating from sigs.k8s.io/yaml](https://recursiveai.org/oneyaml/go/docs/migrate-sigs-k8s-yaml/).
- Conformance: the tests that run the [oneyaml-kit](https://github.com/recursive-ai-oss/oneyaml-kit) cases run in CI against a pinned kit revision. See [Conformance](https://recursiveai.org/oneyaml/go/docs/conformance/).
- Documentation: [Editing](https://recursiveai.org/oneyaml/go/docs/editing/), [Patches](https://recursiveai.org/oneyaml/go/docs/patches/), [Limits](https://recursiveai.org/oneyaml/go/docs/limits/), [Conformance](https://recursiveai.org/oneyaml/go/docs/conformance/), [Comparison](https://recursiveai.org/oneyaml/go/docs/comparison/) and the two migration guides.

[Unreleased]: https://github.com/recursive-ai-oss/oneyaml-go/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/recursive-ai-oss/oneyaml-go/compare/v0.3.0...v1.0.0
[0.3.0]: https://github.com/recursive-ai-oss/oneyaml-go/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/recursive-ai-oss/oneyaml-go/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/recursive-ai-oss/oneyaml-go/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/recursive-ai-oss/oneyaml-go/releases/tag/v0.1.0
