jsonflutterdartjsondecoder

Need help decoding JSON in Flutter app; Unhandled Exception: type 'Null' is not a subtype of type 'String' in type cast


I've looked at many examples but I'm still struggling to decode the following json in flutter. I'm new to flutter and I found that json.decode will only work if I map it as List<dynamic>. But, I believe I need to eventually map it to List<Map<String, Map<String, dynamic>>. I could be wrong about this so I'm looking for input. I've created two models, ProductLine and PartNumber. The challenge is that the keys shown as Product line # need to be added to the PartNumber object as a value as well as to the name value of ProductLine. These keys can be any string value and are not predetermined.

    [
      {
        "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"
          }
        ]
      }
    ]

Here are my models for ProductLine and PartNumber. The @JsonSerializable() code is autogenerated by build_runner.

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,
    };
}

The error I'm getting is:

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) E/flutter (18120):

Here is my code to read the json and build my models. What am I doing wrong?

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);
      }
    }
  }
}

Solution

  • class TestCallClass {
      void testFunction() {
        try {
          //print(jsonData);
          List<ProductLine> productLines = ProductLineFromJson(jsonData);
          for (var productLine in productLines) {
            print('Product Line: ${productLine.productLine}');
            for (var product in productLine.products!) {
              print('  Part Number: ${product.partNumber}');
              print('  Orderable: ${product.orderable}');
              print('  Description: ${product.description}');
            }
            print('-------------------');
          }
        } catch (e) {
          print('Error decoding JSON: $e');
        }
      }
    }
    
    class Product {
      String? partNumber;
      bool? orderable;
      String? description;
    
      Product({
        this.partNumber,
        this.orderable,
        this.description,
      });
    
      factory Product.fromJson(Map<String, dynamic> json) {
        return Product(
          partNumber: json['partNumber'],
          orderable: json['orderable'],
          description: json['description'],
        );
      }
    }
    
    List<ProductLine> ProductLineFromJson(dynamic str) => List<ProductLine>.from((str as List<dynamic>).map((x) => ProductLine.fromJson(x)));
    
    class ProductLine {
      String? productLine;
      List<Product>? products;
    
      ProductLine({
        this.productLine,
        this.products,
      });
    
      factory ProductLine.fromJson(Map<String, dynamic> json) {
        final List<Product> products = (json[json.keys.first] as List).map((productJson) => Product.fromJson(productJson)).toList();
    
        return ProductLine(
          productLine: json.keys.first,
          products: products,
        );
      }
    }
    
    var jsonData = [
      {
        "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"}
        ]
      }
    ];