Skip to content

Rewriting a document changes unquoted numbers: version: 1.10 becomes 1.1, zip: 01234 becomes 1234, lat: 51.507351 becomes 51.50735 #178

Description

@matt-edmondson

What's wrong

Since #174 (the fix for #137, commit 4504544), YamlSerializer's deserializer uses WithAttemptingUnquotedStringTypeDeserialization() (Frontmatter/YamlSerializer.cs:30). Plain scalars now deserialize to the smallest .NET numeric type that fits (Byte, Int16, or Single, a 32-bit float) instead of string.

SerializeYamlObject (Frontmatter/YamlSerializer.cs:135-136) then writes those numbers back out. As a result, every method that re-serializes the header changes the text of numeric-looking values: AddFrontmatter, ReplaceFrontmatter and CombineFrontmatter.

Before #174, every scalar was read as a string, so these values survived byte-for-byte. This is a regression.

Reproduction (at d8d1348)

string doc = "---\nversion: 1.10\nrelease: 1.0\nzip: 01234\nhex: 0x1F\nbig: 123456789012345678901234\nlat: 51.507351\nprice: 1234567.89\n---\nBody\n";
Frontmatter.AddFrontmatter(doc, new() { ["extra"] = "x" });

Output header:

version: 1.1
release: 1
zip: 1234
hex: 31
big: 1.2345679E+23
lat: 51.50735
price: 1234567.9
extra: x

ExtractFrontmatter(doc) reports these types:

Key Type
version Single
zip Int16
hex Byte
big Single

CombineFrontmatter(doc, AsIs, AsIs, None) produces the same output, with no merging or renaming involved.

Why it matters

Adding one unrelated key silently rewrites values the caller never touched:

  • Version strings change meaning: 1.10 becomes 1.1, and 1.0 becomes 1.
  • IDs and zip codes lose their leading zeros.
  • Coordinates and prices lose precision because they go through float.
  • Large integers become floats in scientific notation.

Hand-written frontmatter usually leaves these values unquoted (for example Hugo weight: 1.10 or version: 2.0), so the "quote it" workaround from #137 does not help existing documents.

Suggested fix / acceptance criteria

  • A re-serialized plain scalar produces the same text it was read from. Options:
    • Keep the original scalar text for numbers.
    • Parse only bool, null and int, and fall back to string whenever reformatting would change the text.
    • At minimum, parse floats as double/decimal and integers as long, and keep the raw string whenever value.ToString() differs from the source text.
  • Round-trip tests through AddFrontmatter and CombineFrontmatter show each of these values unchanged: 1.10, 1.0, 01234, 0x1F, 51.507351, 1234567.89, 123456789012345678901234, .inf, +12.
  • The Round-tripping strips quotes, so "true", "1.0", "007" and "null" come back as bool/number/null, and empty values become '' #137 tests in ScalarTypeRoundTripTests keep passing.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions