Skip to content

A misspelled or duplicated key in metadata JSON is silently ignored, so the generator emits wrong output with no diagnostic #31

Description

@matt-edmondson

What's wrong

MetadataFile.Deserialize<T> (SourceGeneratorToolkit/MetadataFile.cs:25-28, used at :119) deserializes with options that set only PropertyNameCaseInsensitive = true. Everything else stays at the System.Text.Json defaults, and two of those defaults hide authoring errors:

  • Unmapped members are skipped. A key that matches no property, such as the typo "thigns" for "things", is dropped and the property keeps its default value.
  • Duplicate properties are allowed and the last one wins. { "things": [...], "things": [] } quietly deserializes to an empty list.

Neither case raises a diagnostic. That contradicts the contract stated in CLAUDE.md: a missing or malformed file always reports, because swallowing a JsonException means malformed metadata silently generates something wrong. It also contradicts the remarks on Deserialize<T>.

Consumers can't opt in from the generator side:

  • DeserializeOptions is private.
  • GeneratorBase<T>.Generate(SourceProductionContext, MetadataSet) is sealed.

Repro

Using the existing ThingsGenerator test fixture:

Harness.Run(new ThingsGenerator(), new Dictionary<string, string>
{
    ["things.json"] = "{ \"thigns\": [ {\"name\":\"Alpha\"} ] }",
});

The run produces 1 generated source (a header and namespace Generated; with no classes) and 0 diagnostics. { "things": [ {"name":"Alpha"} ], "things": [] } behaves the same way.

The loader is stricter about syntax than about content: a stray // comment in the same file does report TST002.

Why it matters

In a hand-edited metadata file, a key typo is the most likely authoring mistake. It turns into a generator that "had nothing to emit", which is exactly the silent failure the toolkit is built to prevent. The consumer then sees missing generated types somewhere far from the cause.

Suggested fix

Add these two settings to DeserializeOptions:

UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
AllowDuplicateProperties = false,
  • Both are available in the System.Text.Json 10.x package already referenced for netstandard2.0.
  • The resulting JsonException is already caught and reported as MetadataParseFailed.
  • The exception message names the key, for example "The JSON property 'thigns' could not be mapped to any .NET member contained in type 'ThingsMetadata'".

Compatibility: a metadata file that carries a "$schema" key, or any other key the model doesn't bind, would now fail. A consumer model can opt back out per type with [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)], which takes precedence over the options. Call this out in the changelog, or explicitly tolerate $schema.

Acceptance criteria

  • An unknown key reports MetadataParseFailed, and the message names the key.
  • A duplicated key reports MetadataParseFailed.
  • A model annotated [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Skip)] still accepts extra keys.
  • Tests cover all three cases through GeneratorHarness overrides.

Verification

This was checked with a temporary MSTest against the current main:

  • The typo case and the duplicate-key case each produced 1 source and 0 diagnostics.
  • With the proposed options, JsonSerializer.Deserialize<ThingsMetadata> threw JsonException for both inputs.
  • A type-level [JsonUnmappedMemberHandling(Skip)] still accepted $schema.

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