Flutter JSON数据探索插件json_data_explorer的使用

Flutter JSON数据探索插件 json_data_explorer 的使用

json_data_explorer 是一个高度可定制的Flutter小部件,用于渲染和与JSON对象进行交互。它提供了展开和折叠类和数组节点、动态搜索和高亮、可配置的主题和交互、可配置的数据显示格式以及缩进指南等功能。

功能特性

  • 展开和折叠类和数组节点:用户可以展开或折叠JSON对象中的类和数组节点。
  • 动态搜索与高亮:支持实时搜索并高亮匹配的键和值。
  • 可配置的主题和交互:可以根据需要自定义字体、颜色和其他样式。
  • 可配置的数据显示格式:可以通过自定义格式化器来改变键和值的显示方式。
  • 缩进指南:提供清晰的缩进线,帮助用户更好地理解JSON结构。

使用方法

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 json_data_explorer 依赖:

dependencies:
  json_data_explorer: ^latest_version
2. 初始化 DataExplorerStore

DataExplorerStore 是管理JSON数据的核心类。为了使用该插件的所有功能,你需要通过 Provider 注册它。

final DataExplorerStore store = DataExplorerStore();

void main() {
  runApp(
    ChangeNotifierProvider.value(
      value: store,
      child: const MyApp(),
    ),
  );
}
3. 加载JSON数据

使用 DataExplorerStore.buildNodes 方法将JSON对象加载到 DataExplorerStore 中。假设你有一个JSON字符串 myJson,你可以这样做:

store.buildNodes(json.decode(myJson));
4. 显示 JsonDataExplorer 小部件

JsonDataExplorer 是用于显示JSON数据的小部件。它需要一个 nodes 参数,该参数可以从 DataExplorerStore 中获取。

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: SafeArea(
      minimum: const EdgeInsets.all(16),
      child: ChangeNotifierProvider.value(
        value: store,
        child: Consumer<DataExplorerStore>(
          builder: (context, state, child) => JsonDataExplorer(
            nodes: state.displayNodes,
          ),
        ),
      ),
    ),
  );
}

自定义外观和交互

1. 主题

你可以通过 DataExplorerTheme 来自定义字体、颜色等样式。例如:

JsonDataExplorer(
  nodes: state.displayNodes,
  theme: DataExplorerTheme(
    rootKeyTextStyle: GoogleFonts.inconsolata(
      color: Colors.black,
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    propertyKeyTextStyle: GoogleFonts.inconsolata(
      color: Colors.black.withOpacity(0.7),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    keySearchHighlightTextStyle: GoogleFonts.inconsolata(
      color: Colors.black,
      backgroundColor: const Color(0xFFFFEDAD),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    focusedKeySearchHighlightTextStyle: GoogleFonts.inconsolata(
      color: Colors.black,
      backgroundColor: const Color(0xFFF29D0B),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    valueTextStyle: GoogleFonts.inconsolata(
      color: const Color(0xFFCA442C),
      fontSize: 16,
    ),
    valueSearchHighlightTextStyle: GoogleFonts.inconsolata(
      color: const Color(0xFFCA442C),
      backgroundColor: const Color(0xFFFFEDAD),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    focusedValueSearchHighlightTextStyle: GoogleFonts.inconsolata(
      color: Colors.black,
      backgroundColor: const Color(0xFFF29D0B),
      fontWeight: FontWeight.bold,
      fontSize: 16,
    ),
    indentationLineColor: const Color(0xFFE1E1E1),
    highlightColor: const Color(0xFFF1F1F1),
  ),
)
2. 格式化器

你可以使用 propertyNameFormattervalueStyleBuilder 来自定义键和值的显示格式。例如,将所有属性键显示为 key ->

JsonDataExplorer(
  nodes: state.displayNodes,
  propertyNameFormatter: (name) => '$name ->',
)
3. 动态更改属性样式

你可以根据值的类型动态更改属性的样式。例如,将数字类型的值显示为蓝色:

JsonDataExplorer(
  nodes: state.displayNodes,
  valueStyleBuilder: (value, style) {
    if (value is num) {
      return PropertyOverrides(
        style: style.copyWith(
          color: Colors.blue,
        ),
      );
    } 
    return PropertyOverrides(
      style: style,
    );
  },
)
4. 自定义组件

你可以通过 collapsableToggleBuilderrootInformationBuildertrailingBuilder 来自定义展开/折叠按钮、根节点信息和尾随组件。例如,使用动画旋转图标作为展开/折叠按钮:

JsonDataExplorer(
  nodes: state.displayNodes,
  collapsableToggleBuilder: (context, node) =>
      AnimatedRotation(
    turns: node.isCollapsed ? -0.25 : 0,
    duration: const Duration(milliseconds: 300),
    child: const Icon(Icons.arrow_drop_down),
  ),
)

搜索功能

DataExplorerStore 提供了搜索功能,JsonDataExplorer 小部件会自动响应搜索结果并高亮显示。你可以通过 searchController 来实现搜索栏,并使用 focusPreviousSearchResultfocusNextSearchResult 方法来导航搜索结果。

Row(
  children: [
    Expanded(
      child: TextField(
        controller: searchController,
        onChanged: (term) => dataExplorerStore.search(term),
        decoration: const InputDecoration(
          hintText: 'Search',
        ),
      ),
    ),
    const SizedBox(
      width: 8,
    ),
    IconButton(
      onPressed: dataExplorerStore.focusPreviousSearchResult,
      icon: const Icon(Icons.arrow_drop_up),
    ),
    IconButton(
      onPressed: dataExplorerStore.focusNextSearchResult,
      icon: const Icon(Icons.arrow_drop_down),
    ),
  ],
),

完整示例

以下是一个完整的示例代码,展示了如何使用 json_data_explorer 插件来加载和显示JSON数据,并提供搜索和自定义样式功能。

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:json_data_explorer/json_data_explorer.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Json Data Explorer',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Test Data Explorer'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text(
            'Small JSON',
            style: Theme.of(context).textTheme.headline6,
          ),
          const _OpenJsonButton(
            title: 'ISS current location',
            url: 'http://api.open-notify.org/iss-now.json',
            padding: EdgeInsets.symmetric(vertical: 8.0),
          ),
          Text(
            'Medium JSON',
            style: Theme.of(context).textTheme.headline6,
          ),
          const _OpenJsonButton(
            title: 'Nobel prizes country',
            url: 'http://api.nobelprize.org/v1/country.json',
            padding: EdgeInsets.symmetric(vertical: 8.0),
          ),
          const _OpenJsonButton(
            title: 'Australia ABC Local Stations',
            url:
                'https://data.gov.au/geoserver/abc-local-stations/wfs?request=GetFeature&typename=ckan_d534c0e9_a9bf_487b_ac8f_b7877a09d162&outputFormat=json',
          ),
          Text(
            'Large JSON',
            style: Theme.of(context).textTheme.headline6,
          ),
          const _OpenJsonButton(
            title: 'Pokémon',
            url: 'https://pokeapi.co/api/v2/pokemon/?offset=0&limit=2000',
            padding: EdgeInsets.symmetric(vertical: 8.0),
          ),
          const _OpenJsonButton(
            title: 'Earth Meteorite Landings',
            url: 'https://data.nasa.gov/resource/y77d-th95.json',
          ),
          const _OpenJsonButton(
            title: 'Reddit r/all',
            url: 'https://www.reddit.com/r/all.json',
          ),
          Text(
            'Exploding JSON',
            style: Theme.of(context).textTheme.headline6,
          ),
          const _OpenJsonButton(
            title: '25MB GitHub Json',
            url:
                'https://raw.githubusercontent.com/json-iterator/test-data/master/large-file.json',
            padding: EdgeInsets.only(top: 8.0, bottom: 32.0),
          ),
          Text(
            'More datasets at https://awesomeopensource.com/project/jdorfman/awesome-json-datasets',
            style: Theme.of(context).textTheme.caption,
          ),
        ],
      ),
    );
  }
}

class DataExplorerPage extends StatefulWidget {
  final String jsonUrl;
  final String title;

  const DataExplorerPage({
    Key? key,
    required this.jsonUrl,
    required this.title,
  }) : super(key: key);

  [@override](/user/override)
  _DataExplorerPageState createState() => _DataExplorerPageState();
}

class _DataExplorerPageState extends State<DataExplorerPage> {
  final searchController = TextEditingController();
  final itemScrollController = ItemScrollController();
  final DataExplorerStore store = DataExplorerStore();

  [@override](/user/override)
  void initState() {
    _loadJsonDataFrom(widget.jsonUrl);
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SafeArea(
        minimum: const EdgeInsets.all(16),
        child: ChangeNotifierProvider.value(
          value: store,
          child: Consumer<DataExplorerStore>(
            builder: (context, state, child) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: searchController,
                        onChanged: (term) => state.search(term),
                        decoration: const InputDecoration(
                          hintText: 'Search',
                        ),
                      ),
                    ),
                    const SizedBox(
                      width: 8,
                    ),
                    if (state.searchResults.isNotEmpty)
                      Text(_searchFocusText()),
                    if (state.searchResults.isNotEmpty)
                      IconButton(
                        onPressed: () {
                          store.focusPreviousSearchResult();
                          _scrollToSearchMatch();
                        },
                        icon: const Icon(Icons.arrow_drop_up),
                      ),
                    if (state.searchResults.isNotEmpty)
                      IconButton(
                        onPressed: () {
                          store.focusNextSearchResult();
                          _scrollToSearchMatch();
                        },
                        icon: const Icon(Icons.arrow_drop_down),
                      ),
                  ],
                ),
                const SizedBox(
                  height: 16.0,
                ),
                Row(
                  children: [
                    TextButton(
                      onPressed: state.areAllExpanded() ? null : state.expandAll,
                      child: const Text('Expand All'),
                    ),
                    const SizedBox(
                      width: 8.0,
                    ),
                    TextButton(
                      onPressed: state.areAllCollapsed() ? null : state.collapseAll,
                      child: const Text('Collapse All'),
                    ),
                  ],
                ),
                const SizedBox(
                  height: 16.0,
                ),
                Expanded(
                  child: JsonDataExplorer(
                    nodes: state.displayNodes,
                    itemScrollController: itemScrollController,
                    itemSpacing: 4,
                    rootInformationBuilder: (context, node) => DecoratedBox(
                      decoration: const BoxDecoration(
                        color: Color(0x80E1E1E1),
                        borderRadius: BorderRadius.all(Radius.circular(2)),
                      ),
                      child: Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 4,
                          vertical: 2,
                        ),
                        child: Text(
                          node.isClass
                              ? '{${node.childrenCount}}'
                              : '[${node.childrenCount}]',
                          style: GoogleFonts.inconsolata(
                            fontSize: 12,
                            color: const Color(0xFF6F6F6F),
                          ),
                        ),
                      ),
                    ),
                    collapsableToggleBuilder: (context, node) =>
                        AnimatedRotation(
                      turns: node.isCollapsed ? -0.25 : 0,
                      duration: const Duration(milliseconds: 300),
                      child: const Icon(Icons.arrow_drop_down),
                    ),
                    trailingBuilder: (context, node) => node.isFocused
                        ? IconButton(
                            padding: EdgeInsets.zero,
                            constraints: const BoxConstraints(maxHeight: 18),
                            icon: const Icon(
                              Icons.copy,
                              size: 18,
                            ),
                            onPressed: () => _printNode(node),
                          )
                        : const SizedBox(),
                    rootNameFormatter: (dynamic name) => '$name',
                    valueStyleBuilder: (dynamic value, style) {
                      final isUrl = _valueIsUrl(value);
                      return PropertyOverrides(
                        style: isUrl
                            ? style.copyWith(
                                decoration: TextDecoration.underline,
                              )
                            : style,
                        onTap: isUrl ? () => _launchUrl(value as String) : null,
                      );
                    },
                    theme: DataExplorerTheme(
                      rootKeyTextStyle: GoogleFonts.inconsolata(
                        color: Colors.black,
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      propertyKeyTextStyle: GoogleFonts.inconsolata(
                        color: Colors.black.withOpacity(0.7),
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      keySearchHighlightTextStyle: GoogleFonts.inconsolata(
                        color: Colors.black,
                        backgroundColor: const Color(0xFFFFEDAD),
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      focusedKeySearchHighlightTextStyle:
                          GoogleFonts.inconsolata(
                        color: Colors.black,
                        backgroundColor: const Color(0xFFF29D0B),
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      valueTextStyle: GoogleFonts.inconsolata(
                        color: const Color(0xFFCA442C),
                        fontSize: 16,
                      ),
                      valueSearchHighlightTextStyle: GoogleFonts.inconsolata(
                        color: const Color(0xFFCA442C),
                        backgroundColor: const Color(0xFFFFEDAD),
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      focusedValueSearchHighlightTextStyle:
                          GoogleFonts.inconsolata(
                        color: Colors.black,
                        backgroundColor: const Color(0xFFF29D0B),
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                      indentationLineColor: const Color(0xFFE1E1E1),
                      highlightColor: const Color(0xFFF1F1F1),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  String _searchFocusText() =>
      '${store.focusedSearchResultIndex + 1} of ${store.searchResults.length}';

  Future _loadJsonDataFrom(String url) async {
    debugPrint('Calling Json API');
    final data = await http.read(Uri.parse(url));
    debugPrint('Done!');
    final dynamic decoded = json.decode(data);
    store.buildNodes(decoded, areAllCollapsed: true);
  }

  void _printNode(NodeViewModelState node) {
    if (node.isRoot) {
      final value = node.isClass ? 'class' : 'array';
      debugPrint('${node.key}: $value');
      return;
    }
    debugPrint('${node.key}: ${node.value}');
  }

  void _scrollToSearchMatch() {
    final index = store.displayNodes.indexOf(store.focusedSearchResult.node);
    if (index != -1) {
      itemScrollController.scrollTo(
        index: index,
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOutCubic,
      );
    }
  }

  bool _valueIsUrl(dynamic value) {
    if (value is String) {
      return Uri.tryParse(value)?.hasAbsolutePath ?? false;
    }
    return false;
  }

  Future _launchUrl(String url) {
    return launch(url);
  }

  [@override](/user/override)
  void dispose() {
    searchController.dispose();
    super.dispose();
  }
}

class _OpenJsonButton extends StatelessWidget {
  final String url;
  final String title;
  final EdgeInsets padding;

  const _OpenJsonButton({
    Key? key,
    required this.url,
    required this.title,
    this.padding = const EdgeInsets.only(bottom: 8.0),
  }) : super(key: key);

  [@override](/user/override)
  Widget build(BuildContext context) => Padding(
        padding: padding,
        child: ElevatedButton(
          child: Text(title),
          onPressed: () => Navigator.of(context).push<MaterialPageRoute>(
            MaterialPageRoute(
              builder: (ctx) => DataExplorerPage(
                jsonUrl: url,
                title: title,
              ),
            ),
          ),
        ),
      );
}

更多关于Flutter JSON数据探索插件json_data_explorer的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter JSON数据探索插件json_data_explorer的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于在Flutter中使用json_data_explorer插件来探索JSON数据,这里是一个简要的代码示例,展示了如何集成和使用该插件。请注意,json_data_explorer插件的具体API和使用方法可能会根据插件版本的不同而有所变化,因此以下代码仅供参考,并假设你已经按照Flutter的常规步骤添加了依赖项并导入了包。

首先,确保在你的pubspec.yaml文件中添加了json_data_explorer依赖项:

dependencies:
  flutter:
    sdk: flutter
  json_data_explorer: ^最新版本号  # 替换为实际的最新版本号

然后,运行flutter pub get来安装依赖项。

接下来,在你的Dart文件中,你可以按照以下方式使用json_data_explorer

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter JSON Data Explorer Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final String jsonData = '''
  {
    "name": "John Doe",
    "age": 30,
    "email": "johndoe@example.com",
    "address": {
      "street": "123 Main St",
      "city": "Anytown",
      "state": "CA"
    },
    "phoneNumbers": [
      {"type": "home", "number": "555-1234"},
      {"type": "work", "number": "555-5678"}
    ]
  }
  ''';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('JSON Data Explorer Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: JsonDataExplorer(
          data: jsonDecode(jsonData),
          theme: JsonDataExplorerTheme.light(), // 或者使用 JsonDataExplorerTheme.dark()
          initialExpandedKeys: [], // 可选,指定初始展开的key路径
        ),
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的Flutter应用,它使用json_data_explorer插件来显示和探索一个硬编码的JSON对象。JsonDataExplorer小部件接受几个参数:

  • data:要显示的JSON数据,必须是Map<String, dynamic>List<dynamic>类型。这里我们使用jsonDecode函数将JSON字符串解析为Dart对象。
  • theme:可选参数,用于定义探索器的主题。这里我们使用了JsonDataExplorerTheme.light()来获取一个浅色主题。
  • initialExpandedKeys:可选参数,用于指定哪些路径下的数据在初始时是展开的。这是一个字符串列表,每个字符串表示一个JSON路径。

运行这个应用后,你将看到一个界面,允许你展开和查看JSON数据的结构,以及每个键的值。这对于调试和查看复杂的JSON数据非常有用。

请确保你已经正确安装了json_data_explorer插件,并根据你的具体需求调整代码。

回到顶部