to use => import 'package:dart_helper_utils/dart_helper_utils.dart';

// Type Definitions

ElementConverter<T>: T Function(Object? element)
// Signature for custom element conversion functions. Used as 'converter', 'keyConverter', 'valueConverter', 'elementConverter' parameters.

// Class Definition

ConvertObject
// Static utilities for type conversion from dynamic inputs. Supports nested Map/List access. Provides throwing (to<Type>) and null-returning (tryTo<Type>) methods.
// Dependencies: dart:developer (for logging in try* methods), package:dart_helper_utils/src/exceptions/exceptions.dart (ParsingException)

  Method Pattern: to<Type>(...) / tryTo<Type>(...)
  // Converts 'object' (optionally after Map/List access via 'mapKey'/'listIndex') to target <Type>.
  // Target Types <Type>: String1, Num, Int, BigInt, Double, Bool, DateTime, Uri, Map<K, V>, Set<T>, List<T>.

  Common Parameters:
    object: dynamic - Input value.
    mapKey: dynamic? - Optional key for map access before conversion.
    listIndex: int? - Optional index for list access before conversion.
    defaultValue: T? - Fallback value used differently by to/try variants (see Returns).
    converter: ElementConverter<T>? - Custom conversion function for the primary value.

  Type-Specific Parameters:
    (Num, Int, Double): format: string? [numeric format pattern], locale: string? [locale code]
    (DateTime): format: string? [date pattern | 'auto'], locale: string? [locale code], useCurrentLocale: bool = false, utc: bool = false
    (Map<K, V>): keyConverter: ElementConverter<K>?, valueConverter: ElementConverter<V>?, decodeInput: bool = false [If true and input is String, attempts JSON decode]
    (Set<T>, List<T>): elementConverter: ElementConverter<T>?, decodeInput: bool = false [If true and input is String, attempts JSON decode]

  Returns (to<Type>): T
    // Converted value.
    // Error Handling: Throws ParsingException on conversion failure. If resolved input is null, returns non-null 'defaultValue' if provided, otherwise throws ParsingException.
  Returns (tryTo<Type>): T?
    // Converted value.
    // Error Handling: Returns 'defaultValue' if input resolves to null OR conversion fails. Returns null if no 'defaultValue' provided in those cases. Logs conversion errors via dart:developer.log.

  Operational Notes:
    Performance: toBigInt/tryToBigInt overhead for large numbers. Collection conversions scale with element count/converter complexity.
    Side Effects: tryTo<Type> methods log conversion errors.

  Invocation Examples (ConvertObject):
    String s = ConvertObject.toString1(123); // "123"
    int? i = ConvertObject.tryToInt("abc"); // null (error logged)
    Map<String, int> m = ConvertObject.toMap<String, int>('{"a": 1, "b": 2}'); // {'a': 1, 'b': 2} (decodeInput defaults to true for Map/List/Set)
    List<String> l = ConvertObject.toList<String>([1, 2], elementConverter: (e) => "Item $e"); // ["Item 1", "Item 2"]
    try { ConvertObject.toInt(null); } catch (e) { /* ParsingException */ }

// Extensions

Extension ConvertObjectIterableEx<E> on Iterable<E>
// Accesses element at 'index' (+ optional nested 'innerMapKey'/'innerIndex'), converts using ConvertObject.to<Type>. Non-null Iterable required.
  Method Pattern: get<Type>(index: int, ...)
    Parameters: index: int, [innerMapKey: dynamic?, innerIndex: int?], [type-specific to<Type> params], [defaultValue: T?], [converters: Function?]
    Returns: T - Converted value.
    Error Handling: Throws RangeError (index out of bounds), ParsingException (conversion failure via ConvertObject.to<Type>).
  Invocation Example:
    List data = [10, "20", {"value": 30}];
    String val1 = data.getString(1); // "20"
    int val2 = data.getInt(2, innerMapKey: "value"); // 30

Extension ConvertObjectIterableNEx<E> on Iterable<E>?
// Null-safe access at 'index' (or 'altIndexes') (+ optional nested 'innerMapKey'/'innerIndex'), converts using ConvertObject.tryTo<Type>.
  Method Pattern: tryGet<Type>(index: int, ...)
    Parameters: index: int, [altIndexes: List<int>?], [innerMapKey: dynamic?, innerIndex: int?], [type-specific tryTo<Type> params], [defaultValue: T?], [converters: Function?]
    Returns: T? - Converted value or null.
    Error Handling: Returns null if iterable is null, index/altIndexes invalid, or conversion fails.
  Invocation Example:
    List? data = [10, "20", {"value": 30}];
    String? val1 = data.tryGetString(1); // "20"
    int? val2 = data.tryGetInt(2, innerMapKey: "value"); // 30
    String? val3 = data.tryGetString(5); // null
    List? nData = null;
    int? val4 = nData.tryGetInt(0); // null

Extension ConvertObjectSetNEx<E> on Set<E>?
  Method: convertTo<R>() -> Set<R>
    Action: Converts non-null input Set using ConvertObject.toSet<R> (elements via ConvertObject.toType<R> by default).
    Error Handling: Throws ParsingException if input Set is null or element conversion fails.
  Invocation Example:
    Set<dynamic> s1 = {1, '2', 3.0};
    Set<int> s2 = s1.convertTo<int>(); // {1, 2, 3}

Extension ConvertObjectListNEx<E> on List<E>?
  Method: convertTo<R>() -> List<R>
    Action: Converts non-null input List using ConvertObject.toList<R> (elements via ConvertObject.toType<R> by default).
    Error Handling: Throws ParsingException if input List is null or element conversion fails.
  Invocation Example:
    List<dynamic> l1 = [1, '2', 3.0];
    List<String> l2 = l1.convertTo<String>(); // ["1", "2", "3.0"]

Extension ConvertObjectMapEx<K, V> on Map<K, V>
// Accesses value at 'key' (or 'altKeys') (+ optional nested 'innerKey'/'innerListIndex'), converts using ConvertObject.to<Type>. Non-null Map required.
  Method Pattern: get<Type>(key: K, ...)
    Parameters: key: K, [altKeys: List<K>?], [innerKey: dynamic?, innerListIndex: int?], [type-specific to<Type> params], [defaultValue: T?], [converters: Function?]
    Returns: T - Converted value.
    Error Handling: Throws ParsingException if key/altKeys not found, associated value is null, or conversion fails.
  Method: parse<T, K2, V2>(key: K, converter: T Function(Map<K2, V2> json)) -> T
    Action: Retrieves map value via getMap<K2, V2>(key), then invokes 'converter'.
    Error Handling: Throws ParsingException if getMap fails or 'converter' throws.
  Invocation Example:
    Map data = {'id': 123, 'name': 'test', 'details': {'valid': true}};
    int id = data.getInt('id'); // 123
    bool isValid = data.getBool('details', innerKey: 'valid'); // true
    // class MyData { final bool valid; MyData.fromJson(Map<String, dynamic> json): valid = json.getBool('valid'); }
    // MyData details = data.parse<MyData, String, dynamic>('details', MyData.fromJson);

Extension ConvertObjectMapNEx<K, V> on Map<K, V>?
// Null-safe access at 'key' (or 'altKeys') (+ optional nested 'innerKey'/'innerListIndex'), converts using ConvertObject.tryTo<Type>.
  Method Pattern: tryGet<Type>(key: K, ...)
    Parameters: key: K, [altKeys: List<K>?], [innerKey: dynamic?, innerListIndex: int?], [type-specific tryTo<Type> params], [defaultValue: T?], [converters: Function?]
    Returns: T? - Converted value or null.
    Error Handling: Returns null if map is null, key/altKeys not found, value is null, or conversion fails.
  Method: tryParse<T, K2, V2>(key: K, converter: T Function(Map<K2, V2> json)) -> T?
    Action: Attempts retrieval via tryGetMap<K2, V2>(key), then invoke 'converter' if successful.
    Error Handling: Returns null if tryGetMap fails or 'converter' throws.
  Invocation Example:
    Map? data = {'id': 123, 'name': 'test', 'details': {'valid': true}};
    int? id = data.tryGetInt('id'); // 123
    bool? isValid = data.tryGetBool('details', innerKey: 'valid'); // true
    String? missing = data.tryGetString('missing'); // null
    Map? nData = null;
    int? nId = nData.tryGetInt('id'); // null
    // MyData? details = data.tryParse<MyData, String, dynamic>('details', MyData.fromJson); // Assumes MyData.fromJson exists
    // MyData? missingDetails = data.tryParse<MyData, String, dynamic>('other', MyData.fromJson); // null

// Top-Level Functions

to<Type>(...) / tryTo<Type>(...)
// Direct alias for ConvertObject.to<Type>(...) / ConvertObject.tryTo<Type>(...). Identical parameters and behavior.
Invocation Example:
  int i = toInt("100");
  String? s = tryToString(null); // null

toType<T>(object: dynamic) -> T
// Generic conversion using ConvertObject.to<Type> for T in {bool, int, BigInt, double, num, String, DateTime}.
// Error Handling: Throws ParsingException (null input, conversion fail, unsupported T).
Invocation Example:
  double d = toType<double>("12.34"); // 12.34

tryToType<T>(object: dynamic) -> T?
// Generic conversion using ConvertObject.tryTo<Type> for T in {bool, int, BigInt, double, num, String, DateTime}.
// Error Handling: Returns null (null input, conversion fails). Throws ParsingException (unsupported T, unexpected delegate error).
Invocation Example:
  int? i = tryToType<int>("56"); // 56
  bool? b = tryToType<bool>(null); // null

## Core Concepts

## Type Conversion Functions

### Basic Conversion Functions

The package provides two variants for each type conversion:

- `toX()`: Returns non-null value or default value (if provided), or throws `ParsingException`
- `tryToX()`: Returns nullable value or default value (if provided)

Available conversion functions:

```dart
String toString1(dynamic object, {...})
String? tryToString(dynamic object, {...})
num toNum(dynamic object, {...})
num? tryToNum(dynamic object, {...})
int toInt(dynamic object, {...})
int? tryToInt(dynamic object, {...})
double toDouble(dynamic object, {...})
double? tryToDouble(dynamic object, {...})
bool toBool(dynamic object, {...})
bool? tryToBool(dynamic object, {...})
BigInt toBigInt(dynamic object, {...})
BigInt? tryToBigInt(dynamic object, {...})
DateTime toDateTime(dynamic object, {...})
DateTime? tryToDateTime(dynamic object, {...})
Uri toUri(dynamic object, {...})
Uri? tryToUri(dynamic object, {...})
```

### Collection Conversion Functions

For collections:

```dart
List<T> toList<T>(dynamic object, {...})
List<T>? tryToList<T>(dynamic object, {...})
Set<T> toSet<T>(dynamic object, {...})
Set<T>? tryToSet<T>(dynamic object, {...})
Map<K,V> toMap<K,V>(dynamic object, {...})
Map<K,V>? tryToMap<K,V>(dynamic object, {...})
```

### Advanced Parameters

All conversion functions support these common parameters:

```dart
{
  dynamic mapKey,         // Extract value from a Map using this key
  int? listIndex,         // Extract element from a List at this index
  T? defaultValue,        // Fallback value if conversion fails
  ElementConverter<T>? converter // Custom conversion logic
}
```

Number-specific parameters:

```dart
{
  String? format,         // Format string for parsing numbers (e.g., "#,##0.00")
  String? locale,         // Locale for number formatting
}
```

DateTime-specific parameters:

```dart
{
  String? format,         // Format for parsing dates (e.g., "yyyy-MM-dd")
  bool autoDetectFormat = false, // Try multiple date formats automatically
  bool useCurrentLocale = false, // Use device's locale for parsing
  bool utc = false        // Parse as UTC time rather than local time
}
```

## Extension Methods

### Map Extensions

```dart
// Type-safe field access
String name = map.getString('name');
int age = map.getInt('age');
double price = map.getDouble('price');
bool isActive = map.getBool('isActive');
DateTime created = map.getDateTime('createdAt');
List<String> tags = map.getList<String>('tags');

// Nullable variants
String? description = map.tryGetString('description');
int? count = map.tryGetInt('count');
```

Advanced Map extension features:

```dart
// Alternative keys (fallbacks)
String name = map.getString('fullName', altKeys: ['name', 'userName']);

// Nested field access with innerKey
String city = map.getString('address', innerKey: 'city');

// Default values
int quantity = map.getInt('quantity', defaultValue: 1);

// Custom conversion
DateTime parsedDate = map.getDateTime(
  'date',
  format: 'yyyy-MM-dd',
  defaultValue: DateTime.now(),
);

// Parse objects directly
Product product = map.parse('product', Product.fromJson);
Product? optionalProduct = map.tryParse('product', Product.fromJson);
```

### List Extensions

```dart
// Access elements safely
String first = list.getString(0);
int second = list.getInt(1);
bool third = list.getBool(2);

// Nullable variants
String? name = list.tryGetString(0);

// Alternative indexes (fallbacks)
int value = list.getInt(0, altIndexes: [1, 2]);

// Type conversion
List<int> intList = dynamicList.convertTo<int>();
```

## Advanced Features

### 1. Nested Property Access

Access nested properties using the `innerKey` parameter:

```dart
// Access nested property
String city = userData.getString('address', innerKey: 'city');

// innerKey can be any valid key for the nested map
// Note: Dot notation in keys is NOT supported for nested access
```

### 2. Alternative Keys/Indexes

Provide fallback keys or indexes to try if the primary one fails:

```dart
// For Maps:
String name = userData.getString('fullName', altKeys: ['name', 'userName']);

// For Lists:
String value = items.getString(0, altIndexes: [1, 2]);
```

### 3. Default Values

Provide default values for when conversion fails:

```dart
int quantity = json.getInt('quantity', defaultValue: 1);
DateTime date = json.getDateTime('date', defaultValue: DateTime.now());
List<String> tags = json.getList('tags', defaultValue: []);
```

### 4. Smart Collection Type Conversion

When converting collections, elements are automatically converted to the target type:

```dart
// List with mixed types will convert all elements to int
List<dynamic> mixed = [1, "2", 3.5];
List<int> ints = mixed.convertTo<int>(); // [1, 2, 3]

// Automatic handling of nested JSON strings
List<int> numbers = tryToList<int>("[1, 2, 3]");
```

### 5. Object Parsing Helpers

Parse nested objects directly:

```dart
// Direct model creation from JSON
User user = json.parse('user', User.fromJson);
User? optionalUser = json.tryParse('user', User.fromJson);

// Parse lists of objects
List<Product> products = json.getList<Product>(
  'products',
  elementConverter: (e) => Product.fromJson(toMap(e)),
  defaultValue: [],
);
```

## Creative Usage Patterns

### 1. Eliminate Null Checks with Default Values

Instead of:

```dart
final count = json.tryGetInt('count') ?? 0;
```

Use defaultValue:

```dart
final count = json.getInt('count', defaultValue: 0);
```

### 2. Nested Field Access with Elegant Fallbacks

Access a nested field that could be in multiple parent objects:

```dart
final buyersCount = json.getInt(
  'itemInsight',
  altKeys: ['packageInsight'],
  innerKey: 'BuyersCount',
  defaultValue: 0,
);
```

### 3. Collection Simplification with Default Values

Ensure you always work with non-null collections:

```dart
final items = json.getList<Item>(
  'items',
  elementConverter: (e) => Item.fromJson(toMap(e)),
  defaultValue: [],
);
// Now 'items' is guaranteed to be a valid list, never null
```

### 4. Multi-Level Fallback Strategy

Create a robust fallback chain:

```dart
final price = json.getDouble(
  'price',
  altKeys: ['cost', 'value'],
  defaultValue: json.tryGetDouble('defaultPrice') ?? 0.0,
);
```

### 5. Custom Conversions for Special Cases

```dart
final status = json.getString(
  'status',
  converter: (value) => value.toString().toUpperCase(),
  defaultValue: 'UNKNOWN',
);
```

### 6. Convert from Multiple Collection Types

Handle data that could come in different formats:

```dart
// Works with direct List or String JSON representation
List<int> ids = toList<int>(dataObject, defaultValue: []);
```

## Best Practices

1. **Use Type-Safe Accessors**: Always use the type-specific getters rather than accessing raw JSON.
2. **Provide Default Values**: Use the `defaultValue` parameter for non-nullable fields to avoid null checks.
3. **Use Alternative Keys**: When field names might vary, use `altKeys` to handle multiple possibilities.
4. **For Complex Nested Structures**: Use the appropriate nesting approach with `innerKey` and `altKeys`.
5. **Prefer Non-Throwing Methods**: When possible, use default values instead of relying on try/catch blocks.
6. **Combine Extensions**: Chain multiple extensions for more complex data transformations.

---

### Sample JSON

```json
{
  "id": 12345,
  "profile": {
    "userName": "john_doe",
    "first_name": "John",
    "email_address": "john@example.com",
    "is_verified": true,
    "age": "34",
    "preferences": {
      "theme": "dark",
      "notifications": true
    }
  },
  "stats": {
    "activityScore": 85,
    "premium_tier": 2
  },
  "orders": [
    {
      "order_id": "ORD-001",
      "amount": "125.50",
      "status": "completed",
      "items": 3
    },
    {
      "order_id": "ORD-002",
      "amount": 89.99,
      "items": 1
    }
  ],
  "payment_methods": {
    "primary": {
      "type": "credit_card",
      "last4": "4242"
    },
    "secondary": {
      "type": "paypal",
      "email": "john@example.com"
    }
  },
  "recent_addresses": [
    {
      "type": "home",
      "street": "123 Main St",
      "city": "Anytown",
      "zip": 12345
    },
    {
      "type": "work",
      "street": "456 Office Blvd",
      "zip": "98765"
    }
  ],
  "membership": {
    "subscribed": true,
    "expiryDate": "2025-06-30",
    "planDetails": {
      "name": "Gold",
      "price": 99.99,
      "features": ["premium_support", "priority_shipping", "exclusive_deals"]
    }
  },
  "last_login": "2025-03-15T14:30:45Z",
  "account_balance": "1250.75",
  "notifications": [
    {"message": "Welcome back!", "read": false},
    {"message": "New offer available", "read": true}
  ],
  "preferences": null
}
```

### Model Implementation

```dart
class User extends Equatable {
  final int id;
  final String username;
  final String firstName;
  final String email;
  final bool isVerified;
  final int age;
  final String theme;
  final bool notificationsEnabled;
  final int activityScore;
  final int premiumTier;
  final List<Order> orders;
  final PaymentMethod primaryPaymentMethod;
  final PaymentMethod? secondaryPaymentMethod;
  final List<Address> addresses;
  final Membership membership;
  final DateTime lastLogin;
  final double accountBalance;
  final List<Notification> notifications;
  final Map<String, dynamic> preferences;

  const User({
    required this.id,
    required this.username,
    required this.firstName,
    required this.email,
    required this.isVerified,
    required this.age,
    required this.theme,
    required this.notificationsEnabled,
    required this.activityScore,
    required this.premiumTier,
    required this.orders,
    required this.primaryPaymentMethod,
    this.secondaryPaymentMethod,
    required this.addresses,
    required this.membership,
    required this.lastLogin,
    required this.accountBalance,
    required this.notifications,
    required this.preferences,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      // Basic field access with direct conversion
      id: json.getInt('id'),

      // Nested field access with innerKey
      username: json.getString('profile', innerKey: 'userName'),

      // Alternative keys with fallbacks
      firstName: json.getString(
        'profile',
        innerKey: 'first_name',
        altKeys: ['given_name', 'forename']
      ),

      // Nested field with combined key names
      email: json.getString(
        'profile',
        innerKey: 'email_address',
        altKeys: ['email', 'contact_email']
      ),

      // Boolean conversion from various formats
      isVerified: json.getBool('profile', innerKey: 'is_verified'),

      // String to int conversion
      age: json.getInt('profile', innerKey: 'age'),

      // Deep nested field with default value
      theme: json.getString(
        'profile',
        innerKey: 'preferences',
        mapKey: 'theme',
        defaultValue: 'light'
      ),

      // Deep nested field with boolean conversion
      notificationsEnabled: json.getBool(
        'profile',
        innerKey: 'preferences',
        mapKey: 'notifications',
        defaultValue: false
      ),

      // Multiple nested levels with alternative keys
      activityScore: json.getInt(
        'stats',
        innerKey: 'activityScore',
        altKeys: ['activity_score', 'activity']
      ),

      // Converting with snake_case vs camelCase
      premiumTier: json.getInt(
        'stats',
        innerKey: 'premium_tier',
        altKeys: ['premiumTier', 'tier']
      ),

      // Converting list of objects with element converter
      orders: json.getList<Order>(
        'orders',
        elementConverter: (e) => Order.fromJson(toMap(e)),
        defaultValue: [],
      ),

      // Nested object conversion
      primaryPaymentMethod: json.parse(
        'payment_methods',
        (methods) => PaymentMethod.fromJson(methods.getMap('primary')),
      ),

      // Optional nested object conversion
      secondaryPaymentMethod: json.tryParse(
        'payment_methods',
        (methods) => PaymentMethod.fromJson(methods.getMap('secondary')),
      ),

      // List of objects with filtering
      addresses: json.getList<Address>(
        'recent_addresses',
        elementConverter: (e) => Address.fromJson(toMap(e)),
        defaultValue: [],
      ),

      // Complex nested object
      membership: json.parse('membership', Membership.fromJson),

      // DateTime conversion
      lastLogin: json.getDateTime(
        'last_login',
        format: "yyyy-MM-dd'T'HH:mm:ss'Z'",
        defaultValue: DateTime.now()
      ),

      // String to double conversion
      accountBalance: json.getDouble('account_balance'),

      // List of simple objects
      notifications: json.getList<Notification>(
        'notifications',
        elementConverter: (e) => Notification.fromJson(toMap(e)),
        defaultValue: [],
      ),

      // Handling potentially null field with default empty map
      preferences: json.getMap<String, dynamic>(
        'preferences',
        defaultValue: {},
      ),
    );
  }

  @override
  List<Object?> get props => [/* properties */];
}

// Supporting model classes would be defined similarly
```

This example demonstrates:

1. Handling basic and nested fields
2. Using alternative keys for flexibility
3. Type conversions across various formats
4. Default values to handle missing data
5. Handling collections of objects
6. Deep nested property access
7. Optional vs required fields
8. Special formatting for dates and numbers

With this comprehensive example, you can see how the utility functions work together to create a robust, type-safe parsing system that handles edge cases elegantly.
