Search code examples
jsonflutterdartdeserialization

How to properly create instances containing a Map from a json file in dart?


I'm trying to deserialize a json file and create instances from it but whatever way I use, I end up stucked because of the dynamic type :

type '_Map<String, dynamic>' is not a subtype of type 'Map<String, int>'

Here's my model :

class Race {
  final String name;
  final Map<String, int> abilitiesUpdater;

  const Race({
    required this.name,
    required this.abilitiesUpdater
  });

  static fromJson(json) => Race(name: json['name'], abilitiesUpdater: json['abilitiesUpdater']);
}

Here's how I'm trying to deserialize the json file :

class RacesApi {
  static Future<List<Race>> getRacesLocally(BuildContext context) async {
    final assetBundle = DefaultAssetBundle.of(context);
    final String fileContent = await assetBundle.loadString('Assets/COC_Monstres/Races.json');

    List<dynamic> parsedListJson = jsonDecode(fileContent);
    List<Race> racesList = List<Race>.from(parsedListJson.map<Race>((dynamic i) => Race.fromJson(i)));
    return racesList;
  }
}

Here's my json file :

[
  {
    "name": "Vampire",
    "abilitiesUpdater": {
      "DEX": 2,
      "CHA": 2
    }
  },
  {
    "name": "Créature du lagon",
    "abilitiesUpdater": {
      "FOR": 2,
      "CON": 2
    }
  },
  ...
]

How can I properly cast this json object to fit into my class ?


Solution

  • Edit : To have something a little bit more handy and scalable, I created an extension, and it works fine eventhough I have to cast twice the object...

    My model :

    // import my extension
    
    class Race {
      Race({
        required this.name,
        required this.abilitiesUpdater,
      });
      late final String name;
      late final Map<String, int> abilitiesUpdater;
      // late final AbilitiesUpdater abilitiesUpdater;
    
      Race.fromJson(Map<String, dynamic> json){
        name = json['name'];
        abilitiesUpdater = (json['abilitiesUpdater'] as Map<String, dynamic>).parseToStringInt();
      }
    }
    

    My extension :

    extension Casting on Map<String, dynamic> {
      Map<String, int> parseToStringInt() {
        final Map<String, int> map = {};
    
        forEach((key, value) {
          int? testInt = int.tryParse(value.toString());
          if (testInt != null) {
            map[key] = testInt;
          } else {
            debugPrint("$value can't be parsed to int");
          }
        });
        return map;
      }
    }
    

    Once again, any help on cleaning this is appreciated !


    Original answer :

    Thanks to Sanket Patel's answer, I ended up with a few changes that made my code works. However I'm pretty clueless on why I can't directly cast a

    Map<String, dynamic>
    

    object into a

    Map<String, int>
    

    one. Any info on this would be appreciated :)

    Here's how I changed my model class in the end :

    class Race {
      Race({
        required this.name,
        required this.abilitiesUpdater,
      });
      late final String name;
      late final AbilitiesUpdater abilitiesUpdater;
    
      Race.fromJson(Map<String, dynamic> json){
        name = json['name'];
        abilitiesUpdater = AbilitiesUpdater.fromJson(json['abilitiesUpdater']);
      }
    }
    
    class AbilitiesUpdater {
    
      final Map<String, int> abilitiesUpdater = {};
    
      AbilitiesUpdater.fromJson(Map<String, dynamic> json){
        json.forEach((key, value) {
          abilitiesUpdater[key] = int.parse(value.toString());
        });
      }
    }