dto_logger 2.1.1 copy "dto_logger: ^2.1.1" to clipboard
dto_logger: ^2.1.1 copied to clipboard

Debug-only logging and safe type coercion for JSON DTOs in Flutter/Dart. Generate classes from JSON, see nulls, missing keys, type mismatches and unused fields per model.

example/main.dart

// ignore_for_file: avoid_print

// Run with asserts on to see the logs (they are debug-only):
//   dart run --enable-asserts example/main.dart
import 'package:dto_logger/dto_logger.dart';

void main() {
  // Configure logging
  DtoLogConfig.enabled = true;
  DtoLogConfig.level = DtoLogLevel.verbose;
  DtoLogConfig.useColors = true;
  DtoLogConfig.useDeveloperLog = false; // Use print() for colors to show
  DtoLogConfig.showTiming = true;
  DtoLogConfig.logSuccess = true;

  print('');
  print(
      '╔══════════════════════════════════════════════════════════════════════╗');
  print(
      '║                    DTO Logger - Full Test Suite                      ║');
  print(
      '╚══════════════════════════════════════════════════════════════════════╝');
  print('');

  // Run all tests
  testSafeParser();
  testCaseConverter();
  testApiResponses();
  testGenerator();
}

// Safe Parser - Type Coercion
void testSafeParser() {
  print('');
  print(
      '┌─────────────────────────────────────────────────────────────────────┐');
  print(
      '│  TEST 1: SafeParser - Type Coercion                                 │');
  print(
      '└─────────────────────────────────────────────────────────────────────┘');

  final json = <String, dynamic>{
    'id': '123', // String → int
    'price': 99, // int → double
    'active': 'true', // String → bool
    'count': null, // null handling
    'timestamp': 1704067200, // Unix timestamp → DateTime
    'ratio': '3.14', // String → double
    'enabled': 1, // int → bool
    'disabled': 0, // int → bool
  };

  print('\nInput JSON: $json\n');

  print('Parsing Results:');
  print(
      '  id (String "123" → int):      ${SafeParser.asInt(json['id']).value}');
  print(
      '  price (int 99 → double):      ${SafeParser.asDouble(json['price']).value}');
  print(
      '  active (String "true" → bool): ${SafeParser.asBool(json['active']).value}');
  print(
      '  count (null → int):           ${SafeParser.asInt(json['count']).value}');
  print(
      '  timestamp (Unix → DateTime):  ${SafeParser.asDateTime(json['timestamp']).value}');
  print(
      '  ratio (String "3.14" → double): ${SafeParser.asDouble(json['ratio']).value}');
  print(
      '  enabled (int 1 → bool):       ${SafeParser.asBool(json['enabled']).value}');
  print(
      '  disabled (int 0 → bool):      ${SafeParser.asBool(json['disabled']).value}');

  print('\nExtension Methods on Map:');
  print('  json.safeInt("id"):           ${json.safeInt("id")}');
  print('  json.safeIntOr("count", 0):   ${json.safeIntOr("count", 0)}');
  print(
      '  json.safeBoolOr("active", false): ${json.safeBoolOr("active", false)}');
  print(
      '  json.safeDoubleOr("missing", 1.0): ${json.safeDoubleOr("missing", 1.0)}');
  print('');
}

// Case Converter
void testCaseConverter() {
  print('');
  print(
      '┌─────────────────────────────────────────────────────────────────────┐');
  print(
      '│  TEST 2: CaseConverter                                              │');
  print(
      '└─────────────────────────────────────────────────────────────────────┘');

  print('\nsnake_case → camelCase:');
  final snakeCases = [
    'user_name',
    '_id',
    '__private',
    'USER_NAME',
    'first__last',
    'api_response_data'
  ];
  for (final s in snakeCases) {
    print('  "$s" → "${CaseConverter.snakeToCamel(s)}"');
  }

  print('\ncamelCase preserved (no underscore):');
  final camelCases = ['userId', 'userName', 'isActive', 'createdAt'];
  for (final s in camelCases) {
    print('  "$s" → "${CaseConverter.snakeToCamel(s)}"');
  }

  print('\nTo PascalCase (for class names):');
  final classNames = ['user_response', 'order_item', 'api_data'];
  for (final s in classNames) {
    print('  "$s" → "${CaseConverter.snakeToPascal(s)}"');
  }

  print('\ncamelCase → snake_case:');
  final toSnake = ['userName', 'userId', 'isActive', 'createdAt'];
  for (final s in toSnake) {
    print('  "$s" → "${CaseConverter.camelToSnake(s)}"');
  }
  print('');
}

// API Response Parsing with Logging
void testApiResponses() {
  print('');
  print(
      '┌─────────────────────────────────────────────────────────────────────┐');
  print(
      '│  TEST 3: API Response Parsing (Like pretty_dio_logger)              │');
  print(
      '└─────────────────────────────────────────────────────────────────────┘');

  // Example 1: User API with type coercion warnings
  print('\n${"─" * 70}');
  print('📡 GET /api/users/123');
  print('─' * 70);

  final userJson = {
    'userId': '456', // String instead of int
    'user_name': 'Ahmed Mohamed',
    'email': null, // null
    'is_active': 'true', // String instead of bool
    'age': 28,
    'balance': '1500.50', // String instead of double
    'created_at': 1704067200, // Unix timestamp
    'profile': {
      'avatar': 'https://example.com/avatar.png',
      'bio': null, // null
      'followers': '1000', // String instead of int
    },
  };

  final user = UserResponse.fromJson(userJson);
  print(
      '→ Result: User ${user.userName}, balance: \$${user.balance}, active: ${user.isActive}');

  // Example 2: Order API with nested objects and lists
  print('\n${"─" * 70}');
  print('📡 GET /api/orders/789');
  print('─' * 70);

  final orderJson = {
    'orderId': 789,
    'orderNumber': 'ORD-2024-001',
    'total': '299.99',
    'tax': 15.0,
    'status': 'processing',
    'isPaid': 1,
    'customer': {
      'customerId': 123,
      'name': 'Mohamed Ali',
      'phone': 22,
      'email': 'mohamed@example.com',
    },
    'shippingAddress': {
      'street': '123 Main St',
      'city': 'Cairo',
      'zipCode': 'null',
      'country': 'Egypt',
    },
    'items': [
      {'productId': 1, 'name': 'iPhone 15', 'quantity': 1, 'price': 199.99},
      {'productId': 2, 'name': 'Case', 'quantity': '2', 'price': 49.99},
      {'productId': 3, 'name': 'Charger', 'quantity': 1, 'price': '50.01'},
    ],
  };

  final order = OrderResponse.fromJson(orderJson);
  print(
      '→ Result: Order #${order.orderNumber}, Total: \$${order.total}, Items: ${order.items?.length ?? 0}');

  // Example 3: API response with no issues
  print('\n${"─" * 70}');
  print('📡 GET /api/settings (Perfect data - no warnings)');
  print('─' * 70);

  final settingsJson = {
    'theme': 'dark',
    'language': 'en',
    'notifications': true,
    'fontSize': 16,
    'autoSave': true,
  };

  final settings = SettingsResponse.fromJson(settingsJson);
  print('→ Result: theme=${settings.theme}, lang=${settings.language}');

  // Example 4: Product with missing/bad data
  print('\n${"─" * 70}');
  print('📡 GET /api/products/999 (Bad API response)');
  print('─' * 70);

  final productJson = {
    'id': 999,
    // 'name': MISSING!
    'price': 'not_a_number', // Can't parse
    'inStock': 'yes', // String "yes" → bool
    'rating': null, // null
  };

  final product = ProductResponse.fromJson(productJson);
  print(
      '→ Result: Product ID=${product.id}, name=${product.name ?? "MISSING"}, price=${product.price}');

  // Example 5: List of items
  print('\n${"─" * 70}');
  print('📡 GET /api/notifications');
  print('─' * 70);

  final notificationsJson = {
    'count': '5', // String instead of int
    'unread': 3,
    'notifications': [
      {'id': 1, 'title': 'Welcome!', 'read': false},
      {
        'id': '2',
        'title': 'New message',
        'read': 'true'
      }, // String id, String bool
      {'id': 3, 'title': null, 'read': 0}, // null title, int bool
    ],
  };

  final notifications = NotificationsResponse.fromJson(notificationsJson);
  print(
      '→ Result: ${notifications.count} notifications, ${notifications.unread} unread');
  print('');
}

// JSON to Dart Generator
void testGenerator() {
  print('');
  print(
      '┌─────────────────────────────────────────────────────────────────────┐');
  print(
      '│  TEST 4: JSON to Dart Generator                                     │');
  print(
      '└─────────────────────────────────────────────────────────────────────┘');

  final json = {
    'userId': 123,
    'user_name': 'Test User',
    'email': null,
    'is_active': true,
    'profile': {
      'avatar_url': 'https://example.com/img.png',
      'bio': 'Hello world',
    },
    'tags': ['flutter', 'dart', 'mobile'],
    'orders': [
      {'id': 1, 'total': 99.5},
      {'id': 2, 'total': 149.0},
    ],
  };

  print('\nInput JSON:');
  print(json);

  final generator = JsonToDartGenerator(
    options: const GeneratorOptions(
      generateFromJson: true,
      generateToJson: true,
      generateCopyWith: true,
      addLogging: true,
      allNullable: true,
    ),
  );

  final result = generator.generate(json, 'ApiUser');

  print('\n${"═" * 70}');
  print('GENERATED CODE:');
  print('═' * 70);
  print(result.fullCode);
  print('═' * 70);

  print('\nGenerated ${result.fields.length} fields:');
  for (final field in result.fields) {
    print('  • ${field.name}: ${field.type} ← JSON key: "${field.jsonKey}"');
  }

  print('\nGenerated ${result.nestedClasses.length} nested classes:');
  for (final nested in result.nestedClasses) {
    print('  • class ${nested.name} (${nested.fields.length} fields)');
  }

  if (result.warnings.isNotEmpty) {
    print('\n⚠️ Warnings:');
    for (final warning in result.warnings) {
      print('  • $warning');
    }
  }
  print('');
}

// MODEL CLASSES: what the generator produces

class UserResponse {
  final int? userId;
  final String? userName;
  final String? email;
  final bool? isActive;
  final int? age;
  final double? balance;
  final DateTime? createdAt;
  final Profile? profile;

  UserResponse({
    this.userId,
    this.userName,
    this.email,
    this.isActive,
    this.age,
    this.balance,
    this.createdAt,
    this.profile,
  });

  factory UserResponse.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => UserResponse(
              userId: json.safeInt('userId'),
              userName: json.safeString('user_name'),
              email: json.safeString('email'),
              isActive: json.safeBool('is_active'),
              age: json.safeInt('age'),
              balance: json.safeDouble('balance'),
              createdAt: json.safeDateTime('created_at'),
              profile: json.safeObject('profile', Profile.fromJson),
            ));
  }
}

class Profile {
  final String? avatar;
  final String? bio;
  final int? followers;

  Profile({this.avatar, this.bio, this.followers});

  factory Profile.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => Profile(
              avatar: json.safeString('avatar'),
              bio: json.safeString('bio'),
              followers: json.safeInt('followers'),
            ));
  }
}

class OrderResponse {
  final int? orderId;
  final String? orderNumber;
  final double? total;
  final double? tax;
  final String? status;
  final bool? isPaid;
  final Customer? customer;
  final ShippingAddress? shippingAddress;
  final List<OrderItem>? items;

  OrderResponse({
    this.orderId,
    this.orderNumber,
    this.total,
    this.tax,
    this.status,
    this.isPaid,
    this.customer,
    this.shippingAddress,
    this.items,
  });

  factory OrderResponse.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => OrderResponse(
              orderId: json.safeInt('orderId'),
              orderNumber: json.safeString('orderNumber'),
              total: json.safeDouble('total'),
              tax: json.safeDouble('tax'),
              status: json.safeString('status'),
              isPaid: json.safeBool('isPaid'),
              customer: json.safeObject('customer', Customer.fromJson),
              shippingAddress:
                  json.safeObject('shippingAddress', ShippingAddress.fromJson),
              items: json.safeList('items', OrderItem.fromJson),
            ));
  }
}

class Customer {
  final int? customerId;
  final String? name;
  final String? phone;
  final String? email;

  Customer({this.customerId, this.name, this.phone, this.email});

  factory Customer.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => Customer(
              customerId: json.safeInt('customerId'),
              name: json.safeString('name'),
              phone: json.safeString('phone'),
              email: json.safeString('email'),
            ));
  }
}

class ShippingAddress {
  final String? street;
  final String? city;
  final String? zipCode;
  final String? country;

  ShippingAddress({this.street, this.city, this.zipCode, this.country});

  factory ShippingAddress.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => ShippingAddress(
              street: json.safeString('street'),
              city: json.safeString('city'),
              zipCode: json.safeString('zipCode'),
              country: json.safeString('country'),
            ));
  }
}

class OrderItem {
  final int? productId;
  final String? name;
  final int? quantity;
  final double? price;

  OrderItem({this.productId, this.name, this.quantity, this.price});

  factory OrderItem.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => OrderItem(
              productId: json.safeInt('productId'),
              name: json.safeString('name'),
              quantity: json.safeInt('quantity'),
              price: json.safeDouble('price'),
            ));
  }
}

class SettingsResponse {
  final String? theme;
  final String? language;
  final bool? notifications;
  final int? fontSize;
  final bool? autoSave;

  SettingsResponse(
      {this.theme,
      this.language,
      this.notifications,
      this.fontSize,
      this.autoSave});

  factory SettingsResponse.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => SettingsResponse(
              theme: json.safeString('theme'),
              language: json.safeString('language'),
              notifications: json.safeBool('notifications'),
              fontSize: json.safeInt('fontSize'),
              autoSave: json.safeBool('autoSave'),
            ));
  }
}

class ProductResponse {
  final int? id;
  final String? name;
  final double? price;
  final bool? inStock;
  final double? rating;

  ProductResponse({this.id, this.name, this.price, this.inStock, this.rating});

  factory ProductResponse.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => ProductResponse(
              id: json.safeInt('id'),
              name: json.safeString('name'),
              price: json.safeDouble('price'),
              inStock: json.safeBool('inStock'),
              rating: json.safeDouble('rating'),
            ));
  }
}

class NotificationsResponse {
  final int? count;
  final int? unread;
  final List<NotificationItem>? notifications;

  NotificationsResponse({this.count, this.unread, this.notifications});

  factory NotificationsResponse.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => NotificationsResponse(
              count: json.safeInt('count'),
              unread: json.safeInt('unread'),
              notifications:
                  json.safeList('notifications', NotificationItem.fromJson),
            ));
  }
}

class NotificationItem {
  final int? id;
  final String? title;
  final bool? read;

  NotificationItem({this.id, this.title, this.read});

  factory NotificationItem.fromJson(Map<String, dynamic> json) {
    return DtoLogger.parse(
        json,
        () => NotificationItem(
              id: json.safeInt('id'),
              title: json.safeString('title'),
              read: json.safeBool('read'),
            ));
  }
}
15
likes
160
points
556
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Debug-only logging and safe type coercion for JSON DTOs in Flutter/Dart. Generate classes from JSON, see nulls, missing keys, type mismatches and unused fields per model.

Repository (GitHub)
View/report issues

Topics

#json #logging #dto #serialization #debugging

License

MIT (license)

More

Packages that depend on dto_logger