inferModels function

List<ModelDef> inferModels(
  1. String rootName,
  2. Object? json
)

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;
}