dart_mappable
dart_mappable copied to clipboard
Mapping deep object nesting using Map with int key
My use case calls for an object hierarchy 3 levels deep. Extremely simplified example below
import 'package:flutter/material.dart';
import 'package:dart_mappable/dart_mappable.dart';
part 'main.mapper.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Matryoshka granddaughter = const Matryoshka('1', 'Granddaughter', null);
Matryoshka daughter = const Matryoshka(2, 'Daughter', null);
Matryoshka granny = Matryoshka(3, 'Granny', {daughter.id: daughter});
print('original ${granny.toString()}');
Matryoshka clone = Matryoshka.fromJson(granny.toJson());
print('clone ${clone.toString()}');
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: const Center(
child: Text('whatever'),
),
);
}
}
@MappableClass()
class Matryoshka with MatryoshkaMappable {
const Matryoshka(this.id, this.name, this.children);
final int id;
final String name;
@MappableField(hook: MappableHookNullableMap())
final Map<int, Matryoshka>? children;
static const fromMap = MatryoshkaMapper.fromMap;
static const fromJson = MatryoshkaMapper.fromJson;
}
class MappableHookNullableMap extends MappingHook {
const MappableHookNullableMap();
@override
Object? beforeEncode(Object? value) {
if (value == null) {
return null;
} else if (value is Map<int, dynamic>) {
return value.map((key, value) => MapEntry(key.toString(), value));
} else {
return value;
}
}
@override
Object? beforeDecode(Object? value) {
if (value == null) {
return null;
} else if (value is List<dynamic> && value.isEmpty) {
return null;
} else if (value is Map<String, dynamic>) {
return value.map((key, value) => MapEntry(int.parse(key), value));
} else {
return value;
}
}
}
Running above code results in
Restarted application in 1 701ms.
I/flutter (23960): original Matryoshka(id: 3, name: Granny, children: {2: Matryoshka(id: 2, name: Daughter, children: null)})
════════ Exception caught by widgets library ═══════════════════════════════════
The following _ChainedMapperException was thrown building MyApp(dirty):
MapperException: Failed to decode (Matryoshka).children(Map<int, Matryoshka>?).value(Matryoshka): Expected a value of type Map<String, dynamic>, but got type String.
The relevant error-causing widget was
MyApp
When the exception was thrown, this was the stack
#0 TypeCheck.checked
#1 ClassMapperBase.decode
#2 InterfaceMapperBase.decoder
#3 ClassMapperBase.decoder
#4 MapperBase.decodeValue
#5 _MapperContainerBase.fromValue
#6 DecodingUtil.$dec
#7 _MapDecoder2._decode.<anonymous closure>
#8 MapBase.map (dart:collection/maps.dart:82:28)
#9 _MapDecoder2._decode
#10 ResolvedType._type
#11 ResolvedType.provideTo
#12 TypePlus.provideTo
#13 _MapDecoder2.decode
#14 _MapDecoder._decode
#15 ResolvedType._type
#16 ResolvedType.provideTo
#17 TypePlus.provideTo
#18 _MapDecoder.decode
#19 MapMapper.decoder
#20 MapperBase.decodeValue
#21 _MapperContainerBase.fromValue
#22 DecodingUtil.$dec
#23 Field.decode
#24 DecodingData.dec
#25 MatryoshkaMapper._instantiate
#26 InterfaceMapperBase.decode
#27 ClassMapperBase.decode
#28 InterfaceMapperBase.decoder
#29 ClassMapperBase.decoder
#30 MapperBase.decodeValue
#31 InterfaceMapperBase.decodeJson
#32 MatryoshkaMapper.fromJson
#33 MyApp.build
#34 StatelessElement.build
#35 ComponentElement.performRebuild
#36 Element.rebuild
#37 ComponentElement._firstBuild
#38 ComponentElement.mount
... Normal element mounting (27 frames)
#65 Element.inflateWidget
#66 Element.updateChild
#67 RenderObjectToWidgetElement._rebuild
#68 RenderObjectToWidgetElement.mount
#69 RenderObjectToWidgetAdapter.attachToRenderTree.<anonymous closure>
#70 BuildOwner.buildScope
#71 RenderObjectToWidgetAdapter.attachToRenderTree
#72 WidgetsBinding.attachRootWidget
#73 WidgetsBinding.scheduleAttachRootWidget.<anonymous closure>
#77 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)
(elided 3 frames from class _Timer and dart:async-patch)
Try using 'afterEncode' instead of 'beforeEncode' in the hook.