diff --git a/CHANGELOG.md b/CHANGELOG.md
index c14e3fe..a3a8a9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,9 @@
# SmarterJSON Change Log
-> π§ Getting ready for the 1.0.0 release - sorry for the interface changes - thank you for your patience! π§
-
> β οΈ **New Interface (since 0.9.7):**
>
-> SmarterJSON **always return an `Array`** of documents:
+> SmarterJSON **always returns an `Array`** of documents.
>
> `SmarterJSON.process` / `SmarterJSON.process_file` return:
>
@@ -16,9 +14,17 @@
> β οΈ We discourage the use of `process(input).first` / `process(input)[0]` because it silently drops potential additional documents
> Please use `process_one` if you are expecting only one JSON doc, e.g. in API payloads.
-## 0.9.10 (unreleased)
+## 1.0.0 (2026-06-08)
+
+RSpec tests: 1,034
+
+- **The public interface is now stable** β `process`, `process_one`, `process_file`, `generate`, and the documented options; semantic versioning from here on.
- Unknown or wrongly-typed options now raise `ArgumentError` instead of being silently ignored, so a typo (e.g. `symbolize_names:` instead of `symbolize_keys:`) is caught immediately.
- Input tagged `ASCII-8BIT` whose bytes are valid UTF-8 (e.g. a `Net::HTTP` `response.body`) is now read as UTF-8, so its string values compare equal to UTF-8 literals; ASCII-8BIT input that is not valid UTF-8 raises `SmarterJSON::EncodingError` (pass an explicit `encoding:` for legacy encodings).
+- Object keys may now use smart/curly quotes too (e.g. JSON pasted from a word processor), not just string values.
+- `SmarterJSON.generate` accepts `allow_nan: true` to emit `NaN` / `Infinity` / `-Infinity` (JSON5-style) instead of raising, so non-finite numbers round-trip; the default still raises.
+- A numeric literal that overflows `Float` range (e.g. `1e400`) now reports a `:number_overflow` warning via `on_warning` instead of silently becoming `Infinity`.
+- `SmarterJSON.generate` is now iterative (like the parser), so serializing a deeply nested structure no longer risks `SystemStackError` β reading and writing are both depth-safe.
## 0.9.9 (2026-06-07)
- Much faster pure-Ruby parsing (the path used without the C extension) β roughly 3Γ on string-heavy data, ~2Γ on number-heavy, ~1.7Γ on object-heavy (on a YJIT-enabled Ruby). Parsed values are unchanged.
diff --git a/README.md b/README.md
index 9387069..b6761f1 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ Three things set it apart:
- Trailing commas; unquoted keys (`{host: localhost}`); single-quoted, triple-quoted (`'''β¦'''`), and quoteless string values
- Implicit root object β a config file that starts with `key: value`, no outer `{}`
- `NaN`, `Infinity`, hex (`0xFF`), leading `+` / `.`, underscores in numbers (`1_000_000`)
-- UTF-8 BOM, smart/curly quotes, Python literals (`True` / `False` / `None`), JavaScript `undefined`
+- UTF-8 BOM, smart/curly quotes (in keys and values), Python literals (`True` / `False` / `None`), JavaScript `undefined`
- Mixed CR / LF / CRLF line endings, and any Ruby-supported input encoding (via `encoding:`)
- Duplicate keys (last value wins by default; configurable)
@@ -176,7 +176,7 @@ Where a like-for-like comparison exists, here is SmarterJSON's C path against ea
| config.jsonc | **1.1Γ faster** | 1.2Γ slower | **3.6Γ faster** |
| deeply_nested | **1.2Γ faster** | **can't parse** β‘ | **4.1Γ faster** |
| github_events | β tied | 1.1Γ slower | **2.7Γ faster** |
-| string_array | **1.1Γ faster** | β tied | **1.7Γ faster** |
+| string_array | β tied | β tied | **1.6Γ faster** |
| twitter | **1.3Γ faster** | 1.2Γ slower | **3.2Γ faster** |
| usgs_earthquakes β | **1.4Γ faster** | 1.1Γ slower | **3.4Γ faster** |
| weather_berlin | **1.8Γ faster** | **1.1Γ faster** | **3.2Γ faster** |
@@ -201,7 +201,7 @@ In short: **SmarterJSON's C path matches or beats Oj/strict on every file** (app
| `decimal_precision` | `:auto` | `:auto` keeps high-precision decimals as `BigDecimal`; `:float` forces `Float`; `:bigdecimal` forces `BigDecimal` |
| `acceleration` | `true` | `true` uses the C extension when compiled and loadable; `false` forces pure Ruby (identical results) |
| `encoding` | `nil` | labels the input's encoding; `nil` keeps the input's own (no transcoding pass; see below) |
-| `on_warning` | `nil` | a callable invoked once per lenient fix applied (`:empty_slot`, `:empty_value`, `:duplicate_key`), passed a `SmarterJSON::Warning`; the return value is never changed. See below. |
+| `on_warning` | `nil` | a callable invoked once per lenient fix applied (`:empty_slot`, `:empty_value`, `:duplicate_key`, `:number_overflow`), passed a `SmarterJSON::Warning`; the return value is never changed. See below. |
## Examples
@@ -299,7 +299,7 @@ TEXT
## Nesting & untrusted input
-Both the C extension and the pure-Ruby engine are **iterative, not recursive** β they track nesting on an explicit, heap-allocated stack rather than the call stack. So deeply nested input **cannot overflow the call stack or segfault**: nesting is bounded only by available memory, the same posture as Oj (which also ships no nesting limit; the stdlib `json` caps at 100). The `deeply_nested.json` benchmark (212 MB of nesting) is handled without issue.
+Both the C extension and the pure-Ruby engine are **iterative, not recursive** β they track nesting on an explicit, heap-allocated stack rather than the call stack. So deeply nested input **cannot overflow the call stack or segfault**: nesting is bounded only by available memory, the same posture as Oj (which also ships no nesting limit; the stdlib `json` caps at 100). The `deeply_nested.json` benchmark (212 MB of nesting) is handled without issue. **`generate` is iterative too**, so serializing a deeply nested Ruby structure can't overflow the stack either β reading *and* writing are both depth-safe.
The trade-off: there is currently **no fixed nesting or input-size limit**, so extremely large or adversarially-nested untrusted input is bounded by memory (it can exhaust RAM), not by a crash. If you process untrusted input and want a hard cap, that's a planned opt-in guard β for now, size-limit upstream.
diff --git a/docs/basic_write_api.md b/docs/basic_write_api.md
index 66df12f..12de944 100644
--- a/docs/basic_write_api.md
+++ b/docs/basic_write_api.md
@@ -58,7 +58,7 @@ SmarterJSON.generate(Float::INFINITY) # raises SmarterJSON::GenerateError β
SmarterJSON.generate(Float::NAN) # raises SmarterJSON::GenerateError β non-finite Float
```
-(`GenerateError` is a kind of `SmarterJSON::Error`, so `rescue SmarterJSON::Error` catches it. `Infinity` and `NaN` are accepted on the *read* side as a leniency, but they are not valid JSON to *write*.)
+(`GenerateError` is a kind of `SmarterJSON::Error`, so `rescue SmarterJSON::Error` catches it. `Infinity` and `NaN` are accepted on the *read* side as a leniency; to *write* them, pass `allow_nan: true` and they're emitted as `NaN` / `Infinity` / `-Infinity` (JSON5-style, so SmarterJSON reads them back) β otherwise non-finite values raise, since they aren't valid strict JSON.)
By default `generate` is strict: it only writes the types above and raises on anything else. To serialize `Time`, `Date`, or your own objects, pass `coerce: true` β an unsupported value is then converted by its own `as_json` (whose result is re-emitted, so escaping/`indent`/`sort_keys` still apply) or, failing that, `to_json` (spliced verbatim):
diff --git a/docs/options.md b/docs/options.md
index 81f0d57..c85f823 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -43,7 +43,7 @@ warns.map(&:type) # => [:empty_slot]
warns.first.to_s # => "extra comma, collapsed an empty slot at line 1, col 4"
```
-The warning types are `:empty_slot` (a collapsed empty comma slot, e.g. `[1,,2]`), `:empty_value` (a key with no value, read as `null`, e.g. `{a:}`), and `:duplicate_key` (a repeated key that was dropped), plus wrapper-recovery warnings such as `:code_fence_stripped`, `:prefix_text_ignored`, `:suffix_text_ignored`, and `:wrapper_tag_stripped`. Clean input never invokes the handler. Warnings work on both the C and pure-Ruby paths, so `acceleration:` doesn't change them.
+The warning types are `:empty_slot` (a collapsed empty comma slot, e.g. `[1,,2]`), `:empty_value` (a key with no value, read as `null`, e.g. `{a:}`), `:duplicate_key` (a repeated key that was dropped), and `:number_overflow` (a numeric literal too large for `Float`, e.g. `1e400`, collapsed to `Infinity`), plus wrapper-recovery warnings such as `:code_fence_stripped`, `:prefix_text_ignored`, `:suffix_text_ignored`, and `:wrapper_tag_stripped`. Clean input never invokes the handler. Warnings work on both the C and pure-Ruby paths, so `acceleration:` doesn't change them.
### A note on `:encoding`
@@ -59,12 +59,13 @@ These options are passed to [`SmarterJSON.generate`](./basic_write_api.md) as th
| Option | Default | Explanation |
|------------|---------|-----------------------------------------------------------------------------------------------------------------------------|
+| `:allow_nan` | `false` | When `true`, non-finite `Float`/`BigDecimal` values emit the JSON5 barewords `NaN` / `Infinity` / `-Infinity` (which SmarterJSON reads back, so they round-trip). When `false` (the default), a non-finite number raises `SmarterJSON::GenerateError` β they aren't valid strict JSON. |
+| `:ascii_only` | `false` | Escape every non-ASCII character as `\uXXXX` (astral characters as a UTF-16 surrogate pair). The default emits raw UTF-8. |
+| `:coerce` | `false` | When `true`, a value that isn't natively supported is converted by its own `as_json` (the result is re-emitted, so the other options still apply) or, failing that, `to_json` (spliced verbatim). When `false` (the default), such a value raises `SmarterJSON::GenerateError`. |
| `:format` | `:json` | `:json` writes standard JSON (Hash β object, Array β array, scalar β scalar). `:ndjson` writes newline-delimited JSON: an Array becomes one element per line, any other value becomes a single line. |
| `:indent` | `0` | Spaces per nesting level for pretty-printing. `0` (the default) is compact output. Empty objects/arrays stay inline. Not allowed with `:ndjson` (a record must be a single line). |
-| `:sort_keys` | `false` | Emit object keys in sorted order (Symbol keys sorted by their string form). Useful for canonical, diff-friendly output. |
-| `:ascii_only` | `false` | Escape every non-ASCII character as `\uXXXX` (astral characters as a UTF-16 surrogate pair). The default emits raw UTF-8. |
| `:script_safe` | `false` | Escape the `/` in `` and the JS line separators U+2028 / U+2029, so output is safe to embed in an HTML `