content: import 'package:codable_dart/common.dart';
  import 'package:codable_dart/core.dart';
  import 'package:codable_dart/extended.dart';
  
  import 'async_value.dart';
  
  class Person implements SelfEncodable {
    Person(this.name, this.age, this.parent, this.friends, this.comments);
  
    static const Codable<Person> codable = PersonCodable();
  
    final String name;
    final int age;
    final AsyncValue<Person?> parent;
    final AsyncValue<List<AsyncValue<Person>>> friends;
    final Stream<String>? comments;
  
    // @override
    // bool operator ==(Object other) =>
    //     identical(this, other) ||
    //     other is Person &&
    //         runtimeType == other.runtimeType &&
    //         name == other.name &&
    //         age == other.age &&
    //         parent == other.parent &&
    //         friends == other.friends &&
    //         comments == other.comments;
  
    // @override
    // int get hashCode => Object.hash(name, age, parent, friends, comments);
  
    @override
    String toString() {
      return 'Person(name: $name, age: $age, parent: $parent, friends: $friends)';
    }
  
    // ====== Codable Code ======
    // Keep in mind that the below code could also easily be generated by macros or a code generator.
  
    @override
    void encode(Encoder encoder) {
      encoder.encodeKeyed()
        ..encodeString('name', name)
        ..encodeInt('age', age)
        ..encodeObject('parent', parent, using: personNullAsync)
        ..encodeObject('friends', friends, using: personListAsync)
        ..encodeStreamOrNull('comments', comments)
        ..end();
    }
  }
  
  final personNullAsync = Person.codable.orNull.async();
  final personListAsync = Person.codable.async().list().async();
  
  /// Codable implementation for [Person].
  ///
  /// This extends the [SelfCodable] class for a default implementation of [encode] and
  /// implements the [decode] method.
  class PersonCodable extends SelfCodable<Person> {
    const PersonCodable();
  
    @override
    Person decode(Decoder decoder) {
      return switch (decoder.whatsNext()) {
        // If the format prefers mapped decoding, use mapped decoding.
        DecodingType.mapped || DecodingType.map => decodeMapped(decoder.decodeMapped()),
        // If the format prefers keyed decoding or is non-self describing, use keyed decoding.
        DecodingType.keyed || DecodingType.unknown => decodeKeyed(decoder.decodeKeyed()),
        _ => decoder.expect('mapped or keyed'),
      };
    }
  
    Person decodeKeyed(KeyedDecoder keyed) {
      late String name;
      late int age;
      late AsyncValue<Person?> parent;
      late AsyncValue<List<AsyncValue<Person>>> friends;
      Stream<String>? comments;
  
      for (Object? key; (key = keyed.nextKey()) != null;) {
        switch (key) {
          case 'name':
            name = keyed.decodeString();
          case 'age':
            age = keyed.decodeInt();
          case 'parent':
            parent = keyed.decodeObject(using: personNullAsync);
          case 'friends':
            friends = keyed.decodeObject(using: personListAsync);
          case 'comments':
            comments = keyed.decodeStreamOrNull();
          default:
            keyed.skipCurrentValue();
        }
      }
  
      return Person(name, age, parent, friends, comments);
    }
  
    Person decodeMapped(MappedDecoder mapped) {
      return Person(
        mapped.decodeString('name'),
        mapped.decodeInt('age'),
        mapped.decodeObject('parent', using: personNullAsync),
        mapped.decodeObject('friends', using: personListAsync),
        mapped.decodeStreamOrNull('comments'),
      );
    }
  }