Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "codable",
"request": "launch",
"type": "dart",
"toolArgs": [
"--enable-experiment=augmentations,enhanced-parts,macros,inference-update-4,num-shorthands,variance"
]
}
]
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Codable RFC

## 1.0.1

- Added augment_test directory containing versions of examples as if
a builder was used to automatically generate augmentations as indicated by
proposed annotations.

## 1.0.0

- Initial version.
2 changes: 2 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ include: package:lints/recommended.yaml
analyzer:
enable-experiment:
- macros
- augmentations

2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: 1.0.0
repository: https://github.com/schultek/codable

environment:
sdk: ^3.5.0
sdk: ^3.7.0

topics:
- codable
Expand Down
36 changes: 36 additions & 0 deletions test/augment_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Augmentation versions generally work except for:
------------------------------------------------------

generics/basic/model
box.dart // SEE NOTES in these files
box.codable.dart // augment versions can cause CRASHES in analyzer and cause weird errors in the analyzer


polymorphism/complex/model
box.dart // SEE NOTES in these files
box.codable.dart // augment versions can cause CRASHES in analyzer OR cause the weird analyzer errors





--------

And currently (2/3/2025) the compiler is completely broken for the `augment` key word and nothing compiles (or even formats)

I have filed
https://github.com/dart-lang/sdk/issues/60039

to detail the bug in the `augment` keyword


#Files used for sdk bug report issues:
----------------------------------------------------------
test\augment_test\basic\model\superbasic_for_issue_bug_report.dart
stand alone code create for https://github.com/dart-lang/sdk/issues/60039


test\augment_test\generics\basic\model\standalone_error.dart
Stand alone code created for https://github.com/dart-lang/sdk/issues/60040

62 changes: 62 additions & 0 deletions test/augment_test/basic/basic_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:codable/json.dart';
import 'package:codable/src/formats/msgpack.dart';
import 'package:codable/standard.dart';
import 'package:test/test.dart';

import 'model/person.dart';
import 'test_data.dart';

void main() {
group("basic model", () {
// Person to compare against.
final expectedPerson = PersonRaw.fromMapRaw(personTestData);

test("decodes from map", () {
// Uses the fromMap extension on Decodable to decode the map.
Person p = Person.codable.fromMap(personTestData);
expect(p, equals(expectedPerson));
});

test("encodes to map", () {
// Uses the toMap extension on SelfEncodable to encode the map.
final Map<String, dynamic> encoded = expectedPerson.toMap();
expect(encoded, equals(personTestData));
});

test("decodes from json", () {
// Uses the fromJson extension on Decodable to decode the json string.
Person p = Person.codable.fromJson(personTestJson);
expect(p, equals(expectedPerson));
});

test("encodes to json", () {
// Uses the toJson extension on SelfEncodable to encode the json string.
final String encoded = expectedPerson.toJson();
expect(encoded, equals(personTestJson));
});

test("decodes from json bytes", () {
// Uses the fromJsonBytes extension on Decodable to decode the json bytes.
Person p = Person.codable.fromJsonBytes(personTestJsonBytes);
expect(p, equals(expectedPerson));
});

test("encodes to json bytes", () {
// Uses the toJsonBytes extension on SelfEncodable to encode the json bytes.
final List<int> encoded = expectedPerson.toJsonBytes();
expect(encoded, equals(personTestJsonBytes));
});

test('decodes from msgpack bytes', () {
// Uses the fromMsgPackBytes extension on Decodable to decode the msgpack bytes.
Person p = Person.codable.fromMsgPack(personTestMsgpackBytes);
expect(p, equals(expectedPerson));
});

test('encodes to msgpack bytes', () {
// Uses the toMsgPackBytes extension on SelfEncodable to encode the msgpack bytes.
final List<int> encoded = expectedPerson.toMsgPack();
expect(encoded, equals(personTestMsgpackBytes));
});
});
}
118 changes: 118 additions & 0 deletions test/augment_test/basic/model/person.codable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// @dart = 3.8
part of 'person.dart';

augment class Person implements SelfEncodable {
const Person(this.name, this.age, this.height, this.isDeveloper, this.parent, this.hobbies, this.friends);

static const Codable<Person> codable = PersonCodable();

// ====== 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)
..encodeDouble('height', height)
..encodeBool('isDeveloper', isDeveloper)
..encodeObjectOrNull('parent', parent)
..encodeIterable('hobbies', hobbies)
..encodeIterable('friends', friends)
..end();
}
}

// Equatable stuff
augment class Person {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Person &&
runtimeType == other.runtimeType &&
name == other.name &&
age == other.age &&
height == other.height &&
isDeveloper == other.isDeveloper &&
parent == other.parent &&
hobbies.indexed.every((e) => other.hobbies[e.$1] == e.$2) &&
friends.indexed.every((e) => other.friends[e.$1] == e.$2);

@override
int get hashCode => Object.hash(name, age, height, isDeveloper, parent, hobbies, friends);
}

// toString stuff
augment class Person {
@override
String toString() {
return 'Person(name: $name, age: $age, height: $height, isDeveloper: $isDeveloper, parent: $parent, hobbies: $hobbies, friends: $friends)';
}

}



/// 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 double height;
late bool isDeveloper;
Person? parent;
late List<String> hobbies;
late List<Person> friends;

for (Object? key; (key = keyed.nextKey()) != null;) {
switch (key) {
case 'name':
name = keyed.decodeString();
case 'age':
age = keyed.decodeInt();
case 'height':
height = keyed.decodeDouble();
case 'isDeveloper':
isDeveloper = keyed.decodeBool();
case 'parent':
parent = keyed.decodeObjectOrNull(using: Person.codable);
case 'hobbies':
hobbies = keyed.decodeList();
case 'friends':
friends = keyed.decodeList(using: Person.codable);
default:
keyed.skipCurrentValue();
}
}

return Person(name, age, height, isDeveloper, parent, hobbies, friends);
}

Person decodeMapped(MappedDecoder mapped) {
return Person(
mapped.decodeString('name'),
mapped.decodeInt('age'),
mapped.decodeDouble('height'),
mapped.decodeBool('isDeveloper'),
mapped.decodeObjectOrNull('parent', using: Person.codable),
mapped.decodeList('hobbies'),
mapped.decodeList('friends', using: Person.codable),
);
}
}
69 changes: 69 additions & 0 deletions test/augment_test/basic/model/person.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// @dart = 3.8
import 'dart:convert';

import 'package:codable/core.dart';

part 'person.codable.dart';

//~@Codable(equatable:true,toString:true)
//! arguments could be used to trigger creation of equatable and toString() methods
class Person with PersonRaw {

final String name;
final int age;
final double height;
final bool isDeveloper;
final Person? parent;
final List<String> hobbies;
final List<Person> friends;

}

/// Baseline implementations for encoding and decoding a [Person] instance.
///
/// This is how we usually encode and decode models in Dart (e.g. code generated by json_serializable).
/// Its used as a baseline against checking performance and correctness of the codable implementation.
mixin PersonRaw {
static Person fromMapRaw(Map<String, dynamic> map) {
return Person(
map['name'] as String,
(map['age'] as num).toInt(),
(map['height'] as num).toDouble(),
map['isDeveloper'] as bool,
map['parent'] == null ? null : PersonRaw.fromMapRaw(map['parent'] as Map<String, dynamic>),
(map['hobbies'] as List).cast<String>(),
(map['friends'] as List).map((e) => PersonRaw.fromMapRaw(e as Map<String, dynamic>)).toList(),
);
}

static Person fromJsonRaw(String json) {
return fromMapRaw(jsonDecode(json) as Map<String, dynamic>);
}

static Person fromJsonBytesRaw(List<int> json) {
return fromMapRaw(jsonBytes.decode(json) as Map<String, dynamic>);
}

Map<String, dynamic> toMapRaw() {
final value = this as Person;
return {
'name': value.name,
'age': value.age,
'height': value.height,
'isDeveloper': value.isDeveloper,
'parent': value.parent?.toMapRaw(),
'hobbies': value.hobbies,
'friends': value.friends.map((e) => e.toMapRaw()).toList(),
};
}

String toJsonRaw() {
return jsonEncode(toMapRaw());
}

List<int> toJsonBytesRaw() {
return jsonBytes.encode(toMapRaw());
}
}

final jsonBytes = json.fuse(utf8);
20 changes: 20 additions & 0 deletions test/augment_test/basic/model/superbasic_for_issue_bug_report.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @dart = 3.7

class Person {
Person(this.name);

final String name;
}

augment class Person {
@override
String toString() {
return 'Person(name: $name)';
}
}

void main() {
var person = Person('John Doe');

print(person);
}
Loading