I have an object I am wanting to save to local storage, but whenever I write that object to storage I get the error shown in the title.
Here's the full stack trace
[VERBOSE-2:ui_dart_state.cc(198)] Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, double>' in type cast
#0 new Product.fromJson (package:app/src/objects/product.dart:27:47)
#1 new ShoppingListProduct.fromJson (package:app/src/objects/shopping_list_product.dart:18:29)
#2 ShoppingListHandler.readShoppingListProducts.<anonymous closure> (package:app/src/handlers/shopping_list_handler.dart:65:38)
#3 MappedListIterable.elementAt (dart:_internal/iterable.dart:413:31)
#4 ListIterator.moveNext (dart:_internal/iterable.dart:342:26)
#5 new _GrowableList._ofEfficientLengthIterable (dart:core-patch/growable_array.dart:189:27)
#6 new _GrowableList.of (dart:core-patch/growable_array.dart:150:28)
#7 new List.of (dart:core-patch/array_patch.dart:51:28)
And here's the handler class which is used for writing, reading, and updating the shoppingListProduct class.
class ShoppingListHandler {
ShoppingListHandler._privateConstructor();
static final ShoppingListHandler instance = ShoppingListHandler._privateConstructor();
static File? _file;
static const _fileName = 'shopping_list_file.txt';
// Get the data file
Future<File> get file async {
if (_file != null) return _file!;
_file = await _initFile();
return _file!;
}
// Initialize file
Future<File> _initFile() async {
final _directory = await getApplicationDocumentsDirectory();
final _path = _directory.path;
// Check if file exists
File file = File('$_path/$_fileName');
if(await file.exists() == false){
await file.create(recursive: true);
}
return file;
}
// Users
static Set<ShoppingListProduct> _shoppingListSet = {};
Future<void> writeShoppingList(ShoppingListProduct shoppingListProduct) async {
final File fl = await file;
_shoppingListSet.add(shoppingListProduct);
// Now convert the set to a list as the jsonEncoder cannot encode
// a set but a list.
final _shoppingListMap = _shoppingListSet.map((e) => e.toJson()).toList();
await fl.writeAsString(jsonEncode(_shoppingListMap));
}
Future<List<ShoppingListProduct>> readShoppingListProducts() async {
final File fl = await file;
final _content = await fl.readAsString();
List<dynamic> _jsonData = [];
if(_content.isNotEmpty){
_jsonData = jsonDecode(_content);
}
final List<ShoppingListProduct> _shoppingListProducts = _jsonData
.map(
(e) => ShoppingListProduct.fromJson(e as Map<String, dynamic>),
)
.toList();
return _shoppingListProducts;
}
Future<void> deleteShoppingListProduct(ShoppingListProduct shoppingListProduct) async {
final File fl = await file;
_shoppingListSet.removeWhere((e) => e == shoppingListProduct);
final _shoppingListMap = _shoppingListSet.map((e) => e.toJson()).toList();
await fl.writeAsString(jsonEncode(_shoppingListMap));
}
Future<void> updateShoppingListProduct({
required String key,
required ShoppingListProduct updatedShoppingListProduct,
}) async {
_shoppingListSet.removeWhere((e) => e.ID == updatedShoppingListProduct.ID);
await writeShoppingList(updatedShoppingListProduct);
}
}
the shoppingListProduct class only has 3 parameters. an ID which is just an integer. a Product parameter which is a Product object from the Product class. and a ticked boolean.
I believe the problem is that inside the Product class I have a "price" object which is a map with a string and a double. Here's the product class.
class Product extends Equatable{
const Product({
required this.user,
required this.id,
required this.name,
required this.image,
required this.brandName,
required this.productPrices,
});
final String user;
final int id;
final String name;
final String image;
final String brandName;
final Map<String, double> productPrices;
Product.fromJson(Map<String, dynamic> map)
: user = (map['user'] as String),
id = (map['id'] as int ).toInt(),
name = (map['name'] as String),
image = (map['image'] as String),
brandName = (map['brandName'] as String),
productPrices = (map['productPrices'] as Map<String, double>);
Map<String, dynamic> toJson(){
return {
'user': user,
'id': id,
'name': name,
'image': image,
'brandName': brandName,
'productPrices': productPrices,
};
}
@override
String toString(){
return 'Product:\n\tId: $id\n\tName: $name\n\tImage: $image\n\tBrandName: $brandName\n\tProductPrices: $productPrices';
}
@override
List<Object> get props => [id, name, image, brandName, productPrices];
}
And here's the shoppingListProduct class which is used in the saving process.
class ShoppingListProduct extends Equatable {
ShoppingListProduct({
required this.ID,
required this.product,
required this.ticked,
});
final int ID;
final Product product;
late bool ticked;
ShoppingListProduct.fromJson(Map<String, dynamic> map)
: ID = (map['ID'] as int).toInt(),
product = Product.fromJson(map['product']),
ticked = (map['ticked'] as bool);
Map<String, dynamic> toJson() {
return {
'ID': ID,
'product': product,
'ticked': ticked,
};
}
@override
String toString() {
return 'ShoppingListProduct:\n\tID: $ID\n\tProduct: $product\n\tTicked: $ticked';
}
@override
List<Object> get props => [ID, product, ticked];
}
CodePudding user response:
In Dart, type parameters are preserved at runtime and are considered during type checks. So a Map<String, dynamic>
is not a subtype of Map<String, double>
and cannot be cast to it, which is the error that you're seeing here.
Since the JSON parser in Dart can't know the right type of the map beforehand, all JSON objects are parsed as a Map<String, dynamic>
. Since you're sure that the map will only contain doubles as values, you can use (map['productPrices'] as Map<String, dynamic>).cast<String, double>()
in the fromJson
constructor instead.
The cast
method on map will return a view over the original map. This view will do casts during lookups instead of for the whole map you did it with the as
operator.