autoDefaultJsonBuilder function

Builder autoDefaultJsonBuilder(
  1. BuilderOptions options
)

Replaces the stock json_serializable + source_gen:combining_builder pipeline with a single builder that runs JsonSerializableGenerator and JsonEnumGenerator itself, then post-processes the generated *FromJson functions in two ways:

  1. Every non-nullable field with no explicit @JsonKey(defaultValue:) falls back to a type-based default ('', 0, 0.0, false, const [], const {}) instead of throwing on a missing/null value.
  2. A class annotated @LenientConverter(int: true, ...) gets its matching-type fields routed through the corresponding Lenient*Converter (see lib/src/json_converters.dart) — no need to stack @LenientIntConverter()/@LenientDoubleConverter()/... A field marked @DisableLenient() is left completely untouched by both of the above (pure stock json_serializable behavior).
  3. The builder's own options.lenient section in build.yaml can turn leniency on/off per scalar type project-wide and override the fallback default value for each — see _YamlLenientConfig and _ClassLenientConfig.isEnabledFor for the precedence rules.

用一个 builder 替换掉默认的 json_serializable + source_gen:combining_builder 流程:自己跑一遍 JsonSerializableGenerator/JsonEnumGenerator,再对生成的 *FromJson 函数做两件事:

  1. 没有显式写 @JsonKey(defaultValue:) 的非空字段,统一按类型兜底默认值 (''、0、0.0、false、const []、const {}),而不是在 key 缺失/为 null 时抛异常。
  2. 类上标了 @LenientConverter(int: true, ...) 的话,匹配类型的字段会被 改写成走对应的 Lenient*Converter(见 lib/src/json_converters.dart) ——不用再叠 @LenientIntConverter()/@LenientDoubleConverter()/...。 字段标了 @DisableLenient() 的话完全不受以上两条影响(纯官方 json_serializable 行为)。
  3. build.yaml 里本 builder 的 options.lenient 段可以按标量类型全局 开关宽松转换,并覆盖各自的兜底默认值——优先级规则见 _YamlLenientConfig 和 _ClassLenientConfig.isEnabledFor。

Why not a PostProcessBuilder reading json_serializable's own .g.dart output: build_runner refuses to let one rewrite the very asset it read as input (confirmed in build_runner's post_process_build_step_impl.dart — writing back to the input id throws InvalidOutputException('Asset already exists')). Producing a second part file alongside also doesn't work: source_gen's combining builder refuses to run at all unless the exact part 'x.g.dart'; directive is present, and including it verbatim alongside a second part would duplicate every generated top-level declaration. Fully replacing the pipeline sidesteps both problems.

为什么不用 PostProcessBuilder 去读 json_serializable 自己生成的 .g.dart 再改:build_runner 不允许它把内容写回自己读取的那个输入文件(见 build_runner 源码 post_process_build_step_impl.dart——写回 input id 会抛 InvalidOutputException('Asset already exists'))。另外生成第二个 part 文件 也不行:source_gen 的 combining builder 必须看到一模一样的 part 'x.g.dart'; 才会运行,而如果把这行也保留、同时再加一个 part,会导致 每个生成的顶层声明重复定义。所以只能整体替换掉这条流水线。

Implementation

Builder autoDefaultJsonBuilder(BuilderOptions options) {
  final config = JsonSerializable.fromJson(
    _resolveJsonSerializableConfig(options.config),
  );

  final rawLenient = options.config[_lenientOptionKey];
  final yml = _YamlLenientConfig.fromOptions(
    rawLenient is Map ? rawLenient : null,
  );

  final explicitPageWidth = _readPageWidth(options.config);
  final explicitTrailingCommas = _readTrailingCommas(options.config);
  // Only bother walking the filesystem when at least one of the two is
  // actually missing — an explicit `options:` value always wins anyway.
  final detected = (explicitPageWidth == null || explicitTrailingCommas == null)
      ? _detectFormatterOptionsFromAnalysisOptions()
      : _emptyDetectedFormatterOptions;

  return PartBuilder(
    [
      _LenientAwareGenerator(JsonSerializableGenerator(config: config), yml),
      const JsonEnumGenerator(),
    ],
    '.g.dart',
    formatOutput: _formatCodeWith(
      explicitPageWidth ?? detected.pageWidth,
      explicitTrailingCommas ?? detected.trailingCommas,
    ),
  );
}