Skip to content

Decode \u surrogate pairs in quoted strings - #84

Merged
trobro merged 4 commits into
hjson:masterfrom
chiliec:fix-surrogate-pair-escape
Sep 29, 2026
Merged

trobro merged 4 commits into
hjson:masterfrom
chiliec:fix-surrogate-pair-escape

Conversation

@chiliec

@chiliec chiliec commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #83

readString decoded each \uXXXX escape as its own rune, so a UTF-16 surrogate pair ("\ud83d\ude00", the way JSON encoders write non-BMP characters) came out as two U+FFFD. Now, when a \u escape is a high surrogate and is immediately followed by a \u escape for a matching low surrogate, the two are combined with utf16.DecodeRune, which matches encoding/json. Lone or mismatched surrogates still decode to U+FFFD like they do today, and a bad hex digit after \u still returns the existing "Bad \u char" error. The hex-digit parsing moved into a small hexValue helper so it can be reused for the lookahead.

Tests: added TestUnicodeSurrogatePair, which covers a pair (lower and upper case), a pair inside an object value, and lone/reversed/unpaired surrogates.

go test -run TestUnicodeSurrogatePair .   # before the fix: FAIL (3 pair cases give "\ufffd\ufffd")
go test ./...                             # with the fix: ok

@trobro

trobro commented Sep 29, 2026

Copy link
Copy Markdown
Member

Thanks for the PR! I moved test to the asset files, they are easier to exchange between different implementations of Hjson. I removed tests that resulted in \ufffd output, that's not really a desired output, could be considered a bug.

@trobro
trobro merged commit a425bb6 into hjson:master Sep 29, 2026
4 checks passed
@chiliec

chiliec commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @trobro — the shared asset files are clearly the better home for these.

On the \ufffd cases: those are lone/unpaired surrogates, and I mapped them to U+FFFD on purpose since that's what Go's own unicode/utf8 and encoding/json do with invalid surrogates. Fine to leave out of the shared suite until Hjson settles the canonical behavior — happy to switch it to error on a lone surrogate instead if you'd prefer.

@chiliec
chiliec deleted the fix-surrogate-pair-escape branch September 29, 2026 14:47
@trobro

trobro commented Sep 30, 2026

Copy link
Copy Markdown
Member

The behavior of encoding/json differs between v1 and v2: v2 rejects inputs with invalid UTF-8. But I don't want to do that breaking change in Hjson.

@chiliec

chiliec commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Makes sense. Keeping v1's U+FFFD behaviour avoids breaking existing inputs. Thanks for merging!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unmarshal decodes a \u surrogate pair to two U+FFFD instead of the character

2 participants