使用 freezed 比单纯的 json to dart 在功能上是有加强的。 大致有如下几点: - 减少 model 代码量 - 不可变类型定义 @freezed - 可变类型 @unfreezed - copyWith 方法复制 - copyWith + 深拷贝 - operator == 操作 - toString 方法 - 空对象支持 - 对数据(反)序列化

flutter freezed json 转 model 代码生成

视频

https://www.youtube.com/watch?v=G4xDj5RpUvQ

https://www.bilibili.com/video/BV16w411k7xs/

前言

原文 https://ducafecat.com/blog/flutter_application_freezed

https://pub-web.flutter-io.cn/packages/freezed

使用 freezed 比单纯的 json to dart 在功能上是有加强的。

大致有如下几点:

  • 减少 model 代码量
  • 不可变类型定义 @freezed
  • 可变类型 @unfreezed
  • copyWith 方法复制
  • copyWith + 深拷贝
  • operator == 操作
  • toString 方法
  • 空对象支持
  • 对数据(反)序列化

参考

freezed

https://pub-web.flutter-io.cn/packages/freezed

https://github.com/rrousselGit/freezed/blob/master/resources/translations/zh_CN/README.md

json 转 freezed

https://app.quicktype.io/

https://dartj.web.app/#/

知识点

步骤

第一步:安装包

编写 pub-add.sh

flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed
# 如果你要使用 freezed 来生成 fromJson/toJson,则执行:
flutter pub add json_annotation
flutter pub add --dev json_serializable

# 常用必备组件
flutter pub add dio

第二步:编写模型

复制 json , 这里复制的是猫哥 woo 课程商品列表,这个业务的数据比较复杂,有一定的代表性。

只需复制一条记录就行,如果你拿到的 json 没有格式化,可以用在线的工具处理下。

https://www.sojson.com/

{
    "id": 15,
    "name": "Beanie",
    "slug": "beanie",
    "permalink": "https://wp.ducafecat.tech/product/beanie/",
    "date_created": "2022-03-31T23:19:37",
    "date_created_gmt": "2022-03-31T15:19:37",
    "date_modified": "2022-04-18T23:50:20",
    "date_modified_gmt": "2022-04-18T15:50:20",
    "type": "simple",
    "status": "publish",
    "featured": true,
    "catalog_visibility": "visible",
    "description": "<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>\n",
    "short_description": "<p>This is a simple product.</p>\n",
    "sku": "woo-beanie",
    "price": "18",
    "regular_price": "20",
    "sale_price": "18",
    "date_on_sale_from": null,
    "date_on_sale_from_gmt": null,
    "date_on_sale_to": null,
    "date_on_sale_to_gmt": null,
    "on_sale": true,
    "purchasable": true,
    "total_sales": 0,
    "virtual": false,
    "downloadable": false,
    "downloads": [],
    "download_limit": 0,
    "download_expiry": 0,
    "external_url": "",
    "button_text": "",
    "tax_status": "taxable",
    "tax_class": "",
    "manage_stock": false,
    "stock_quantity": null,
    "backorders": "no",
    "backorders_allowed": false,
    "backordered": false,
    "low_stock_amount": null,
    "sold_individually": false,
    "weight": "1",
    "dimensions": {
        "length": "12",
        "width": "12",
        "height": "3"
    },
    "shipping_required": true,
    "shipping_taxable": true,
    "shipping_class": "china_shipping",
    "shipping_class_id": 41,
    "reviews_allowed": true,
    "average_rating": "0.00",
    "rating_count": 0,
    "upsell_ids": [],
    "cross_sell_ids": [],
    "parent_id": 0,
    "purchase_note": "",
    "categories": [{
            "id": 34,
            "name": "Bag",
            "slug": "bag"
        },
        {
            "id": 29,
            "name": "Man",
            "slug": "man"
        },
        {
            "id": 15,
            "name": "More",
            "slug": "more"
        },
        {
            "id": 30,
            "name": "Woman",
            "slug": "woman"
        }
    ],
    "tags": [{
        "id": 42,
        "name": "beanie",
        "slug": "beanie"
    }],
    "images": [{
        "id": 44,
        "date_created": "2022-04-01T07:20:01",
        "date_created_gmt": "2022-03-31T15:20:01",
        "date_modified": "2022-04-01T07:20:01",
        "date_modified_gmt": "2022-03-31T15:20:01",
        "src": "https://ducafecat.oss-cn-beijing.aliyuncs.com/wp-content/uploads/2022/03/beanie-2.jpg",
        "name": "beanie-2.jpg",
        "alt": ""
    }],
    "attributes": [{
            "id": 1,
            "name": "Color",
            "position": 0,
            "visible": true,
            "variation": false,
            "options": [
                "Green",
                "Purple"
            ]
        },
        {
            "id": 2,
            "name": "Size",
            "position": 1,
            "visible": true,
            "variation": false,
            "options": [
                "XL",
                "2XL",
                "3XL"
            ]
        }
    ],
    "default_attributes": [],
    "variations": [],
    "grouped_products": [],
    "menu_order": 0,
    "price_html": "<del aria-hidden=\"true\"><span class=\"woocommerce-Price-amount amount\"><bdi><span class=\"woocommerce-Price-currencySymbol\">&yen;</span>20.00</bdi></span></del> <ins><span class=\"woocommerce-Price-amount amount\"><bdi><span class=\"woocommerce-Price-currencySymbol\">&yen;</span>18.00</bdi></span></ins>",
    "related_ids": [
        13,
        12,
        11,
        14
    ],
    "meta_data": [{
            "id": 89,
            "key": "_original_id",
            "value": "48"
        },
        {
            "id": 479,
            "key": "_wpcom_is_markdown",
            "value": "1"
        }
    ],
    "stock_status": "instock",
    "has_options": false,
    "_links": {
        "self": [{
            "href": "https://wp.ducafecat.tech/wp-json/wc/v3/products/15"
        }],
        "collection": [{
            "href": "https://wp.ducafecat.tech/wp-json/wc/v3/products"
        }]
    }
}

开始转换 freezed 格式

https://app.quicktype.io/

输入模型名称,转换目标 dart

image-20231011174332653

设置 freezed 格式转换

创建 product.dart 文件放入 models 目录

lib/models/product.dart

// To parse this JSON data, do
//
//     final product = productFromJson(jsonString);

import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:convert';

part 'product.freezed.dart';
part 'product.g.dart';

Product productFromJson(String str) => Product.fromJson(json.decode(str));

String productToJson(Product data) => json.encode(data.toJson());

@freezed
class Product with _$Product {
  const factory Product({
    @JsonKey(name: "id") required int id,
    @JsonKey(name: "name") required String name,
    @JsonKey(name: "slug") required String slug,
    @JsonKey(name: "permalink") required String permalink,
    @JsonKey(name: "date_created") required DateTime dateCreated,
    @JsonKey(name: "date_created_gmt") required DateTime dateCreatedGmt,
    @JsonKey(name: "date_modified") required DateTime dateModified,
    @JsonKey(name: "date_modified_gmt") required DateTime dateModifiedGmt,
    @JsonKey(name: "type") required String type,
    @JsonKey(name: "status") required String status,
    @JsonKey(name: "featured") required bool featured,
    @JsonKey(name: "catalog_visibility") required String catalogVisibility,
    @JsonKey(name: "description") required String description,
    @JsonKey(name: "short_description") required String shortDescription,
    @JsonKey(name: "sku") required String sku,
    @JsonKey(name: "price") required String price,
    @JsonKey(name: "regular_price") required String regularPrice,
    @JsonKey(name: "sale_price") required String salePrice,
    @JsonKey(name: "date_on_sale_from") required dynamic dateOnSaleFrom,
    @JsonKey(name: "date_on_sale_from_gmt") required dynamic dateOnSaleFromGmt,
    @JsonKey(name: "date_on_sale_to") required dynamic dateOnSaleTo,
    @JsonKey(name: "date_on_sale_to_gmt") required dynamic dateOnSaleToGmt,
    @JsonKey(name: "on_sale") required bool onSale,
    @JsonKey(name: "purchasable") required bool purchasable,
    @JsonKey(name: "total_sales") required int totalSales,
    @JsonKey(name: "virtual") required bool virtual,
    @JsonKey(name: "downloadable") required bool downloadable,
    @JsonKey(name: "downloads") required List<dynamic> downloads,
    @JsonKey(name: "download_limit") required int downloadLimit,
    @JsonKey(name: "download_expiry") required int downloadExpiry,
    @JsonKey(name: "external_url") required String externalUrl,
    @JsonKey(name: "button_text") required String buttonText,
    @JsonKey(name: "tax_status") required String taxStatus,
    @JsonKey(name: "tax_class") required String taxClass,
    @JsonKey(name: "manage_stock") required bool manageStock,
    @JsonKey(name: "stock_quantity") required dynamic stockQuantity,
    @JsonKey(name: "backorders") required String backorders,
    @JsonKey(name: "backorders_allowed") required bool backordersAllowed,
    @JsonKey(name: "backordered") required bool backordered,
    @JsonKey(name: "low_stock_amount") required dynamic lowStockAmount,
    @JsonKey(name: "sold_individually") required bool soldIndividually,
    @JsonKey(name: "weight") required String weight,
    @JsonKey(name: "dimensions") required Dimensions dimensions,
    @JsonKey(name: "shipping_required") required bool shippingRequired,
    @JsonKey(name: "shipping_taxable") required bool shippingTaxable,
    @JsonKey(name: "shipping_class") required String shippingClass,
    @JsonKey(name: "shipping_class_id") required int shippingClassId,
    @JsonKey(name: "reviews_allowed") required bool reviewsAllowed,
    @JsonKey(name: "average_rating") required String averageRating,
    @JsonKey(name: "rating_count") required int ratingCount,
    @JsonKey(name: "upsell_ids") required List<dynamic> upsellIds,
    @JsonKey(name: "cross_sell_ids") required List<dynamic> crossSellIds,
    @JsonKey(name: "parent_id") required int parentId,
    @JsonKey(name: "purchase_note") required String purchaseNote,
    @JsonKey(name: "categories") required List<Category> categories,
    @JsonKey(name: "tags") required List<Category> tags,
    @JsonKey(name: "images") required List<WooImage> images,
    @JsonKey(name: "attributes") required List<Attribute> attributes,
    @JsonKey(name: "default_attributes")
    required List<dynamic> defaultAttributes,
    @JsonKey(name: "variations") required List<dynamic> variations,
    @JsonKey(name: "grouped_products") required List<dynamic> groupedProducts,
    @JsonKey(name: "menu_order") required int menuOrder,
    @JsonKey(name: "price_html") required String priceHtml,
    @JsonKey(name: "related_ids") required List<int> relatedIds,
    @JsonKey(name: "meta_data") required List<MetaDatum> metaData,
    @JsonKey(name: "stock_status") required String stockStatus,
    @JsonKey(name: "has_options") required bool hasOptions,
    @JsonKey(name: "_links") required Links links,
  }) = _Product;

  factory Product.fromJson(Map<String, dynamic> json) =>
      _$ProductFromJson(json);
}

@freezed
class Attribute with _$Attribute {
  const factory Attribute({
    @JsonKey(name: "id") required int id,
    @JsonKey(name: "name") required String name,
    @JsonKey(name: "position") required int position,
    @JsonKey(name: "visible") required bool visible,
    @JsonKey(name: "variation") required bool variation,
    @JsonKey(name: "options") required List<String> options,
  }) = _Attribute;

  factory Attribute.fromJson(Map<String, dynamic> json) =>
      _$AttributeFromJson(json);
}

@freezed
class Category with _$Category {
  const factory Category({
    @JsonKey(name: "id") required int id,
    @JsonKey(name: "name") required String name,
    @JsonKey(name: "slug") required String slug,
  }) = _Category;

  factory Category.fromJson(Map<String, dynamic> json) =>
      _$CategoryFromJson(json);
}

@freezed
class Dimensions with _$Dimensions {
  const factory Dimensions({
    @JsonKey(name: "length") required String length,
    @JsonKey(name: "width") required String width,
    @JsonKey(name: "height") required String height,
  }) = _Dimensions;

  factory Dimensions.fromJson(Map<String, dynamic> json) =>
      _$DimensionsFromJson(json);
}

@freezed
class WooImage with _$WooImage {
  const factory WooImage({
    @JsonKey(name: "id") required int id,
    @JsonKey(name: "date_created") required DateTime dateCreated,
    @JsonKey(name: "date_created_gmt") required DateTime dateCreatedGmt,
    @JsonKey(name: "date_modified") required DateTime dateModified,
    @JsonKey(name: "date_modified_gmt") required DateTime dateModifiedGmt,
    @JsonKey(name: "src") required String src,
    @JsonKey(name: "name") required String name,
    @JsonKey(name: "alt") required String alt,
  }) = _WooImage;

  factory WooImage.fromJson(Map<String, dynamic> json) =>
      _$WooImageFromJson(json);
}

@freezed
class Links with _$Links {
  const factory Links({
    @JsonKey(name: "self") required List<Collection> self,
    @JsonKey(name: "collection") required List<Collection> collection,
  }) = _Links;

  factory Links.fromJson(Map<String, dynamic> json) => _$LinksFromJson(json);
}

@freezed
class Collection with _$Collection {
  const factory Collection({
    @JsonKey(name: "href") required String href,
  }) = _Collection;

  factory Collection.fromJson(Map<String, dynamic> json) =>
      _$CollectionFromJson(json);
}

@freezed
class MetaDatum with _$MetaDatum {
  const factory MetaDatum({
    @JsonKey(name: "id") required int id,
    @JsonKey(name: "key") required String key,
    @JsonKey(name: "value") required String value,
  }) = _MetaDatum;

  factory MetaDatum.fromJson(Map<String, dynamic> json) =>
      _$MetaDatumFromJson(json);
}

运行代码生成命令

dart run build_runner build -d
 dart run build_runner build -d                                                                   
Building package executable... (8.2s)
Built build_runner:build_runner.
[INFO] Generating build script completed, took 364ms
[INFO] Precompiling build script... completed, took 7.9s
[INFO] Building new asset graph completed, took 901ms
[INFO] Checking for unexpected pre-existing outputs. completed, took 1ms
[INFO] Generating SDK summary completed, took 4.6s
[INFO] Running build completed, took 19.2s
[INFO] Caching finalized dependency graph completed, took 47ms
[INFO] Succeeded after 19.3s with 47 outputs (56 actions)

成功后目录和文件是这样的。

image-20231011180433573

关闭警告 The annotation 'JsonKey' can only be used on fields or getters.

analysis_options.yaml

...

analyzer:
  errors:
    invalid_annotation_target: ignore

@JsonKey 用来指定 json 中的字段名称

第三步:通过 dio 拉取数据显示商品列表

编写商品列表页 lib/views/product_page.dart

成员变量

  // dio 实例
  final dio = Dio();
  // 商品列表
  List<Product> products = [];

拉取数据

  // 拉取数据
  loadData() async {
    final response = await dio.get(
        'https://wpapi.ducafecat.tech/products?page=1&pre_page=10&featured=true&category=&search=&slug=&status=&sku=&attribute=&attribute_term=&min_price=&max_price=&orderby=date&order=desc&tag=');
    final data = response.data as List<dynamic>;
    for (final item in data) {
      products.add(Product.fromJson(item));
    }
    setState(() {});
  }

init 准备数据

  @override
  void initState() {
    super.initState();
    loadData();
  }

主视图

  // 主视图
  Widget _mainView() {
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(products[index].name),
          subtitle: Image.network(
            products[index].images[0].src,
            height: 100,
            fit: BoxFit.cover,
          ),
        );
      },
    );
  }

build 函数

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("freezed"),
      ),
      body: _mainView(),
    );
  }

启动视图 lib/main.dart

...
home: const ProductListPage(),

启动 app

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_freezed

小结

总的来说,Freezed是一个强大的Flutter库,它简化了不可变数据类的创建和管理。它提供了许多有用的功能,可以提高代码的可靠性、可维护性和开发效率。

感谢阅读本文

如果我有什么错?请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥 ducafecat.com

end