Json 解码 Sharedpreferences 返回 List<dynamic> 而不是 List<Contact>

Posted

技术标签:

【中文标题】Json 解码 Sharedpreferences 返回 List<dynamic> 而不是 List<Contact>【英文标题】:JsonDecoding Shared Preferences returning List<dynmaic> instead of List<Contact> 【发布时间】:2021-09-24 13:54:08 【问题描述】:

我有一个列表 contacts 存储 Contact 这是从 contact_services 包获取的类型

List<Contact> contacts = [];

我使用SharedPrefrences 存储列表,首先jsonEncode 它然后保存它

void saveContacts() async
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('list', jsonEncode(contacts));

但是当我尝试加载列表时,它返回异常type 'List&lt;dynamic&gt;' is not a subtype of type 'List&lt;Contact&gt;'

void loadList() async
    SharedPreferences prefs = await SharedPreferences.getInstance();
    contacts = await jsonDecode(prefs.getString('list'));


更新代码以突出显示更改:

这是整个saveContacts 函数:

  void saveContacts() async
    SharedPreferences prefs = await SharedPreferences.getInstance();
    var json = jsonEncode(contacts, toEncodable: (e) => e.toMap());
    await prefs.setString('list', jsonEncode(json));
  

但我收到错误消息:The method 'toMap' isn't defined for the type 'Object'.

contacts只是一个存储Contact类型的List

List&lt;Contact&gt; contact;

最初contact 存储在一个单独的文件夹(全局)中以便于访问,但这并不影响jsonEncode 的结果

【问题讨论】:

这能回答你的问题吗? How to Deserialize a list of objects from json in flutter 否,因为问题不在于访问 josn 字符串中的信息,而是如何将其从字符串转换回联系人类型。 -@ישו אוהב אותך 【参考方案1】:

您需要转换为 Map&lt;String, dynamic&gt;,然后将 json 转换为您的 Contact。这是一个将Contact 列表转换为 json 文本并作为联系人列表再次读取的工作示例。 (转到下面代码中的parseContacts 函数)。

import 'dart:convert';

class Contact 
  final String name;
  final String phone;

  Contact(this.name, this.phone);

  Contact.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        phone = json['phone'];

  Map<String, dynamic> toJson() => 
        'name': name,
        'phone': phone,
      ;


void main() 
  List<Contact> contacts = [];
  for (int i = 0; i < 10; i++) 
    contacts.add(Contact("name$i", i.toString()));
  
  
  var jsonText = jsonEncode(contacts);
  print(jsonText);

  var contactsFromJson = parseContacts(jsonText);
  for (var item in contactsFromJson) 
    print(item.name);
  


List<Contact> parseContacts(String jsonText) 
  final parsed = jsonDecode(jsonText).cast<Map<String, dynamic>>();

  return parsed.map<Contact>((json) => Contact.fromJson(json)).toList();

更多详情,您可以阅读https://flutter.dev/docs/cookbook/networking/background-parsing#complete-example的示例

【讨论】:

嘿,Contact 是从插件contacts_service 获取的类型,因此它不是一个josn 文件,您不能使用fromJson 从中提取数据。顺便说一句,Contact 只包含一个像这样的电话号码的字符串:+123456789,但我必须将其转换为Contact 才能在插件的其余部分中使用它。 -@ישו אוהב אותך @AnanSaadi:你是说这个pub.dev/packages/contacts_service 包吗?您可以尝试以下代码进行编码:var json = jsonEncode(contacts, toEncodable: (e) =&gt; e.toMap()); 然后在parseContacts 中将Contact.fromJson(json)).toList(); 更改为Contact.fromMap(json)).toList();,您需要使用库中的Contact 对象。见相关代码:github.com/lukasgit/flutter_contacts/blob/master/lib/… 我收到错误消息:error: The method 'toMap' isn't defined for the type 'Object'. - @ישו אוהב אותך【参考方案2】:

更新:

我原本以为问题可以通过casting 解决,但是潜入json.dart,我发现了以下cmets:

json.encode/jsonEncode
  /// If value contains objects that are not directly encodable to a JSON
  /// string (a value that is not a number, boolean, string, null, list or a map
  /// with string keys), the [toEncodable] function is used to convert it to an
  /// object that must be directly encodable.
  ///
  /// If [toEncodable] is omitted, it defaults to a function that returns the
  /// result of calling `.toJson()` on the unencodable object.
JsonEncoder.convert 内部由jsonEncode/json.encode使用:
  /// Directly serializable values are [num], [String], [bool], and [Null], as
  /// well as some [List] and [Map] values. For [List], the elements must all be
  /// serializable. For [Map], the keys must be [String] and the values must be
  /// serializable.
  ///
  /// If a value of any other type is attempted to be serialized, the
  /// `toEncodable` function provided in the constructor is called with the value
  /// as argument. The result, which must be a directly serializable value, is
  /// serialized instead of the original value.

TLDR;如果value 参数包含一个对象,如果该类没有toJson 实现,我们还必须提供toEncodable。而且由于在您的情况下您无法为该类提供toJson 实现,因此正确编码对象的唯一方法是提供toEncodable。我已经实现了一个示例供您理解:

import 'dart:convert';

class Class 
  String name;
  Class(this.name);

  @override
  String toString() => name;


void main() 
  final list = <Class>[
    Class('a'),
    Class('b'),
    Class('c'),
  ];

  final encoded = json.encode(list, toEncodable: (c) 
    if (c is Class) 
      return 'name': c.name;
    
  );
  print(encoded);
  
  // Save and retreive `encoded`
  
  print((json.decode(encoded) as List).map((map) 
    if (map.containsKey('name')) 
      return Class(map['name']);
    
  ));

附录:不要提供toEncodable 的自定义实现并从json 创建Contact,而是尝试已经定义的toMapfromMap


原答案:

您可以在List 上使用cast 方法转换列表类型。

所以首先使用as将解码后的数据转换为List,然后在其上应用cast以获得所需的类型。

contacts = await (jsonDecode(prefs.getString('list')) as List).cast<Contact>();

【讨论】:

我遇到了一个异常type 'String' is not a subtype of type 'Contact' in type cast -@happy_san 你能把你更新的代码提供给我吗? 我完全复制了您提供的代码,我认为问题在于,一旦 json 字符串被解码,它就会作为行字符串而不是列表出现,因此 cast 方法不知道如何将其转换为 Contact 类型 - @happy_san @AnanSaadi 你试过我的解决方案了吗? 嘿,我很忙,所以我没有检查代码,但我试过了,但我得到了一个错误:error: The method 'toMap' isn't defined for the type 'Object'.@happy_san

以上是关于Json 解码 Sharedpreferences 返回 List<dynamic> 而不是 List<Contact>的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 LinkedHashMap 被错误地转换为 JSON 并保存在 SharedPreferences 中?

Flutter 使用 SharedPreferences 和 Json 来存储对象

如何在flutter中使用mysql数据库(或json)中的sharedpreferences键选择以列出最喜欢的记录?

如何使用提供程序将 List<object> 存储在 sharedpreferences 中?

默认 SharedPreferences 不保存值

尝试从 sharedPreferences 检索地图时出现颤振错误