Flutter/Dart - Parsing JSON to a model

639

Solution 1

Just for the fun of it - I tried to implement the same thing using json_serializable since I recommended it in my comment. Hopefully this shows you that it is not that complicated - in fact, it is simpler than actually figuring out how to code it your self.

First I started with the blank Flutter project.

Second, I added required dependencies to pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
cupertino_icons: ^1.0.2

# Added this
json_annotation: ^4.0.1
  

dev_dependencies:
  flutter_test:
    sdk: flutter

  # and these two...
  build_runner: ^2.0.4
  json_serializable: ^4.1.3

Next thing, I created a separate file json_demo.dart, and took part of your code. I also added few things:

  1. part instruction that will include generated json mapping file
  2. For each class added .fromJson named constructor
  3. For each class added toJson() method.
  4. Added @JsonSerializable() for each class - this is how the tool knows which class to look at
  5. Added @JsonKey(name: 'poster_profile_pic') and @JsonKey(name: 'poster_username') - since your filed names are different than Json property names (poster_profile_pic vs posterProfilePic), you need to tell the tool how to rename it back and forth.
  6. Made all properties nullable (since I'm using latest version with null-safety)
import 'package:json_annotation/json_annotation.dart';

part 'json_demo.g.dart';

@JsonSerializable()
class VideoComments {
  int? id;
  String? comment;
  int? uid;
  int? likes;
  bool? isLikedByUser;

  @JsonKey(name: 'poster_profile_pic')
  String? posterProfilePic;

  @JsonKey(name: 'poster_username')
  String? posterUsername;
  List<Responses>? responses;

  VideoComments(
      {this.id,
      this.comment,
      this.uid,
      this.likes,
      this.isLikedByUser,
      this.posterProfilePic,
      this.posterUsername,
      this.responses});

  factory VideoComments.fromJson(Map<String, dynamic> json) => _$VideoCommentsFromJson(json);
  Map<String, dynamic> toJson() => _$VideoCommentsToJson(this);
}

@JsonSerializable()
class Responses {
  int? id;
  String? comment;
  int? uid;
  int? likes;
  bool? isLikedByUser;

  Responses({this.id, this.comment, this.uid, this.likes, this.isLikedByUser});

  factory Responses.fromJson(Map<String, dynamic> json) => _$ResponsesFromJson(json);
  Map<String, dynamic> toJson() => _$ResponsesToJson(this);
}

Now, simply run flutter pub run build_runner build, and you get json_demo.g.dart file generated:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'json_demo.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

VideoComments _$VideoCommentsFromJson(Map<String, dynamic> json) {
  return VideoComments(
    id: json['id'] as int?,
    comment: json['comment'] as String?,
    uid: json['uid'] as int?,
    likes: json['likes'] as int?,
    isLikedByUser: json['isLikedByUser'] as bool?,
    posterProfilePic: json['poster_profile_pic'] as String?,
    posterUsername: json['poster_username'] as String?,
    responses: (json['responses'] as List<dynamic>?)
        ?.map((e) => Responses.fromJson(e as Map<String, dynamic>))
        .toList(),
  );
}

Map<String, dynamic> _$VideoCommentsToJson(VideoComments instance) =>
    <String, dynamic>{
      'id': instance.id,
      'comment': instance.comment,
      'uid': instance.uid,
      'likes': instance.likes,
      'isLikedByUser': instance.isLikedByUser,
      'poster_profile_pic': instance.posterProfilePic,
      'poster_username': instance.posterUsername,
      'responses': instance.responses,
    };

Responses _$ResponsesFromJson(Map<String, dynamic> json) {
  return Responses(
    id: json['id'] as int?,
    comment: json['comment'] as String?,
    uid: json['uid'] as int?,
    likes: json['likes'] as int?,
    isLikedByUser: json['isLikedByUser'] as bool?,
  );
}

Map<String, dynamic> _$ResponsesToJson(Responses instance) => <String, dynamic>{
      'id': instance.id,
      'comment': instance.comment,
      'uid': instance.uid,
      'likes': instance.likes,
      'isLikedByUser': instance.isLikedByUser,
    };

Note how it correctly renamed the json property based on the annotation:

posterProfilePic: json['poster_profile_pic'] as String?,

One more thing to notice - this is how it fixed the problem you had:

responses: (json['responses'] as List<dynamic>?)
        ?.map((e) => Responses.fromJson(e as Map<String, dynamic>))
        .toList(),

From now on, each time you change your class, simple re-run the build script.

As you explore further, you'll see that it can handle enums, it has nice annotations to automatically rename all fields in a class from snake case to kebab case (or whatever it is called). But most importantly - does it in a consistent and tested way. As you add more classes, it will really help you save some time...

And to make your life easier, create user snippet in VS Code (File->Preferences->User snippets):

{
    "Json Serializible": {
        "prefix": "serial",
        "body": [
            "factory ${1}.fromJson(Map<String, dynamic> json) => _$${1}FromJson(json);",
            "Map<String, dynamic> toJson() => _$${1}ToJson(this);"
        ]
    }
}

Here's how to use it:

  1. Copy the name of your class
  2. start typing 'serial' - this is the shortcut for the code snippet
  3. Select the snippet and paste the class name - it will paste it 3 times where needed.

See - simpler than learning how to manually encode/decode Json....

Solution 2

As @Andrija suggested, that is certainly a good way, if you don't want to use that then go ahead with the vscode extension bendixma.dart-data-class-generator, create your class and keep your variables, just do ctrl + shift + p -> Search data class, hit enter when you find dart data class generator from class properties.

Share:
639
James P
Author by

James P

Updated on December 22, 2022

Comments

  • James P
    James P over 1 year

    I was wondering if anyone could help, please? I'm very new to Flutter/Dart here and I'm trying to parse a nested JSON response, into a model. I've used the "JSON to Dart" generator, which seems to have worked well, except when it is parsing "responses".

    I suspect the issue is because the "responses" vary in results - sometimes it could be null, a single array, or multiple.

    Running .runtimeType has shown me that it can return null if it's empty, List<dynamic> if there is only one array, and _InternalLinkedHashMap<String, dynamic> when there are multiple.

    I have tried many different approaches to try and resolve this and looked through many different StackOverflow answers, but nothing seems to work. The error simply changes with every change I make.

    Below is my code and my error.

    The error: _TypeError (type '(dynamic) => Null' is not a subtype of type '(String, dynamic) => void' of 'f')

    The code:

    class VideoComments {
      int id;
      String comment;
      int uid;
      int likes;
      bool isLikedByUser;
      String posterProfilePic;
      String posterUsername;
      List<Responses> responses;
    
      VideoComments(
          {this.id,
          this.comment,
          this.uid,
          this.likes,
          this.isLikedByUser,
          this.posterProfilePic,
          this.posterUsername,
          this.responses});
    
      VideoComments.fromJson(Map<String, dynamic> json) {
        print("RESP: ${json['responses'].runtimeType}");
    
        id = json['id'];
        comment = json['comment'];
        uid = json['uid'];
        likes = json['likes'];
        isLikedByUser = json['isLikedByUser'];
        posterProfilePic = json['poster_profile_pic'];
        posterUsername = json['poster_username'];
    
        if (json['responses'] != null) {
          List<Responses> responses = [];
          json['responses'].forEach((v) {
            responses.add(new Responses.fromJson(v));
          });
        }
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = new Map<String, dynamic>();
        data['id'] = this.id;
        data['comment'] = this.comment;
        data['uid'] = this.uid;
        data['likes'] = this.likes;
        data['isLikedByUser'] = this.isLikedByUser;
        data['poster_profile_pic'] = this.posterProfilePic;
        data['poster_username'] = this.posterUsername;
        if (this.responses != null) {
          data['responses'] = this.responses.map((v) => v.toJson()).toList();
        }
        return data;
      }
    }
    
    class Responses {
      int id;
      String comment;
      int uid;
      int likes;
      bool isLikedByUser;
    
      Responses({this.id, this.comment, this.uid, this.likes, this.isLikedByUser});
    
      Responses.fromJson(Map<String, dynamic> json) {
        id = json['id'];
        comment = json['comment'];
        uid = json['uid'];
        likes = json['likes'];
        isLikedByUser = json['isLikedByUser'];
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = new Map<String, dynamic>();
        data['id'] = this.id;
        data['comment'] = this.comment;
        data['uid'] = this.uid;
        data['likes'] = this.likes;
        data['isLikedByUser'] = this.isLikedByUser;
        return data;
      }
    }
    

    Any help is appreciated!

    • Andrija
      Andrija almost 3 years
      Not sure if you tried this - this worked for me: flutter.dev/docs/development/data-and-backend/… Challange is - you still need to create the class yourself, but at least the json mapping is done properly (and with null safety).
    • James P
      James P almost 3 years
      Thanks, Andrija. I have seen this, I'm not sure I'm at a level of proficiency with Dart/Flutter yet to be using this. It seems well above my skill level
    • Kamal Bunkar
      Kamal Bunkar almost 3 years
      when you use online tools to convert JSON into Dart for example - dripcoding.com/json-to-dart then always check the data type of each variable. You are getting error because one of the variable is define as STRING but the actual value is NON String value. You can do one thing - change all variable data type to dynamic. it will resolve the issue.
  • James P
    James P almost 3 years
    thank you very much for this. One of the best answers I've come across in a while and as you said, it explains how simple it can be too. So with that said, I will not re-write it all to start using this!