Skip to content

x.json2: reject string-to-number type casting during decoding - #26220

Merged
spytheman merged 5 commits into
vlang:masterfrom
fleximus:fix_26082
Jan 1, 2026
Merged

spytheman merged 5 commits into
vlang:masterfrom
fleximus:fix_26082

Conversation

@fleximus

Copy link
Copy Markdown
Member

Honor the JSON specification which requires numeric values to be unquoted.

This fix lets json.decode[int]('"100"') correctly return a JsonDecodeError with message "Expected number, but got string", while unquoted numbers like json.decode[int]('100') continue to work as expected.

Fixes #26082

Honor the JSON specification which requires numeric values to be unquoted.

This fix lets `json.decode[int]('"100"')` correctly return a `JsonDecodeError` with message `"Expected number,
but got string"`, while unquoted numbers like `json.decode[int]('100')` continue to work as expected.
@spytheman

Copy link
Copy Markdown
Contributor

@Larsimusrex , @dy-tea what do you think?

@Larsimusrex

Larsimusrex commented Dec 31, 2025 •

Copy link
Copy Markdown
Contributor

#23424 originally requested this feature. I don't think it is really a problem because a decoder that "does more than it should" doesn't create incompatible json files. It just increases compatibility with other poorly written encoders. Another example would be surrogate decoding which is non-standart but if removed would break compatibility with pythons json encoder.

Also the json is only defines the syntax, not which datatypes should/shouldn't be allowed.

@spytheman

spytheman commented Dec 31, 2025 •

Copy link
Copy Markdown
Contributor

What do you all think of @JalonSolov's idea, of adding a strict decoding mode, that will error in such cases, while keeping the default lenient (i.e. accepting them, to keep the compatibility with existing encoders in the wild)?

@dy-tea

dy-tea commented Dec 31, 2025

Copy link
Copy Markdown
Member

I think having this as an option is good but agree it doesn't need to be the default.

Implementation:
  - `DecodeParams` struct with `strict` field
  - `decode2` function accepting params
  - Default mode: accepts quoted strings as numbers (for compatibility)
  - Strict mode: rejects quoted strings as numbers (JSON spec compliance)

Tests:
  - Additional tests for both modes
@fleximus

fleximus commented Jan 1, 2026

Copy link
Copy Markdown
Member Author

Alright, good feedback guys. I've added a strict mode flag for JSON decoding with two modes:

Mode Behavior
Default Accepts quoted strings as numbers for compatibility (e.g., "123" β†’ 123)
Strict Rejects quoted strings as numbers per JSON spec compliance

Usage looks like this:

  import x.json2 as json

  // Default mode - accepts quoted strings as numbers
  value := json.decode[int]('"123"')!        // returns 123
  arr := json.decode[[]int]('["1", 2, "3"]')! // returns [1, 2, 3]

  // Strict mode - rejects quoted strings as numbers
  value := json.decode2[int]('123', strict: true)!  // returns 123
  json.decode2[int]('"123"', strict: true) or {
      // Error: "Expected number, but got string"
  }

Comment thread vlib/x/json2/decode.v Outdated
@enghitalo

enghitalo commented Jan 1, 2026 •

Copy link
Copy Markdown
Contributor

Hmmm.... May I suggest that the strict flag not be included in the function parameters? A public function to create a decoder with configuration would be nice, more or less like how it works in the encoder.

@enghitalo

Copy link
Copy Markdown
Contributor

@spytheman Do you prefer strict to be the default, or the other way around?

@fleximus

fleximus commented Jan 1, 2026

Copy link
Copy Markdown
Member Author

@enghitalo I agree on all your points.

For the question what should be the default, I think a tolerant parsing is the better choice and have the strict one as opt-in. It's pragmatic and follows the Robustness principle also known as Postel's law. This pattern is also seen in browsers and chooses practicality over pedantry.

@spytheman

Copy link
Copy Markdown
Contributor

@enghitalo I also prefer the lenient mode (the one that is present right now) to be the default. I do not see a reason for the strict mode to be the default what so ever - it will break the backwards compatibility, and will make the overall system more brittle, since the decoder will be normally used to parse data coming from external sources.

  Add DecoderOptions and public Decoder struct to allow configurable JSON
  decoding. By default, the decoder accepts quoted strings as numbers for
  compatibility with real-world APIs (Postel's Law). Strict mode can be
  enabled for JSON spec compliance.
@spytheman

Copy link
Copy Markdown
Contributor

@fleximus please change it to:

pub fn decode[T](val string, params DecoderOptions) !T {

No deprecations are needed. Old code will still be able to call it just with:

res := decode[Type]('....')!

while new code can also do:

res := decode[Type]('....', strict: true)!

@spytheman

Copy link
Copy Markdown
Contributor

That will also remove the need for the pub struct Decoder { that currently does nothing but just stores the passed parameters.

  Replace the Decoder struct and `new_decoder()` function with a single
  `decode()` function that accepts optional DecoderOptions parameter.

  This provides a cleaner API while maintaining backward compatibility:
  - Old: `json.decode[Type](str)!`
  - New: `json.decode[Type](str, strict: true)!`

  The `@[params]` attribute on `DecoderOptions` allows omitting the second
  argument when default (lenient) behavior is desired.
@vlang vlang deleted a comment from enghitalo Jan 1, 2026

@spytheman spytheman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @fleximus πŸ™‡πŸ» .
Thanks to all reviewers too πŸ™‡πŸ»β€β™‚οΈ .

@spytheman
spytheman merged commit 6c46fdd into vlang:master Jan 1, 2026
79 checks passed
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.

x.json: string to number cast, not following JSON specfications

7 participants