Skip to content

📊 Data Modeling Standards

Table of Contents

Core Standards

Model Definition Rules

  1. Always use @JsonSerializable annotation for automatic JSON serialization
  2. Use fieldRename: FieldRename.snake for consistent JSON field naming
  3. Use final fields for immutability if needed
  4. Follow PascalCase for class names
  5. Use descriptive field names with proper types
dart
@JsonSerializable(fieldRename: FieldRename.snake)
class UserModel {
  final String id;
  final String email;
  final String? firstName;
  final String? lastName;
  final DateTime createdAt;

  UserModel(
    this.id,
    this.email,
    this.firstName,
    this.lastName,
    this.createdAt,
  );

  factory UserModel.fromJson(Map<String, dynamic> json) => 
      _$UserModelFromJson(json);

  Map<String, dynamic> toJson() => _$UserModelToJson(this);
}

File Structure Requirements

  1. Include part directive for generated files
  2. Use .g.dart suffix for generated files
  3. Place models in correct directories based on scope
  4. Use consistent naming for model files
dart
import 'package:json_annotation/json_annotation.dart';

part 'user_model.g.dart'; // Required for code generation

@JsonSerializable(fieldRename: FieldRename.snake)
class UserModel {
  // ... implementation
}

Model Organization

Directory Structure Rules

  1. Feature-specific models go in feature_folder/data/models/
  2. Global/shared models go in lib/shared/data/models/
  3. Use descriptive folder names that match feature names
  4. Group related models in the same directory
  5. Use consistent file naming with _model.dart suffix
lib/
├── features/
│   └── auth/
│       └── data/
│           └── models/
│               ├── user_model.dart
│               ├── login_request_model.dart
│               └── auth_response_model.dart
└── shared/
    └── data/
        └── models/
            ├── api_response_model.dart
            ├── pagination_model.dart
            └── error_model.dart

Model Naming Conventions

  1. Use PascalCase for class names
  2. Use Model suffix for all model classes
  3. Use descriptive names that reflect the data structure
  4. Use consistent prefixes for related models (e.g., UserModel, UserProfileModel)
dart
// ✅ Good: Clear, descriptive names
class UserModel { }
class UserProfileModel { }
class LoginRequestModel { }
class ApiResponseModel<T> { }

// ❌ Bad: Unclear or inconsistent names
class User { }
class UserData { }
class Login { }
class Response { }

Implementation Guidelines

Generic Model Rules

  1. Use generics for reusable model structures
  2. Use type constraints when appropriate
  3. Provide clear type parameters in documentation
  4. Use consistent generic naming (T, E, K, V)
dart
@JsonSerializable(fieldRename: FieldRename.snake)
class ApiResponseModel<T> {
  final bool success;
  final String? message;
  final T? data;
  final List<String>? errors;

  ApiResponseModel({
    required this.success,
    this.message,
    this.data,
    this.errors,
  });

  factory ApiResponseModel.fromJson(
    Map<String, dynamic> json,
    T Function(Object? json) fromJsonT,
  ) => _$ApiResponseModelFromJson(json, fromJsonT);

  Map<String, dynamic> toJson(Object? Function(T value) toJsonT) =>
      _$ApiResponseModelToJson(this, toJsonT);
}

Nested Model Rules

  1. Use composition for complex nested structures
  2. Create separate models for nested objects
  3. Use proper serialization for nested models
  4. Handle null safety for optional nested models
dart

// order_model.dart
@JsonSerializable(fieldRename: FieldRename.snake)
class OrderModel {
  final String id;
  final UserModel customer;
  final List<OrderItemModel> items;
  final AddressModel? shippingAddress;
  final OrderStatusModel status;

  OrderModel({
    required this.id,
    required this.customer,
    required this.items,
    this.shippingAddress,
    required this.status,
  });
}

// order_item_model.dart
@JsonSerializable(fieldRename: FieldRename.snake)
class OrderItemModel {
  final String productId;
  final int quantity;
  final double unitPrice;

  OrderItemModel({
    required this.productId,
    required this.quantity,
    required this.unitPrice,
  });
}

Code Generation Rules

Build Runner Requirements

  1. Always run build_runner after creating or modifying models
  2. Use --delete-conflicting-outputs flag for clean builds
  3. Check generated files into version control
bash
# Generate code for all models
flutter pub run build_runner build --delete-conflicting-outputs

# Watch mode for development
flutter pub run build_runner watch --delete-conflicting-outputs

Generated File Management

  1. Never edit .g.dart files manually
  2. Regenerate files when model changes
dart
// ✅ Good: Proper part directive
part 'user_model.g.dart';

// ❌ Bad: Missing part directive
// Generated files won't be included

Dependencies Requirements

  1. Include json_annotation in dependencies
  2. Include json_serializable in dev_dependencies
  3. Include build_runner in dev_dependencies
  4. Use compatible versions of all packages

Common Violations

DO NOT Violate These Rules

  1. Don't forget @JsonSerializable annotation
  2. Don't skip fieldRename: FieldRename.snake configuration
  3. Don't forget part directive for generated files
  4. Don't edit .g.dart files manually
  5. Don't use inconsistent naming conventions
  6. Don't forget to run build_runner after changes
  7. Don't use var or late or dynamic for model fields
  8. Don't skip null safety annotations

ALWAYS Follow These Rules

  1. Use @JsonSerializable(fieldRename: FieldRename.snake) for all models
  2. Use final fields for immutability if needed
  3. Use ? for nullable fields
  4. Include part directive for generated files
  5. Use PascalCase for class names
  6. Use camelCase for field names
  7. Use descriptive names for classes and fields
  8. Run build_runner after model changes