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

78 views Asked by At

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

There are 1 answers

0
Rassel98 On BEST ANSWER
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"}
    ]
  }
];