开发者

flutter中的JSON和序列化方法及使用详解

开发者 https://www.devze.com 2023-01-13 10:20 出处:网络 作者: 前端那些年
目录引言哪种jsON序列化方法适合手动进行序列化使用代码自动序列化Flutter 中是否有 GSON/Jackson/Moshi 之类的序列化类库?使用Dart:convert内置库手动进行序列化调用jsonDecode()方法:在模型类中序列化JSON使用序
目录
  • 引言
  • 哪种jsON序列化方法适合
    • 手动进行序列化
    • 使用代码自动序列化
  • Flutter 中是否有 GSON/Jackson/Moshi 之类的序列化类库?
    • 使用Dart:convert内置库手动进行序列化
      • 调用jsonDecode()方法:
      • 在模型类中序列化JSON
    • 使用序列化库
      • 运行代码生成实用程序
      • 一次性代码生成
      • 持续生成代码
      • 使用json_serializable模型

    引言

    很难想象一款移动应用程序不需要与web服务器通信,也不需要存储结构化数据。在开发一款网络连接的应用程序时,它迟早会需要使用一些JSON。

    这里简单介绍一下JSON在flutter中的使用。

    Tips:

    编码和序列化是将数据结构转换为字符串的同一件事。解码和反序列化是将字符串转换为数据结构的相反过程。然而,序列化通常也指将数据结构转换为更易于阅读的格式的整个过程。

    哪种JSON序列化方法适合

    这里主要简单介绍两种序列化方式:

    • 手动序列化
    • 使用代码自动序列化

    不同的项目复杂度以及用例都不同,对于一些较小的项目或者类似原型的的应用,使用代码生成可能有些大材小用,而对于有很多不同json模型的应用程序,使用手动序列化则除了无聊之外,有可能会产生不必要的问题和麻烦。

    手动进行序列化

    手动进行json解码说的是使用dart:convert内置的json解码器,通过将原始的json数据传递给jsonDecode()方法,然后在返回的Map<String, dynamic>这个类型的数据中我们可以找到我们想用的数据。不需要别的依赖和其他的设置过程,对于验证一些快速的原型或者小型的项目非常有效。

    当项目逐渐变的越来越大的时候,手动解码可能会表现的不尽人意。手动编写解码逻辑可能会变得越来越难以管理,而且变得非常容易出错,如果访问到不存在的字段,或者编写时有拼写错误,代码在运行时就会发生错误。

    使用代码自动序列化

    对于中大型项目来说,使用代码自动进行序列化可能会是一个比较不错的选择,意味着我们可以使用外部的依赖库来生成我们想要的模版。我们通过设置一些初始化的配置,然后运行一个file watcher从我们的模型类中生成我们想要的代码数据。

    比如我们可以使用:json_serializable或者build_value诸如之类的库。

    这种方法适用于更大的项目。不需要手工编写模版,并且在编译时会捕捉到访问JSON字段时的拼写错误。

    代码生成的缺点是需要一些初始设置。另外,生成的源文件可能会在项目导航器中产生视觉上的混乱。

    Flutter 中是否有 GSON/Jackson/Moshi 之类的序列化类库?

    GSON以及Jackson都是 Java中用来序列化json的类库。

    Moshi则是Kotlin中用来序列化json的类库。

    事实上Flutter中并没有类似的库。

    因为,这样的库需要使用运行时反射,这在Flutter中是禁用的。运行时反射会干扰【树抖动】treeShaking,Dart已经支持了很长时间。通过treeShaking树抖动,您可以从发布版本中“抖掉”未使用的代码,这可以优化应用程序的大小。

    由于反射默认情况下会隐式使用所有代码,因此很难进行treeShaking树抖动。这些工具无法知道哪些部分在运行时未使用,因此冗余代码很难去除。使用反射时,无法轻松优化应用程序大小。

    虽然我们不能在Flutter中使用运行时反射,但有些库提供了类似的API,是基于代码生成。

    使用dart:convert内置库手动进行序列化

    Flutter中的基本JSON序列化非常简单。Flutter有一个内置的dart:convert库,其中包含一个简单的JSON编码器和解码器。

    看下面的示例:

    {
      "name": "John Smith",
      "email": "john@example.com"
    }
    

    使用dart:convert库,我们有两种方法进行序列化。

    调用jsonDecode()方法:

    Map<String, dynamic> user = jsonDecode(jsonString);
    print('Howdy, ${user['name']}!');
    print('We sent the verification link to ${user['email']}.');
    

    但是需要注意的是,jsonDecode()方法会返回一个类型为Map<String, dynamic>的类型,这样的话,我们就特别需要注意json中字段的各种类型。

    在模型类中序列化JSON

    此外,我们可以引入一个简单的模型类(在本例中称为User)来解决前面提到的问题。在User类中,我们可以发现:

    • User.fromJson()构造函数,用于从Map构造新的User实例。
    • toJson()方法,将User实例转换为开发者_C入门Map。

    使用这种方法,调用代码时可以具有类型安全及编译时异常提醒。如果我们输入了错别字,或者将字段视为int而不是String,应用程序将不会编译,而不会在运行时崩溃。

    // user.dart
    class User {
      final String name;
      final String email;
      User(this.name, this.email);
      User.fromJson(Map<String, dynamic> json)
          : name = json['name'],
            email = json['email'];
      Map<String, dynamic> toJson() => {
            'name': name,
            'email': email,
          };
    }
    

    http://www.devze.com码逻辑的责任现在转移到模型本身内部。使用这种新方法,您可以轻松地解码User:

    Map<String, dynamic> userMap = jsonDecode(jsonString);
    var user = User.fromJson(userMap);
    print('Howdy, ${user.name}!');
    print('We sent the verification link to ${user.email}.');
    

    和解码(Decode)相反的是编码(Encode),如果我们想要对User进行编码,我们可以使用jsonEncode()方法:

    String json = jsonEncode(user);
    

    使用这种方法,调用代码根本不必担心JSON序列化。然而,模型类仍然必须这样做。在生产应用程序中,我们需要确保序列化工作正常进行。在实际开发过程中,User.fromJson()User.toJson()方法可能都需要进行单元测试以保证结果的正确性。

    使用序列化库

    尽管有其他库可用,但是这里使用了json_serializable,这是一个自动源代码生成器,可为我们生成json序列化模版。

    要在项目中包含json_serializable,需要一个常规依赖项和两个开发依赖项。简而言之,开发依赖项是不包含在我们的应用程序源代码中的依赖项,它们只在开发环境中使用。

    我们需要在pubspec.yaml进行如下配置:

    **pubspewww.devze.comc.yaml**
    dependencies:
      # Your other regular dependencies here
      json_annotation: <latest_version>
    dev_dependencies:
      # Your other dev_dependencies here
      build_runner: <latest_version>
      json_serializable: <latest_version>
    

    然后在项目根文件夹中运行flutter pub-get以安装依赖。

    然后我们以json_serializable的方式创建模型类:

    // user.dart
    import 'package:json_annotation/json_annotation.dart';
    /// This allows the `User` class to Access private members in
    /// the generated file. The value for this is *.g.dart, where
    /// the star denotes the source file name.
    part 'user.g.dart';
    /// An annotation for the code generator to know that this class needs the
    /// JSON serialization logic to be generated.
    @JsonSeria编程客栈lizable()
    class User {
      User(this.name, this.email);
      String name;
      String email;
      /// A necessary factory constructor for creating a new User instance
      /// from a map. Pass the map to the generated `_$UserFromJson()` constructor.
      /// The constructor is named after the source class, in this case, User.
      factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
      /// `toJson` is the convention for a class to declare support for serialization
      /// to JSON. The implementation simply calls the private, generated
      /// helper method `_$UserToJson`.
      Map<String, dynamic> toJson() => _$UserToJson(this);
    }
    

    通过这种设置,源代码生成器生成用于对JSON中的nameemail字段进行编码和解码的代码。

    如果需要的话,我们还可以定制命名策略,比如,如果API返回带有的对象带有snake_case属性,并且我们希望在模型中使用lowerCamelCase,则可以使用带有name参数的@JsonKey注释:

    /// Tell json_serializable that "registration_date_millis" should be
    /// mapped to this property.
    @JsonKey(name: 'registration_date_millis')
    final int registrationDateMillis;
    

    服务器和客户端最好都遵循相同的命名策略。

    @JsonSerializable()提供了fieldRename的枚举,用于将dart字段完全转换为JSON键。

    修改@JsonSerializable(fieldRename:fieldRename.sake)相当于向每个字段添加@JsonKey(namjse:“<snake_case>”)

    服务器返回的数据是不确定的,所以有必要验证和保护客户端上的数据。

    其他常用的@JsonKey注释包括:

    /// Tell json_serializable to use "defaultValue" if the JSON doesn't
    /// contain this key or if the value is `null`.
    @JsonKey(defaultValue: false)
    final bool isAdult;
    /// When `true` tell json_seriawww.devze.comlizable that JSON must contain the key, 
    /// If the key doesn't exist, an exception is thrown.
    @JsonKey(required: true)
    final String id;
    /// When `true` tell json_serializable that generated code should 
    /// ignore this field completely. 
    @JsonKey(ignore: true)
    final String verificationCode;
    

    运行代码生成实用程序

    当第一次创建json_serializable类时,会出现类似下图所示的错误。

    flutter中的JSON和序列化方法及使用详解

    这些错误完全是正常的,只是因为为模型类生成的代码还不存在。要解决此问题,我们需要运行生成序列化样板的代码生成器。

    运行代码生成器有两种方法。

    • 一次性代码生成
    • 持续生成代码

    一次性代码生成

    通过在项目根目录中运行

    flutter pub run build_runner build --delete-conflicting-outputs
    

    我们可以在需要时为模型生成JSON序列化代码。这将触发一次性构建,该构建将遍历源文件,选择相关文件,并为它们生成必要的序列化代码。

    虽然这很方便,但如果我们不必每次在模型类中进行更改时都手动运行构建,那就更好了。

    持续生成代码

    观察者模式使我们的源代码生成过程更加方便。它监听项目文件中的更改,并在需要时自动生成必要的文件。 通过在项目根目录中运行

    flutter pub run build_runner watch --delete-conflicting-outputs
    

    可以安全地启动一次观察程序,并让它在一直后台运行。

    使用json_serializable模型

    要以JSON_serializable的方式解码JSON字符串,实际上不需要对我们之前的代码进行任何更改。

    Map<String, dynamic> userMap = jsonDecode(jsonString);
    var user = User.fromJson(userMap);
    

    编码也是如此。调用API与之前相同。

    String json = jsonEncode(user);
    

    使用json_serializable,我们可以放弃User类中的任何手动json序列化。源代码生成器创建一个名为user.g.dart的文件,该文件具有所有必要的序列化逻辑。我们不再需要编写自动化测试来确保序列化工作,现在库负责确保序列化工作正常。

    ps:这里所说的解码和编码,对应的是DecodeEncode

    以上就是flutter中的JSON和序列化方法及使用详解的详细内容,更多关于flutter JSON序列化的资料请关注我们其它相关文章!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号