inferModels function
Infers a set of ModelDefs from a decoded JSON value. The root object
becomes a model named rootName (PascalCase); nested objects become their
own models named after the key that holds them. Returns the root model first
followed by nested models (de-duplicated by name).
Type inference: int/double/bool/String from primitives, DateTime
for ISO-8601-looking strings, List<T> from arrays (element type from the
first element), nested object/array-of-object types as generated models, and
dynamic for null or empty arrays.
Implementation
List<ModelDef> inferModels(String rootName, Object? json) {
final models = <ModelDef>[];
final seen = <String>{};
void addModel(String name, Map<String, dynamic> map) {
if (seen.contains(name)) return;
seen.add(name);
final fields = <FieldSpec>[];
final nested = <_PendingModel>[];
map.forEach((key, value) {
final fieldName = Naming.camel(key);
if (fieldName.isEmpty) return;
final inferred = _inferField(fieldName, key, value, nested);
fields.add(inferred);
});
models.add(ModelDef(name, fields));
// Recurse into nested objects after registering this model.
for (final p in nested) {
addModel(p.name, p.map);
}
}
if (json is Map<String, dynamic>) {
addModel(Naming.pascal(rootName), json);
} else if (json is List && json.isNotEmpty && json.first is Map) {
// A top-level array of objects: model the element.
addModel(Naming.pascal(rootName), json.first as Map<String, dynamic>);
} else {
// Not an object — emit a placeholder model with a single `value` field.
models.add(ModelDef(
Naming.pascal(rootName),
[FieldSpec('value', _scalarType(json), false)],
));
}
return models;
}