autoDefaultJsonBuilder function
- 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:
- 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. - A class annotated
@LenientConverter(int: true, ...)gets its matching-type fields routed through the correspondingLenient*Converter(seelib/src/json_converters.dart) — no need to stack@LenientIntConverter()/@LenientDoubleConverter()/... A field marked@DisableLenient()is left completely untouched by both of the above (pure stockjson_serializablebehavior). - The builder's own
options.lenientsection inbuild.yamlcan turn leniency on/off per scalar type project-wide and override the fallback default value for each — see_YamlLenientConfigand_ClassLenientConfig.isEnabledForfor the precedence rules.
用一个 builder 替换掉默认的 json_serializable + source_gen:combining_builder
流程:自己跑一遍 JsonSerializableGenerator/JsonEnumGenerator,再对生成的
*FromJson 函数做两件事:
- 没有显式写
@JsonKey(defaultValue:)的非空字段,统一按类型兜底默认值 (''、0、0.0、false、const []、const {}),而不是在 key 缺失/为 null 时抛异常。 - 类上标了
@LenientConverter(int: true, ...)的话,匹配类型的字段会被 改写成走对应的Lenient*Converter(见lib/src/json_converters.dart) ——不用再叠@LenientIntConverter()/@LenientDoubleConverter()/...。 字段标了@DisableLenient()的话完全不受以上两条影响(纯官方json_serializable行为)。 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,
),
);
}