Flutter插件lemonade的使用_lemonade 是一个简单而强大的数据验证库

Flutter插件lemonade的使用_lemonade 是一个简单而强大的数据验证库

lemonade 是一个简单而强大的数据验证库。它可以用于标准数据结构以及任何自定义数据。该库具有以下特点:

  • 通用性:可以用于标准数据结构和任何自定义数据。
  • 可扩展性:可以轻松扩展以处理任何同步验证用例。
  • 安全性:防止由于拼写错误或空值导致的运行时错误。
  • 测试覆盖率超过80%
  • 纯Dart编写
  • 零依赖

主要灵感来源是JSON Schema和ajv库。

数据验证是什么?

验证是一种确保应用程序使用的数据正确且安全的方法。这意味着对象的所有属性都已指定,并且符合预期。

例如,JSON数据验证:

// 正确的JSON
{
  "name": "John",
  "age": 25,
  "email": "john@example.com"
}

// 错误的JSON
{
  "name": "John",
  "age": "25", // 字符串而不是数字
  "email": "john@example.com"
}

如果你为这样的对象编写验证器,你可以100%确定数据会像你期望的那样。

使用方法

例如,你想验证一个GeoJSON点对象,其格式如下:

{
    "type": "Point",
    "coordinates": [125.6, 10.1]
}

验证器将如下所示:

final pointsValidator = Validator.object(
  items: {
    // "type": "Point"
    'type': Validator.equals('Point'),
    // "coordinates": [125.6, 10.1]
    'coordinates': Validator.list(
      item: Validator.number(),
      // 长度正好为2
      minItems: 2,
      maxItems: 2,
    ),
  },
);

然后,你可以使用非常基础的方法检查任何数据:

final decodedData = jsonDecode('{"type":"Point","coordinates":[125.6,10.1]}');

print(pointsValidator.validate(decodedData)); // true

如果想检查具体哪里出错,可以使用getError方法:

final wrongData = jsonDecode('{"type":"Point","coordinates":[125.6,"10.1"]}');

print(pointsValidator.getError(wrongData)); // object.coordinates > list<number>[1] > expected(number).got("10.1")

字符串形式的错误信息表示:

  • 在字段"coordinates"
    • 在索引[1]
      • 期望任意数字
      • 得到"10.1"

这样很容易解码并追踪数据中的错误,不是吗?

常见模式

大多数情况下,你会遇到一些常见的数据类型:UUID、IP地址、十六进制字符串等。你可以使用此库轻松验证这些数据!

例如,使用Validators类验证UUID:

final stringToCheck = 'd87955f3-42a8-43db-aea2-78c0e29e5d23';

Validators.uuid().validate(stringToCheck);

它也可以在复杂的嵌套验证器中轻松使用:

final structureToCheck = {
  'ip': '127.0.0.1',
  'mac': '0C:D3:86:34:34:66',
};

final validator = Validator.object(
  items: {
    'ip': Validators.ipv4(),
    'mac': Validators.mac(),
  },
);

validator.validate(structureToCheck);

自定义验证器

有时你需要验证某些自定义数据结构(例如自己的类)。lemonade 提供了两种方法来实现这一点:

  1. 创建通用、可重用的验证器类。
  2. 使用Validator.customValue工厂。

通用验证器类

lemonade 导出了几个抽象类,你可以继承它们以实现自定义验证器:

  • Validator
  • ValueValidator
  • CollectionValidator
  • CompoundValidator

验证工作方式类似:

  • getError 方法必须覆盖,如果返回null,则数据有效;如果返回ValidationError,则数据无效。
  • 必须指定annotation,以便将验证器字符串化并查看其验证的内容。

ValidationError有两个必需字段:expectedactual(类似于dart测试)。 在actual中,你应该指定被检查的值。 在expected中,你应该指定有效的数据标准。

例如,你想验证数字是否为偶数。首先,你必须检查数据是否为数字,然后检查偶数性。实现如下:

class EvennessValidator extends Validator {
  EvennessValidator() : super(annotation: 'even number');

  @override
  ValidationError? getError(dynamic data) {
    if (data is! int) {
      // 我们期望数字,但得到了其他东西
      return ValidationError(expected: 'number', actual: data);
    }

    if (!data.isEven) {
      // 我们期望偶数,但得到了奇数
      return ValidationError(expected: 'even number', actual: data);
    }

    // 如果一切正常,返回null
    return null;
  }
}

然后你可以这样使用它:

// 创建一个新的验证器实例
final validator = EvennessValidator();
// 使用默认方法
validator.validate(14);

这种方法的主要优点:

  • 验证器看起来和工作方式与默认的一样。
  • 验证错误更具描述性和可定制性。
  • 验证器可以轻松重用。

但是也有几个缺点:

  • 需要更多的定制和整体代码。
  • 对于一次性使用的验证器来说不够方便。

Validator.customValue

如果你只是想完成任务,不需要理解具体的无效原因,并且这个验证器只会被使用一次,那么使用简单的Validator.customValue工厂会更容易。

对于检查数字是否为偶数的验证器,代码如下:

// 创建一个新的验证器实例
final validator = Validator.customValue((data) {
  if (data is! int) return false;

  if (!data.isEven) return false;

  return true;
});
// 使用默认方法
validator.validate(14);

这很简单!这种方法的优点:

  • 编写的代码更少。
  • 不需要定制、调优、描述。
  • 可以使用与Iterable中的where方法相同的回调。

但是也有一些缺点:

  • 不容易重用。
  • 导致代码一致性较差。

功能和路线图

我开始这个项目是因为需要编写大量JSON验证器。后来我尝试使其更加通用,以便用于其他用例。

  • ✅ 基本类型的验证器
  • ✅ 常见数据类型的内置验证器
  • ✅ 自定义数据结构的内置验证器
  • ❌ 与JSON Schema完全向后兼容
  • ❌ 客户端自动HTTP响应验证
  • ❌ 服务器端自动HTTP请求验证

示例代码

以下是使用lemonade验证Rick and Morty API数据的完整示例:

// 忽略警告:prefer_const_constructors, avoid_print, avoid_dynamic_calls

import 'dart:convert';

import 'package:lemonade/lemonade.dart';

/// 遵循这里模式的验证器
/// https://rickandmortyapi.com/documentation/#get-all-characters
final validator = Validator.object(
  items: {
    'info': Validator.object(
      items: {
        'count': Validator.integer(min: 0),
        'pages': Validator.integer(min: 0),
        'next': Validator.string(),
        'prev': Validator.string().nullable(),
      },
    ),
    'results': Validator.list(
      item: Validator.object(
        items: {
          'id': Validator.integer(min: 1),
          'name': Validator.string(minLength: 1),
          'status': Validator.equals('Alive') |
              Validator.equals('Dead') |
              Validator.equals('Unknown'),
          'species': Validator.string(minLength: 1),
          'type': Validator.string(),
          'gender': Validator.equals('Female') |
              Validator.equals('Male') |
              Validator.equals('Genderless') |
              Validator.equals('unknown'),
          'origin': locationValidator,
          'location': locationValidator,
          'image': Validator.string(),
          'episode': Validator.list(
            item: Validator.string(),
            minItems: 1,
          ),
          'url': Validator.string(),
          'created': Validator.string(),
        },
      ),
    ),
  },
);

/// 遵循这里模式的验证器
/// https://rickandmortyapi.com/documentation/#location-schema
final locationValidator = Validator.object(
  items: {
    'name': Validator.string(minLength: 1),
    'url': Validator.string(),
  },
);

/// 应该验证的示例数据
const exampleJson = '''
{
  "info": {
    "count": 826,
    "pages": 42,
    "next": "https://rickandmortyapi.com/api/character/?page=2",
    "prev": null
  },
  "results": [
    {
      "id": 1,
      "name": "Rick Sanchez",
      "status": "Alive",
      "species": "Human",
      "type": "",
      "gender": "Male",
      "origin": {
        "name": "Earth",
        "url": "https://rickandmortyapi.com/api/location/1"
      },
      "location": {
        "name": "Earth",
        "url": "https://rickandmortyapi.com/api/location/20"
      },
      "image": "https://rickandmortyapi.com/api/character/avatar/1.jpeg",
      "episode": [
        "https://rickandmortyapi.com/api/episode/1",
        "https://rickandmortyapi.com/api/episode/2"
      ],
      "url": "https://rickandmortyapi.com/api/character/1",
      "created": "2017-11-04T18:48:46.250Z"
    }
  ]
}
''';

void main() {
  final parsedJson = jsonDecode(exampleJson);

  // 这里一切都应该没问题
  final isValid = validator.validate(parsedJson);
  if (isValid) {
    print('Everything is fine');
  } else {
    print('Validation error!');
  }

  print('------------------');

  // 让我们破坏这个完美的模式
  parsedJson['results'][0]['type'] = 999;

  // 现在这里有一个错误
  final errors = validator.getError(parsedJson);
  print('Errors: $errors');
}

更多关于Flutter插件lemonade的使用_lemonade 是一个简单而强大的数据验证库的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter插件lemonade的使用_lemonade 是一个简单而强大的数据验证库的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在探索Flutter中未知功能插件(如lemonade)的潜在用途时,直接给出代码案例可能有些困难,因为“lemonade”并不是一个广为人知的Flutter插件名称。不过,我可以基于一般的Flutter插件开发和使用流程,给出一个假设性的代码案例,用以展示如何集成和探索一个假设的Flutter插件。

假设lemonade插件提供了一个用于数据收集和分析的功能,我们可以编写以下Flutter代码来集成并探索其潜在用途。

1. 添加依赖

首先,我们需要在pubspec.yaml文件中添加对lemonade插件的依赖(注意:这里的lemonade是假设的,实际使用时需要替换为真实插件名):

dependencies:
  flutter:
    sdk: flutter
  lemonade: ^x.y.z  # 假设的版本号

2. 导入插件

在需要使用lemonade插件的Dart文件中导入它:

import 'package:lemonade/lemonade.dart';

3. 初始化插件

在Flutter应用的入口点(通常是main.dart)中初始化插件:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  // 假设lemonade有一个初始化方法
  Lemonade.instance.initialize();
  runApp(MyApp());
}

4. 使用插件功能

假设lemonade插件提供了数据收集和分析的功能,我们可以在应用的某个页面中使用它:

import 'package:flutter/material.dart';
import 'package:lemonade/lemonade.dart';

class DataCollectionPage extends StatefulWidget {
  @override
  _DataCollectionPageState createState() => _DataCollectionPageState();
}

class _DataCollectionPageState extends State<DataCollectionPage> {
  String analysisResult = '';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Data Collection with Lemonade'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Analysis Result:',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 16),
            Text(
              analysisResult,
              style: TextStyle(fontSize: 18),
            ),
            SizedBox(height: 32),
            ElevatedButton(
              onPressed: () async {
                // 假设lemonade有一个collectData方法进行数据收集
                // 和一个analyzeData方法进行数据分析
                List<Map<String, dynamic>> data = await Lemonade.instance.collectData();
                String result = await Lemonade.instance.analyzeData(data);
                setState(() {
                  analysisResult = result;
                });
              },
              child: Text('Collect and Analyze Data'),
            ),
          ],
        ),
      ),
    );
  }
}

5. 运行应用

完成上述步骤后,运行Flutter应用,你应该能够在指定的页面上看到一个按钮,点击该按钮将触发数据收集和分析操作,并在页面上显示分析结果。

注意事项

  • 上述代码是基于假设的lemonade插件功能编写的,实际使用时需要根据真实插件的文档进行调整。
  • 如果lemonade插件不存在或功能不符,你可能需要寻找其他类似的Flutter插件或自行开发所需功能。
  • 在集成和使用任何第三方插件时,务必阅读其官方文档,了解插件的API、使用方法和注意事项。
回到顶部