You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter JSON解码报错:Null无法转为String,求解决方案

解决Flutter动态JSON键的解码问题

作为Flutter新手,解码给定JSON数据时遇到困难:json.decode返回List<dynamic>,需要将其映射为对应模型,但JSON中的动态键(如"Product line #")需同时作为ProductLine的name和PartNumber的productLine值,这些键是不确定的字符串。

给定的JSON数据

[
  {
    "Product line 1": [
      {
        "partNumber": "160-9013-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9104-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9105-900",
        "orderable": false,
        "description": "Part Number Description"
      }
    ]
  },
  {
    "Product line 2": [
      {
        "partNumber": "160-9113-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9114-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9115-900",
        "orderable": false,
        "description": "Part Number Description"
      }
    ]
  },
  {
    "Product line 3": [
      {
        "partNumber": "160-9205-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9211-900",
        "orderable": true,
        "description": "Part Number Description"
      },
      {
        "partNumber": "160-9212-900",
        "orderable": false,
        "description": "Part Number Description"
      }
    ]
  }
]

已定义的模型代码

const uuid = Uuid();
    
@JsonSerializable()
class PartNumber {
  PartNumber({
    String? id,
    required this.partNumber,
    required this.orderable,
    required this.description,
    required this.productLine,
  }) : id = id ?? uuid.v4();
    
  final String id;
  final String partNumber;
  final bool   orderable;
  final String description;
  final String productLine;
    
  factory PartNumber.fromJson(Map<String, dynamic> json) =>
      _$PartNumberFromJson(json);
    
  Map<String, dynamic> toJson() => _$PartNumberToJson(this);
 
  PartNumber _$PartNumberFromJson(Map<String, dynamic> json) => PartNumber(
      id: json['id'] as String?,
      partNumber: json['partNumber'] as String,
      orderable: json['orderable'] as bool,
      description: json['description'] as String,
      productLine: json['productLine'] as String,
    );
    
  Map<String, dynamic> _$PartNumberToJson(PartNumber instance) =>
    <String, dynamic>{
      'id': instance.id,
      'partNumber': instance.partNumber,
      'orderable': instance.orderable,
      'description': instance.description,
      'productLine': instance.productLine,
    };
}

@JsonSerializable()
class ProductLine {
  ProductLine({
    String? id,
    required this.name,
    required this.partNumbers,
  }) : id = id ?? uuid.v4();

  final String id;
  final String name;
  final List<PartNumber> partNumbers;

  factory ProductLine.fromJson(Map<String, dynamic> json) =>
      _$ProductLineFromJson(json);

  Map<String, dynamic> toJson() => _$ProductLineToJson(this);

  ProductLine _$ProductLineFromJson(Map<String, dynamic> json) => ProductLine(
      id: json['id'] as String?,
      name: json['name'] as String,
      partNumbers: (json['partNumbers'] as List<dynamic>)
          .map((e) => PartNumber.fromJson(e as Map<String, dynamic>))
          .toList(),
    );

  Map<String, dynamic> _$ProductLineToJson(ProductLine instance) =>
    <String, dynamic>{
      'id': instance.id,
      'name': instance.name,
      'partNumbers': instance.partNumbers,
    };
}

遇到的错误

E/flutter (18120): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'String' in type cast
E/flutter (18120): #0      _$ProductLineFromJson (package:test_app/models/product_line.g.dart:11:26)
E/flutter (18120): #1      new ProductLine.fromJson (package:test_app/models/product_line.dart:24:7)
E/flutter (18120): #2      PartNumberManager.formatPartNumbers (package:test_app/managers/part_number_manager.dart:62:39)
E/flutter (18120): 
E/flutter (18120): #3      PartNumberManager.loadPartNumbers (package:test_app/managers/part_number_manager.dart:36:5)

当前的JSON处理代码

class PartNumberManager {
  List<PartNumber>? partNumbers;

  Future<bool> loadPartNumbers() async {
    await formatPartNumbers();
    final partNumbersLoaded = await batchInsertPartNumbers();

    return partNumbersLoaded;
  }

  // Read part_numbers.json file before adding data to the sql database
  Future<List<dynamic>> readJson() async {
    try {
      final String response =
          await rootBundle.loadString('assets/part_numbers.json');
      final data = (json.decode(response) as List<dynamic>);

      return data;
    } catch (error) {
      throw Exception(
          'PartNumberManager, Unexpected error reading JSON: $error');
    }
  }

  Future<void> formatPartNumbers() async {
    List<ProductLine> tmpProductLines = [];

    List<dynamic> data = await readJson();

    for (var i = 0; i < data.length; i++) {
      final productLine = ProductLine.fromJson(data[i]);
      tmpProductLines.add(productLine);
    }

    for (var product in tmpProductLines) {
      for (var pnum in product.partNumbers) {
        final partNumber = PartNumber(
          partNumber: pnum.partNumber,
          orderable: pnum.orderable,
          description: pnum.description,
          productLine: product.name,
        );
        partNumbers!.add(partNumber);
      }
    }
  }
}

问题根源

  1. 结构不匹配:直接调用ProductLine.fromJson(data[i])错误,因为data[i]的结构是{动态键: [零件数组]},而非模型期望的{"name": "...", "partNumbers": [...]},导致解析时找不到name和partNumbers字段,触发类型转换错误。
  2. 空指针风险:partNumbers变量未初始化,直接用partNumbers!.add会触发空指针异常。

修正后的处理代码

class PartNumberManager {
  // 初始化空列表,避免空指针
  List<PartNumber> partNumbers = [];

  Future<bool> loadPartNumbers() async {
    await formatPartNumbers();
    final partNumbersLoaded = await batchInsertPartNumbers();
    return partNumbersLoaded;
  }

  Future<List<dynamic>> readJson() async {
    try {
      final String response =
          await rootBundle.loadString('assets/part_numbers.json');
      final data = (json.decode(response) as List<dynamic>);
      return data;
    } catch (error) {
      throw Exception('PartNumberManager, Unexpected error reading JSON: $error');
    }
  }

  Future<void> formatPartNumbers() async {
    List<ProductLine> tmpProductLines = [];
    List<dynamic> data = await readJson();

    for (var item in data) {
      // 提取动态产品线名称和对应的零件列表
      MapEntry<String, dynamic> entry = item.entries.first;
      String productLineName = entry.key;
      List<dynamic> partsJson = entry.value;

      // 构建零件列表,同时绑定产品线名称
      List<PartNumber> partNumbersList = partsJson.map((partJson) {
        return PartNumber(
          partNumber: partJson['partNumber'] as String,
          orderable: partJson['orderable'] as bool,
          description: partJson['description'] as String,
          productLine: productLineName,
        );
      }).toList();

      // 构建ProductLine对象
      ProductLine productLine = ProductLine(
        name: productLineName,
        partNumbers: partNumbersList,
      );
      tmpProductLines.add(productLine);
    }

    // 合并所有零件到全局列表
    for (var product in tmpProductLines) {
      partNumbers.addAll(product.partNumbers);
    }
  }

  // 补充数据库插入逻辑(原代码未实现)
  Future<bool> batchInsertPartNumbers() async {
    // 这里编写你的数据库插入代码
    return true;
  }
}

额外说明

  • 由于JSON结构与模型结构不匹配,无法直接用JsonSerializable的fromJson解析顶层动态键,手动遍历键值对是最直接的解决方案。
  • 若后续需要用JsonSerializable处理动态键,可以自定义JsonConverter,但当前场景手动解析更简洁。

内容的提问来源于stack exchange,提问作者Joe G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 18:45:16