Skip to content

Opt-in strict union parsing - #97

Open
Szer wants to merge 1 commit into
Dolfik1:masterfrom
Szer:feat/strict-parsing
Open

Szer wants to merge 1 commit into
Dolfik1:masterfrom
Szer:feat/strict-parsing

Conversation

@Szer

@Szer Szer commented Sep 12, 2026

Copy link
Copy Markdown

DiscriminatedUnionConverter.resolveObject falls back to shape-based overlap scoring whenever a discriminator value doesn't match any declared case, and Array.maxBy silently picks the first declared case. Its payload is then deserialized from JSON that's missing the case's real fields, leaving them null even though F# records/DUs are non-nullable by declaration. Real example: Telegram Bot API 10.3 added RichBlockButtons, but RichBlock has no Buttons case, so

{"type":"buttons","buttons":[{"text":"GET MORE","style":"success","url":"https://example.com"}],"align":"center"}

silently deserializes as RichBlock.Paragraph with Text = null, which NREs downstream. This PR adds an opt-in strict mode that fails loudly instead.

  • Level 1 - unknown discriminator fails: when the JSON carries an [<Always>] discriminator value that matches no case, strict mode raises JsonException instead of scoring. Shape-based scoring is untouched and still used for DUs that have no discriminator at all, and non-strict mode is byte-for-byte unchanged.
  • Level 2 - required fields validated: after a case is deserialized, strict mode checks that every public, non-option, reference-typed property of the payload record is non-null, raising JsonException naming the missing field and case if not.
  • Funogram.Tools.strictOptions: a new JsonSerializerOptions sitting next to the existing options, built from the same converters with the strict flag flipped on. Nested unions inherit it automatically since they resolve converters through the same JsonSerializerOptions.
  • Not validated: nested records inside a case's payload (out of scope for this PR).
  • Default (non-strict) behaviour is unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EMiBp5SyqwpeiZSYDSn7xZ

@Szer
Szer force-pushed the feat/strict-parsing branch from 8b3e1d0 to 3eab2bc Compare September 12, 2026 23:40
…ired fields raise)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMiBp5SyqwpeiZSYDSn7xZ
@Szer
Szer force-pushed the feat/strict-parsing branch from 3eab2bc to 8fd212a Compare September 12, 2026 23:50
@ForNeVeR

Copy link
Copy Markdown
Contributor

Note that the whole bot infrastructure (Funogram.Tools.parseJsonStream) will still use the old options object. Are we ok with that?

@Szer

Szer commented Sep 14, 2026

Copy link
Copy Markdown
Author

Note that the whole bot infrastructure (Funogram.Tools.parseJsonStream) will still use the old options object. Are we ok with that?

that's a backward compatible change, hence old options object behaves exactly as before.
Some bots are okay with "best effort parsing" mode, some don't. If a change can be a non-breaking one, why not?

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.

2 participants