Search code examples
jsonflutter

How to decode JSON in Flutter when the keys are the values


I'm trying to decode the following JSON in Flutter and I am only getting the first key/value pair in my response. The first level keys (i.e. "Product Line 1", etc.) will all be dynamic and I need to capture those keys as values to add as a variable in my ProductCodeEntity class. Can someone point out what I'm doing wrong?

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

My ProductLineEntity class looks like this. I believe the problem is in the factory ProductLineEntity.fromJson() function, but I haven't been able to get it to work.

class ProductLineEntity {
  int? id;
  String? name;
  List<ProductCodeEntity>? productCodeList;

  ProductLineEntity({
    this.id,
    this.name,
    this.productCodeList,
  });

  factory ProductLineEntity.fromJson(Map<String, dynamic> json) {
    final List<ProductCodeEntity> productCodes = (json[json.keys.first] as List)
        .map((productCodeJson) => ProductCodeEntity.fromJson(productCodeJson))
        .toList();

    return ProductLineEntity(
      name: json.keys.first,
      productCodeList: productCodes,
    );
  }
}

My ProductCodeEntity class looks like this:

class ProductCodeEntity {
  int? id;
  String? partNumber;
  bool? orderable;
  String? description;
  String? productLineName;

  ProductCodeEntity({
    this.id,
    this.partNumber,
    this.orderable,
    this.description,
    this.productLineName,
  });

  factory ProductCodeEntity.fromJson(Map<String, dynamic> json) {
    return ProductCodeEntity(
      id: (json['id'] as num?)?.toInt(),
      partNumber: json['partNumber'] as String?,
      orderable: json['orderable'] as bool?,
      description: json['description'] as String?,
      productLineName: json['productLineName'] as String?,
    );
  }
}

Here is my code to read the JSON file:

Future<List<ProductLineEntity>> readJson() async {
  List<ProductLineEntity> productLineFromJson(dynamic str) =>
    List<ProductLineEntity>.from(
      (str as List<dynamic>).map((x) => ProductLineEntity.fromJson(x)));

  try {
    final String jsonData =
      await rootBundle.loadString('assets/part_numbers2.json');

    final response = (json.decode(jsonData) as List<dynamic>);

    List<ProductLineEntity> productLines = productLineFromJson(response);

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

Solution

    1. JSON structure mismatch: The original JSON data structure is a list containing a single object, while ProductLineEntity.fromJson requires a Map<String, dynamic>, resulting in a type mismatch error.
    2. Product line name handling: In the ProductLineEntity.fromJson method, you use json.keys.first to get the key, which results in only the first one being parsed when parsing a JSON object with multiple keys.

    Here is the revised code

    import 'dart:convert';
    
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:logger/logger.dart';
    
    void main() {
      runApp(const MaterialApp(home: TestPage()));
    }
    
    class TestPage extends StatefulWidget {
      const TestPage({super.key});
    
      @override
      State<TestPage> createState() => _TestPageState();
    }
    
    class _TestPageState extends State<TestPage> {
      var logger = Logger(
        printer: PrettyPrinter(),
      );
    
      @override
      void initState() {
        super.initState();
        logger.init;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: TextButton(
            onPressed: () async {
              List<ProductLineEntity> productLines = await readJson1();
              logger.i(json.encode(productLines));
            },
            child: Text('Read JSON'),
          ),
        );
      }
    
      Future<List<ProductLineEntity>> readJson1() async {
        List<ProductLineEntity> products = [];
    
        try {
          final String jsonData =
          await rootBundle.loadString('assets/part_numbers2.json');
          final List<dynamic> response = json.decode(jsonData);
    
          for (var productLine in response) {
            productLine.forEach((key, value) {
              products.add(ProductLineEntity.fromJson(key, value));
            });
          }
    
          logger.i(products);
    
          return products;
        } catch (error) {
          throw Exception(
              'ProductLineEntity, Unexpected error reading JSON: $error');
        }
      }
    }
    
    class ProductLineEntity {
      int? id;
      String? name;
      List<ProductCodeEntity>? productCodeList;
    
      ProductLineEntity({
        this.id,
        this.name,
        this.productCodeList,
      });
    
      factory ProductLineEntity.fromJson(String key, List<dynamic> value) {
        return ProductLineEntity(
          name: key,
          productCodeList: value.map((e) => ProductCodeEntity.fromJson(e)).toList(),
        );
      }
    
      Map toJson() {
        Map<String, dynamic> data = {};
    
        data['name'] = name;
        data['productCodeList'] =
            productCodeList?.map((e) => e.toJson()).toList() ?? [];
        data['id'] = id;
    
        return data;
      }
    }
    
    class ProductCodeEntity {
      int? id;
      String? partNumber;
      bool? orderable;
      String? description;
      String? productLineName;
    
      ProductCodeEntity({
        this.id,
        this.partNumber,
        this.orderable,
        this.description,
        this.productLineName,
      });
    
      factory ProductCodeEntity.fromJson(Map<String, dynamic> json) {
        return ProductCodeEntity(
          id: (json['id'] as num?)?.toInt(),
          partNumber: json['partNumber'] as String?,
          orderable: json['orderable'] as bool?,
          description: json['description'] as String?,
          productLineName: json['productLineName'] as String?,
        );
      }
    
      Map toJson() => {
        'id': id,
        'partNumber': partNumber,
        'orderable': orderable,
        'description': description,
        'productLineName': productLineName
      };
    }