From 35d9cdd9b1fc3a1ed155b055acccd1322d4c8ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Ku=C3=9F?= Date: Sun, 21 Apr 2024 17:17:45 +0200 Subject: [PATCH] Add support for more types for JSON serialization/deserialization (#26) * Refactor to separate converters file * Fix formatting * Add test for json collections * Add tests for dart:core types * Update fixtures * Fix formatting * Add Map support * Fix formatting --- packages/genq_test/lib/json/json_balance.dart | 12 + .../genq_test/lib/json/json_collections.dart | 22 ++ .../genq_test/test/json_collection_test.dart | 96 ++++++++ .../genq_test/test/json_core_types_test.dart | 33 +++ .../fixtures/json_basic/input.dart | 4 + .../fixtures/json_basic/input.genq.dart | 50 +++- .../fixtures/json_collections/input.dart | 27 +++ .../fixtures/json_collections/input.genq.dart | 217 ++++++++++++++++++ tool/generation_test/gen_test.go | 4 + tool/templates/json.go | 69 ++---- tool/templates/json_converters.go | 101 ++++++++ 11 files changed, 579 insertions(+), 56 deletions(-) create mode 100644 packages/genq_test/lib/json/json_balance.dart create mode 100644 packages/genq_test/lib/json/json_collections.dart create mode 100644 packages/genq_test/test/json_collection_test.dart create mode 100644 packages/genq_test/test/json_core_types_test.dart create mode 100644 tool/generation_test/fixtures/json_collections/input.dart create mode 100644 tool/generation_test/fixtures/json_collections/input.genq.dart create mode 100644 tool/templates/json_converters.go diff --git a/packages/genq_test/lib/json/json_balance.dart b/packages/genq_test/lib/json/json_balance.dart new file mode 100644 index 0000000..b0e503d --- /dev/null +++ b/packages/genq_test/lib/json/json_balance.dart @@ -0,0 +1,12 @@ +import 'package:genq/genq.dart'; + +part 'json_balance.genq.dart'; + +@Genq(json: true) +class BalanceSnapshot with _$BalanceSnapshot { + const factory BalanceSnapshot({ + required BigInt balance, + required DateTime timestamp, + required Uri uri, + }) = _BalanceSnapshot; +} diff --git a/packages/genq_test/lib/json/json_collections.dart b/packages/genq_test/lib/json/json_collections.dart new file mode 100644 index 0000000..2bdb2d4 --- /dev/null +++ b/packages/genq_test/lib/json/json_collections.dart @@ -0,0 +1,22 @@ +import 'package:genq/genq.dart'; +import 'package:genq_test/json/json_account.dart'; + +part 'json_collections.genq.dart'; + +@Genq(json: true) +class JsonCollections with _$JsonCollections { + factory JsonCollections({ + required List somethingList, + required Set somethingSet, + required Set somethingSetNullableValue, + required Map somethingMap, + required Map somethingUriMap, + }) = _JsonCollections; +} + +@Genq(json: true) +class Something with _$Something { + factory Something({ + required String name, + }) = _Something; +} diff --git a/packages/genq_test/test/json_collection_test.dart b/packages/genq_test/test/json_collection_test.dart new file mode 100644 index 0000000..4bc2400 --- /dev/null +++ b/packages/genq_test/test/json_collection_test.dart @@ -0,0 +1,96 @@ +import 'package:genq_test/json/json_collections.dart'; +import 'package:test/test.dart'; + +void main() { + test('collections fromJson', () { + final value = $JsonCollectionsFromJson({ + 'somethingList': [ + {'name': 'A'}, + {'name': 'B'}, + ], + 'somethingSet': [ + {'name': 'A'}, + {'name': 'B'}, + ], + 'somethingSetNullableValue': [ + {'name': 'A'}, + null, + {'name': 'B'}, + ], + 'somethingMap': { + 'a': {'name': 'A'}, + 'b': {'name': 'B'}, + }, + 'somethingUriMap': { + 'https://example.com': {'name': 'A'}, + 'https://example.org': {'name': 'B'}, + }, + }); + + expect(value.somethingList, + equals([Something(name: 'A'), Something(name: 'B')])); + expect(value.somethingSet, + equals({Something(name: 'A'), Something(name: 'B')})); + expect(value.somethingSetNullableValue, + equals({Something(name: 'A'), null, Something(name: 'B')})); + expect(value.somethingMap, + equals({'a': Something(name: 'A'), 'b': Something(name: 'B')})); + expect( + value.somethingUriMap, + equals({ + Uri.parse('https://example.com'): Something(name: 'A'), + Uri.parse('https://example.org'): Something(name: 'B'), + }), + ); + }); + + test('collections toJson', () { + final value = $JsonCollectionsToJson(JsonCollections( + somethingList: [Something(name: 'A'), Something(name: 'B')], + somethingSet: {Something(name: 'A'), Something(name: 'B')}, + somethingSetNullableValue: { + Something(name: 'A'), + null, + Something(name: 'B') + }, + somethingMap: {'a': Something(name: 'A'), 'b': Something(name: 'B')}, + somethingUriMap: { + Uri.parse('https://example.com'): Something(name: 'A'), + Uri.parse('https://example.org'): Something(name: 'B'), + }, + )); + + expect( + value, + equals({ + 'somethingList': [ + {'name': 'A'}, + {'name': 'B'}, + ], + 'somethingSet': [ + {'name': 'A'}, + {'name': 'B'}, + ], + 'somethingSetNullableValue': [ + {'name': 'A'}, + null, + {'name': 'B'}, + ], + 'somethingMap': { + 'a': {'name': 'A'}, + 'b': {'name': 'B'}, + }, + 'somethingUriMap': { + 'https://example.com': {'name': 'A'}, + 'https://example.org': {'name': 'B'}, + }, + }), + ); + + expect(value['somethingList'], isA()); + expect(value['somethingSet'], isA()); + expect(value['somethingSetNullableValue'], isA()); + expect(value['somethingMap'], isA()); + expect(value['somethingUriMap'], isA()); + }); +} diff --git a/packages/genq_test/test/json_core_types_test.dart b/packages/genq_test/test/json_core_types_test.dart new file mode 100644 index 0000000..b00593b --- /dev/null +++ b/packages/genq_test/test/json_core_types_test.dart @@ -0,0 +1,33 @@ +import 'package:genq_test/json/json_balance.dart'; +import 'package:test/test.dart'; + +void main() { + test('BalanceSnapshot.fromJson correctly parses datetime, bigint & uri', () { + final value = $BalanceSnapshotFromJson({ + 'timestamp': '2021-01-01T00:00:00Z', + 'balance': '12345678901234567890', + 'uri': 'https://example.com', + }); + + expect(value.timestamp, equals(DateTime.utc(2021, 1, 1))); + expect(value.balance, equals(BigInt.parse('12345678901234567890'))); + expect(value.uri, equals(Uri.parse('https://example.com'))); + }); + + test('BalanceSnapshot.toJson correctly serializes datetime and bigint', () { + final value = $BalanceSnapshotToJson(BalanceSnapshot( + timestamp: DateTime.utc(2021, 1, 1), + balance: BigInt.parse('12345678901234567890'), + uri: Uri.parse('https://example.com'), + )); + + expect( + value, + equals({ + 'timestamp': '2021-01-01T00:00:00.000Z', + 'balance': '12345678901234567890', + 'uri': 'https://example.com', + }), + ); + }); +} diff --git a/tool/generation_test/fixtures/json_basic/input.dart b/tool/generation_test/fixtures/json_basic/input.dart index 5974f1a..d36a171 100644 --- a/tool/generation_test/fixtures/json_basic/input.dart +++ b/tool/generation_test/fixtures/json_basic/input.dart @@ -9,6 +9,10 @@ class User with _$User { required int? age, required bool registered, required Address? address, + required DateTime? birthday, + required BigInt? balance, + required Object? someObject, + required dynamic someDynamic, }) = _User; factory User.fromJson(Map json) => $UserFromJson(json); diff --git a/tool/generation_test/fixtures/json_basic/input.genq.dart b/tool/generation_test/fixtures/json_basic/input.genq.dart index 4b095af..3c3cc06 100644 --- a/tool/generation_test/fixtures/json_basic/input.genq.dart +++ b/tool/generation_test/fixtures/json_basic/input.genq.dart @@ -5,6 +5,10 @@ mixin _$User { int? get age => throw UnimplementedError(); bool get registered => throw UnimplementedError(); Address? get address => throw UnimplementedError(); + DateTime? get birthday => throw UnimplementedError(); + BigInt? get balance => throw UnimplementedError(); + Object? get someObject => throw UnimplementedError(); + dynamic get someDynamic => throw UnimplementedError(); $UserCopyWith get copyWith => throw UnimplementedError(); } @@ -22,11 +26,27 @@ class _User implements User { @override final Address? address; + @override + final DateTime? birthday; + + @override + final BigInt? balance; + + @override + final Object? someObject; + + @override + final dynamic someDynamic; + _User({ required this.name, required this.age, required this.registered, required this.address, + required this.birthday, + required this.balance, + required this.someObject, + required this.someDynamic, }); @override @@ -34,7 +54,7 @@ class _User implements User { @override String toString() { - return "User(name: $name, age: $age, registered: $registered, address: $address)"; + return "User(name: $name, age: $age, registered: $registered, address: $address, birthday: $birthday, balance: $balance, someObject: $someObject, someDynamic: $someDynamic)"; } @override @@ -45,6 +65,10 @@ class _User implements User { if (!identical(other.age, age) && other.age != age) return false; if (!identical(other.registered, registered) && other.registered != registered) return false; if (!identical(other.address, address) && other.address != address) return false; + if (!identical(other.birthday, birthday) && other.birthday != birthday) return false; + if (!identical(other.balance, balance) && other.balance != balance) return false; + if (!identical(other.someObject, someObject) && other.someObject != someObject) return false; + if (!identical(other.someDynamic, someDynamic) && other.someDynamic != someDynamic) return false; return true; } @@ -56,6 +80,10 @@ class _User implements User { age, registered, address, + birthday, + balance, + someObject, + someDynamic, ); } } @@ -66,6 +94,10 @@ abstract class $UserCopyWith { int? age, bool registered, Address? address, + DateTime? birthday, + BigInt? balance, + Object? someObject, + dynamic someDynamic, }); } @@ -80,12 +112,20 @@ class _$UserCopyWithImpl implements $UserCopyWith { Object? age = genq, Object? registered = genq, Object? address = genq, + Object? birthday = genq, + Object? balance = genq, + Object? someObject = genq, + Object? someDynamic = genq, }) { return User( name: name == genq ? value.name : name as String, age: age == genq ? value.age : age as int?, registered: registered == genq ? value.registered : registered as bool, address: address == genq ? value.address : address as Address?, + birthday: birthday == genq ? value.birthday : birthday as DateTime?, + balance: balance == genq ? value.balance : balance as BigInt?, + someObject: someObject == genq ? value.someObject : someObject as Object?, + someDynamic: someDynamic == genq ? value.someDynamic : someDynamic as dynamic, ); } } @@ -96,6 +136,10 @@ User $UserFromJson(Map json) { age: json['age'] == null ? null : json['age'] as int?, registered: json['registered'] as bool, address: json['address'] == null ? null : $AddressFromJson(json['address']), + birthday: json['birthday'] == null ? null : DateTime.parse(json['birthday']), + balance: json['balance'] == null ? null : BigInt.parse(json['balance']), + someObject: json['someObject'] == null ? null : json['someObject'] as Object?, + someDynamic: json['someDynamic'] as dynamic, ); } @@ -105,6 +149,10 @@ Map $UserToJson(User obj) { 'age': obj.age == null ? null : obj.age!, 'registered': obj.registered, 'address': obj.address == null ? null : $AddressToJson(obj.address!), + 'birthday': obj.birthday == null ? null : obj.birthday!.toIso8601String(), + 'balance': obj.balance == null ? null : obj.balance!.toString(), + 'someObject': obj.someObject == null ? null : obj.someObject!, + 'someDynamic': obj.someDynamic, }; } diff --git a/tool/generation_test/fixtures/json_collections/input.dart b/tool/generation_test/fixtures/json_collections/input.dart new file mode 100644 index 0000000..7c7168b --- /dev/null +++ b/tool/generation_test/fixtures/json_collections/input.dart @@ -0,0 +1,27 @@ +import 'package:genq/genq.dart'; + +part 'input.genq.dart'; + +@Genq(json: true) +class User with _$User { + factory User({ + required List
addressesList, + required List addressesListNullable, + required Set
addressesSet, + required Set addressesSetNullable, + required Map addressesMap, + required Map addressesUriMap, + required Map addressesMapNullableValue, + }) = _User; + + factory User.fromJson(Map json) => $UserFromJson(json); +} + +@Genq(json: true) +class Address with _$Address { + factory Address({ + required String street, + }) = _Address; + + factory Address.fromJson(Map json) => $AddressFromJson(json); +} diff --git a/tool/generation_test/fixtures/json_collections/input.genq.dart b/tool/generation_test/fixtures/json_collections/input.genq.dart new file mode 100644 index 0000000..32e1b42 --- /dev/null +++ b/tool/generation_test/fixtures/json_collections/input.genq.dart @@ -0,0 +1,217 @@ +part of 'input.dart'; + +mixin _$User { + List
get addressesList => throw UnimplementedError(); + List get addressesListNullable => throw UnimplementedError(); + Set
get addressesSet => throw UnimplementedError(); + Set get addressesSetNullable => throw UnimplementedError(); + Map get addressesMap => throw UnimplementedError(); + Map get addressesUriMap => throw UnimplementedError(); + Map get addressesMapNullableValue => throw UnimplementedError(); + + $UserCopyWith get copyWith => throw UnimplementedError(); +} + +class _User implements User { + @override + final List
addressesList; + + @override + final List addressesListNullable; + + @override + final Set
addressesSet; + + @override + final Set addressesSetNullable; + + @override + final Map addressesMap; + + @override + final Map addressesUriMap; + + @override + final Map addressesMapNullableValue; + + _User({ + required this.addressesList, + required this.addressesListNullable, + required this.addressesSet, + required this.addressesSetNullable, + required this.addressesMap, + required this.addressesUriMap, + required this.addressesMapNullableValue, + }); + + @override + $UserCopyWith get copyWith => _$UserCopyWithImpl(this); + + @override + String toString() { + return "User(addressesList: $addressesList, addressesListNullable: $addressesListNullable, addressesSet: $addressesSet, addressesSetNullable: $addressesSetNullable, addressesMap: $addressesMap, addressesUriMap: $addressesUriMap, addressesMapNullableValue: $addressesMapNullableValue)"; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! User) return false; + if (!const DeepCollectionEquality().equals(other.addressesList, addressesList)) return false; + if (!const DeepCollectionEquality().equals(other.addressesListNullable, addressesListNullable)) return false; + if (!const DeepCollectionEquality().equals(other.addressesSet, addressesSet)) return false; + if (!const DeepCollectionEquality().equals(other.addressesSetNullable, addressesSetNullable)) return false; + if (!const DeepCollectionEquality().equals(other.addressesMap, addressesMap)) return false; + if (!const DeepCollectionEquality().equals(other.addressesUriMap, addressesUriMap)) return false; + if (!const DeepCollectionEquality().equals(other.addressesMapNullableValue, addressesMapNullableValue)) return false; + return true; + } + + @override + int get hashCode { + return Object.hash( + runtimeType, + addressesList, + addressesListNullable, + addressesSet, + addressesSetNullable, + addressesMap, + addressesUriMap, + addressesMapNullableValue, + ); + } +} + +abstract class $UserCopyWith { + User call({ + List
addressesList, + List addressesListNullable, + Set
addressesSet, + Set addressesSetNullable, + Map addressesMap, + Map addressesUriMap, + Map addressesMapNullableValue, + }); +} + +class _$UserCopyWithImpl implements $UserCopyWith { + final _$User value; + + _$UserCopyWithImpl(this.value); + + @override + User call({ + Object? addressesList = genq, + Object? addressesListNullable = genq, + Object? addressesSet = genq, + Object? addressesSetNullable = genq, + Object? addressesMap = genq, + Object? addressesUriMap = genq, + Object? addressesMapNullableValue = genq, + }) { + return User( + addressesList: addressesList == genq ? value.addressesList : addressesList as List
, + addressesListNullable: addressesListNullable == genq ? value.addressesListNullable : addressesListNullable as List, + addressesSet: addressesSet == genq ? value.addressesSet : addressesSet as Set
, + addressesSetNullable: addressesSetNullable == genq ? value.addressesSetNullable : addressesSetNullable as Set, + addressesMap: addressesMap == genq ? value.addressesMap : addressesMap as Map, + addressesUriMap: addressesUriMap == genq ? value.addressesUriMap : addressesUriMap as Map, + addressesMapNullableValue: addressesMapNullableValue == genq ? value.addressesMapNullableValue : addressesMapNullableValue as Map, + ); + } +} + +User $UserFromJson(Map json) { + return User( + addressesList: List.of(json['addressesList']).map((e) => $AddressFromJson(e)).toList(), + addressesListNullable: List.of(json['addressesListNullable']).map((e) => e == null ? null : $AddressFromJson(e)).toList(), + addressesSet: Set.of(json['addressesSet']).map((e) => $AddressFromJson(e)).toSet(), + addressesSetNullable: Set.of(json['addressesSetNullable']).map((e) => e == null ? null : $AddressFromJson(e)).toSet(), + addressesMap: Map.of(json['addressesMap']).map((key, value) => MapEntry(key as String, $AddressFromJson(value))), + addressesUriMap: Map.of(json['addressesUriMap']).map((key, value) => MapEntry(Uri.parse(key), $AddressFromJson(value))), + addressesMapNullableValue: Map.of(json['addressesMapNullableValue']).map((key, value) => MapEntry(key as String, value == null ? null : $AddressFromJson(value))), + ); +} + +Map $UserToJson(User obj) { + return { + 'addressesList': obj.addressesList.map((e) => $AddressToJson(e)).toList(), + 'addressesListNullable': obj.addressesListNullable.map((e) => e == null ? null : $AddressToJson(e)).toList(), + 'addressesSet': obj.addressesSet.map((e) => $AddressToJson(e)).toList(), + 'addressesSetNullable': obj.addressesSetNullable.map((e) => e == null ? null : $AddressToJson(e)).toList(), + 'addressesMap': Map.of(obj.addressesMap).map((key, value) => MapEntry(key, $AddressToJson(value))), + 'addressesUriMap': Map.of(obj.addressesUriMap).map((key, value) => MapEntry(key.toString(), $AddressToJson(value))), + 'addressesMapNullableValue': Map.of(obj.addressesMapNullableValue).map((key, value) => MapEntry(key, value == null ? null : $AddressToJson(value))), + }; +} + +mixin _$Address { + String get street => throw UnimplementedError(); + + $AddressCopyWith get copyWith => throw UnimplementedError(); +} + +class _Address implements Address { + @override + final String street; + + _Address({ + required this.street, + }); + + @override + $AddressCopyWith get copyWith => _$AddressCopyWithImpl(this); + + @override + String toString() { + return "Address(street: $street)"; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! Address) return false; + if (!identical(other.street, street) && other.street != street) return false; + return true; + } + + @override + int get hashCode { + return Object.hash( + runtimeType, + street, + ); + } +} + +abstract class $AddressCopyWith { + Address call({ + String street, + }); +} + +class _$AddressCopyWithImpl implements $AddressCopyWith { + final _$Address value; + + _$AddressCopyWithImpl(this.value); + + @override + Address call({ + Object? street = genq, + }) { + return Address( + street: street == genq ? value.street : street as String, + ); + } +} + +Address $AddressFromJson(Map json) { + return Address( + street: json['street'] as String, + ); +} + +Map $AddressToJson(Address obj) { + return { + 'street': obj.street, + }; +} \ No newline at end of file diff --git a/tool/generation_test/gen_test.go b/tool/generation_test/gen_test.go index 6a167c3..d518412 100644 --- a/tool/generation_test/gen_test.go +++ b/tool/generation_test/gen_test.go @@ -84,3 +84,7 @@ func TestConstFactory(t *testing.T) { func TestConstFactoryPrivateConstructor(t *testing.T) { testGenOutput(t, "const_factory_private_constructor") } + +func TestJsonCollections(t *testing.T) { + testGenOutput(t, "json_collections") +} diff --git a/tool/templates/json.go b/tool/templates/json.go index 60f0144..2e8bf49 100644 --- a/tool/templates/json.go +++ b/tool/templates/json.go @@ -41,32 +41,9 @@ func typeFromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName st return customFromJson.Value.Reference.String() + "(" + valueName + ")", false } - if typeRef.Name == "String" { - return valueName, true - } - - if typeRef.Name == "int" { - return valueName, true - } - - if typeRef.Name == "double" { - return valueName, true - } - - if typeRef.Name == "bool" { - return valueName, true - } - - if typeRef.Name == "num" { - return valueName, true - } - - if typeRef.Name == "List" { - return "List.of(" + valueName + ").map((e) => " + typeFromJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "e") + ").toList()", false - } - - if typeRef.Name == "Set" { - return "Set.of(" + valueName + ").map((e) => " + typeFromJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "e") + ").toSet()", false + converter, ok := converters[typeRef.Name] + if ok { + return converter.FromJson(annotation, typeRef, valueName) } // For every other type, we call the generated ${Type}FromJson method. @@ -81,9 +58,14 @@ func typeFromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName st return "$" + typeRef.Name + "FromJson(" + strings.Join(params, ", ") + ")", false } -func typeToJsonNullable(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { +func typeToJsonNullable(annotation GenqAnnotation, typeRef GenqNamedType, valueName string, requiresNonNullCast bool) string { + nonNullCast := "" + if requiresNonNullCast { + nonNullCast = "!" + } + if typeRef.Optional { - return valueName + " == null ? null : " + typeToJson(annotation, typeRef, valueName+"!") + return valueName + " == null ? null : " + typeToJson(annotation, typeRef, valueName+nonNullCast) } else { return typeToJson(annotation, typeRef, valueName) } @@ -95,32 +77,9 @@ func typeToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName stri return customToJson.Value.Reference.String() + "(" + valueName + ")" } - if typeRef.Name == "String" { - return valueName - } - - if typeRef.Name == "int" { - return valueName - } - - if typeRef.Name == "double" { - return valueName - } - - if typeRef.Name == "bool" { - return valueName - } - - if typeRef.Name == "num" { - return valueName - } - - if typeRef.Name == "List" { - return valueName + ".map((e) => " + typeToJson(GenqAnnotation{}, typeRef.GenericTypes[0], "e") + ").toList()" - } - - if typeRef.Name == "Set" { - return valueName + ".map((e) => " + typeToJson(GenqAnnotation{}, typeRef.GenericTypes[0], "e") + ").toSet()" + converter, ok := converters[typeRef.Name] + if ok { + return converter.ToJson(annotation, typeRef, valueName) } // For every other type, we call the generated ${Type}ToJson method. @@ -162,7 +121,7 @@ func templateToJson(str []string, params GenqClassDeclaration) []string { } } - str = append(str, indent(4, fmt.Sprintf("'%s': %s,", jsonKey, typeToJsonNullable(param.Annotation, param.ParamType, "obj."+param.Name)))) + str = append(str, indent(4, fmt.Sprintf("'%s': %s,", jsonKey, typeToJsonNullable(param.Annotation, param.ParamType, "obj."+param.Name, true)))) } str = append(str, indent(2, fmt.Sprintf("};"))) str = append(str, fmt.Sprintf("}")) diff --git a/tool/templates/json_converters.go b/tool/templates/json_converters.go new file mode 100644 index 0000000..39dae5f --- /dev/null +++ b/tool/templates/json_converters.go @@ -0,0 +1,101 @@ +package templates + +import ( + . "genq/parser" +) + +// map of all converters per type +var converters = map[string]jsonConverter{ + "String": noopConverter{}, + "int": noopConverter{}, + "double": noopConverter{}, + "bool": noopConverter{}, + "num": noopConverter{}, + "dynamic": noopConverter{}, + "Object": noopConverter{}, + "DateTime": dateTimeConverter{}, + "BigInt": bigIntConverter{}, + "Uri": uriConverter{}, + "List": mappableConverter{mappableType: "List", methodFromJson: "toList", methodToJson: "toList"}, + "Set": mappableConverter{mappableType: "Set", methodFromJson: "toSet", methodToJson: "toList"}, + "Map": mapConverter{}, +} + +type jsonConverter interface { + ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string + FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) +} + +type noopConverter struct { +} + +func (d noopConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + return valueName +} + +func (d noopConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + return valueName, true +} + +type mappableConverter struct { + mappableType string + methodFromJson string + methodToJson string +} + +func (d mappableConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + return valueName + ".map((e) => " + typeToJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "e", false) + ")." + d.methodToJson + "()" +} + +func (d mappableConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + return d.mappableType + ".of(" + valueName + ").map((e) => " + typeFromJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "e") + ")." + d.methodFromJson + "()", false +} + +type dateTimeConverter struct { +} + +func (d dateTimeConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + return valueName + ".toIso8601String()" +} + +func (d dateTimeConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + return "DateTime.parse(" + valueName + ")", false +} + +type bigIntConverter struct { +} + +func (d bigIntConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + return valueName + ".toString()" +} + +func (d bigIntConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + return "BigInt.parse(" + valueName + ")", false +} + +type uriConverter struct { +} + +func (d uriConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + return valueName + ".toString()" +} + +func (d uriConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + return "Uri.parse(" + valueName + ")", false +} + +type mapConverter struct{} + +func (d mapConverter) ToJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) string { + keyType := typeToJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "key", false) + valueType := typeToJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[1], "value", false) + + return "Map.of(" + valueName + ").map((key, value) => MapEntry(" + keyType + ", " + valueType + "))" +} + +func (d mapConverter) FromJson(annotation GenqAnnotation, typeRef GenqNamedType, valueName string) (string, bool) { + keyType := typeFromJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[0], "key") + valueType := typeFromJsonNullable(GenqAnnotation{}, typeRef.GenericTypes[1], "value") + + return "Map.of(" + valueName + ").map((key, value) => MapEntry(" + keyType + ", " + valueType + "))", false +}