Flutter display nested json in ListView return type String is not a subtype of type 'Map<String, dynamic>' in type cast

870

I find a solution.

The model I'm using is:

model.dart

import 'dart:convert';

List<User> userFromJson(String str) =>
    List<User>.from(json.decode(str).map((x) => User.fromJson(x)));

String userToJson(List<User> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class User {
  User({
    this.name,
    this.surname,
    this.areaName,
    this.details,
  });

  String name;
  String surname;
  String areaName;
  String details;

  factory User.fromJson(Map<String, dynamic> json) => User(
        name: json["name"],
        surname: json["surname"],
        areaName: json["areaName"],
        details: json["details"],
      );

  Map<String, dynamic> toJson() => {
        "name": name,
        "surname": surname,
        "areaName": areaName,
        "details": details,
      };
}

Details DetailsFromJson(String str) => Details.fromJson(json.decode(str));

String DetailsToJson(Details data) => json.encode(data.toJson());

class Details {
  Details({
    this.work,
    this.address,
  });

  Work work;
  Address address;

  factory Details.fromJson(Map<String, dynamic> json) => Details(
        work: Work.fromJson(json["work"]),
        address: Address.fromJson(json["address"]),
      );

  Map<String, dynamic> toJson() => {
        "work": work.toJson(),
        "address": address.toJson(),
      };
}

class Address {
  Address({
    this.street,
    this.city,
  });

  String street;
  String city;

  factory Address.fromJson(Map<String, dynamic> json) => Address(
        street: json["street"],
        city: json["city"],
      );

  Map<String, dynamic> toJson() => {
        "street": street,
        "city": city,
      };
}

class Work {
  Work({
    this.salary,
    this.company,
  });

  String salary;
  String company;

  factory Work.fromJson(Map<String, dynamic> json) => Work(
        salary: json["salary"],
        company: json["company"],
      );

  Map<String, dynamic> toJson() => {
        "salary": salary,
        "company": company,
      };
}

The main code is: main.dart

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'user.dart';
import 'details.dart';
import 'address.dart';
import 'work.dart';
import 'package:built_value/serializer.dart';
import 'dart:convert' as json;
import 'package:meta/meta.dart';
import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
import 'dart:developer';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;


  Future<List<User>> getUsers() async {
    var url = "empty for now"; // here there will be the request now I' m using fake data
    //final response = await http.get(url);
    //final parsed = json.decode(response.body).cast<Map<String, dynamic>>();
    String data = r'''[
      {
        "name": "260",
        "surname": "430011",
        "areaName": "Camera1-Zone1",
        "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"
      },
      {
        "name": "260",
        "surname": "430011",
        "areaName": "Camera1-Zone1",
        "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"
      }
    ]''';
    List<User> users= userFromJson(res);
    return users;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("List"),
          backgroundColor: Colors.blue,
        ),
        body: Container(
          child: FutureBuilder<List<User>>(
            future: getUsers(),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return ListView.builder(
                  itemCount: snapshot.data.length,
                  itemBuilder: (context, int index) {
                    return ListTile(
                        title: Text(snapshot.data[index].name),
                        subtitle: Text(snapshot.data[index].surname));
                  },
                );
              } else if (snapshot.hasError) {
                return Center(
                  child: Text(snapshot.error.toString()),
                );
              }
              return Center(
                child: CircularProgressIndicator(),
              );
            },
          ),
        ),
      ),
    );
  }
}
Share:
870
SpaghettiFunk
Author by

SpaghettiFunk

Updated on November 27, 2022

Comments

  • SpaghettiFunk
    SpaghettiFunk over 1 year

    I' m going on with my journey in Flutter. I' m able to display a simple json in a ListView. Now I' m trying with a json with nested objects but everytime I run the app I get the error

    I' m generating the code for json model classes like suggested in Flutter official documentation.

    The error seems to happen when I' m parsing the User. While debugging, I see that name and surname are successfully parsed, but when I jump to the details object in user.g.dart at the row:

    json['details'] == null
            ? null
            : Details.fromJson(json['details'] as Map<String, dynamic>),
    

    I see the error:

    type String is not a subtype of type 'Map<String, dynamic>' in type cast
    

    How can I solve this error, and how I can access all the nested objects for displaying them?

    Here is the json I have to parse. I edited it, there is no arename field:

    data.json

    [
        {
            "name": "jhon",
            "surname": "walker",
            "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"    
        },
        {
            "name": "peter",
            "surname": "parker",
            "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"    
        }
    ]
    

    Here there are my model classes:

    user.dart

    import 'package:json_annotation/json_annotation.dart';
    import 'details.dart';
    
    part 'user.g.dart';
    
    @JsonSerializable(explicitToJson: true)
    class User {
      User(this.name, this.surname, this.details);
    
      String name;
      String surname;
      Details details;
    
      factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
      Map<String, dynamic> toJson() => _$UserToJson(this);
    }
    

    user.g.dart

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'user.dart';
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    User _$UserFromJson(Map<String, dynamic> json) {
      return User(
        json['name'] as String,
        json['surname'] as String,
        json['details'] == null
            ? null
            : Details.fromJson(json['details'] as Map<String, dynamic>),
      );
    }
    
    Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
          'name': instance.name,
          'surname': instance.surname,
          'details': instance.details?.toJson(),
        };
    

    details.dart

    import 'package:json_annotation/json_annotation.dart';
    import 'address.dart';
    import 'work.dart';
    part 'details.g.dart';
    
    @JsonSerializable(explicitToJson: true)
    class Details {
      Details(this.work, this.address);
    
      Work work;
      Address address;
    
      factory Details.fromJson(Map<String, dynamic> json) =>
          _$DetailsFromJson(json);
      Map<String, dynamic> toJson() => _$DetailsToJson(this);
    }
    

    details.g.dart

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'details.dart';
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    Details _$DetailsFromJson(Map<String, dynamic> json) {
      return Details(
        json['work'] == null
            ? null
            : Work.fromJson(json['work'] as Map<String, dynamic>),
        json['address'] == null
            ? null
            : Address.fromJson(json['address'] as Map<String, dynamic>),
      );
    }
    
    Map<String, dynamic> _$DetailsToJson(Details instance) => <String, dynamic>{
          'work': instance.work?.toJson(),
          'address': instance.address?.toJson(),
        };
    

    work.dart

    import 'package:json_annotation/json_annotation.dart';
    part 'work.g.dart';
    
    @JsonSerializable(explicitToJson: true)
    class Work {
      Work(this.salary, this.company);
    
      String salary;
      String company;
    
      factory Work.fromJson(Map<String, dynamic> json) => _$WorkFromJson(json);
      Map<String, dynamic> toJson() => _$WorkToJson(this);
    }
    

    work.g.dart

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'work.dart';
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    Work _$WorkFromJson(Map<String, dynamic> json) {
      return Work(
        json['salary'] as String,
        json['company'] as String,
      );
    }
    
    Map<String, dynamic> _$WorkToJson(Work instance) => <String, dynamic>{
          'salary': instance.salary,
          'company': instance.company,
        };
    

    address.dart

    import 'package:json_annotation/json_annotation.dart';
    part 'address.g.dart';
    
    @JsonSerializable(explicitToJson: true)
    class Address {
      Address(this.street, this.city);
      
      String street;
      String city;
    
      factory Address.fromJson(Map<String, dynamic> json) =>
          _$AddressFromJson(json);
      Map<String, dynamic> toJson() => _$AddressToJson(this);
    }
    

    address.g.dart

    // GENERATED CODE - DO NOT MODIFY BY HAND
    
    part of 'address.dart';
    
    // **************************************************************************
    // JsonSerializableGenerator
    // **************************************************************************
    
    Address _$AddressFromJson(Map<String, dynamic> json) {
      return Address(
        json['street'] as String,
        json['city'] as String,
      );
    }
    
    Map<String, dynamic> _$AddressToJson(Address instance) => <String, dynamic>{
          'street': instance.street,
          'city': instance.city,
        };
    

    Finally, here there is the application: main.dart

    import 'package:flutter/material.dart';
    import 'dart:convert';
    import 'package:http/http.dart' as http;
    import 'user.dart';
    import 'details.dart';
    import 'address.dart';
    import 'work.dart';
    import 'package:built_value/serializer.dart';
    import 'dart:convert' as json;
    import 'package:meta/meta.dart';
    import 'package:built_value/built_value.dart';
    import 'package:built_collection/built_collection.dart';
    import 'dart:developer';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            // This is the theme of your application.
            //
            // Try running your application with "flutter run". You'll see the
            // application has a blue toolbar. Then, without quitting the app, try
            // changing the primarySwatch below to Colors.green and then invoke
            // "hot reload" (press "r" in the console where you ran "flutter run",
            // or simply save your changes to "hot reload" in a Flutter IDE).
            // Notice that the counter didn't reset back to zero; the application
            // is not restarted.
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      // This widget is the home page of your application. It is stateful, meaning
      // that it has a State object (defined below) that contains fields that affect
      // how it looks.
    
      // This class is the configuration for the state. It holds the values (in this
      // case the title) provided by the parent (in this case the App widget) and
      // used by the build method of the State. Fields in a Widget subclass are
      // always marked "final".
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
    
    
      Future<List<User>> getUsers() async {
        var url = "empty for now"; // here there will be the request now I' m using fake data
        //final response = await http.get(url);
        //final parsed = json.decode(response.body).cast<Map<String, dynamic>>();
        String data = r'''[
          {
            "name": "260",
            "surname": "430011",
            "areaName": "Camera1-Zone1",
            "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"
          },
          {
            "name": "260",
            "surname": "430011",
            "areaName": "Camera1-Zone1",
            "details": "{\"work\":{\"salary\":\"116\",\"company\":\"evolution\"},\"address\":{\"street\":\"grand station\",\"city\":\"salt lake\"}}"
          }
        ]''';
        final parsed = json.jsonDecode(data).cast<Map<String, dynamic>>();
        return parsed.map<User>((json) {
          return User.fromJson(json);
        }).toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: Text("List"),
              backgroundColor: Colors.blue,
            ),
            body: Container(
              child: FutureBuilder<List<User>>(
                future: getUsers(),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ListView.builder(
                      itemCount: snapshot.data.length,
                      itemBuilder: (context, int index) {
                        return ListTile(
                            title: Text(snapshot.data[index].name),
                            subtitle: Text(snapshot.data[index].surname));
                      },
                    );
                  } else if (snapshot.hasError) {
                    return Center(
                      child: Text(snapshot.error.toString()),
                    );
                  }
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                },
              ),
            ),
          ),
        );
      }
    }
    

    Can you help me in solve this error and displaying all the nested data? Thank you!

    Thank you for the help!

    • Admin
      Admin about 3 years
    • SpaghettiFunk
      SpaghettiFunk about 3 years
      No, I mean if I try to return like in the answer a I get a null name reference or something like that. I should loop the parsed variable and add each element of the json by accessing them with the [] operator? Can you give me a hint please? Thank you!
  • SpaghettiFunk
    SpaghettiFunk about 3 years
    I have a few things to say: - I can't find response.isEmpty using Android Studio - I can't find json.decode using Android Studio, I only have jsonDecode or do I need a different libray? - In the case of the json with all the escape charter, I won' t be able to access the single field right?
  • SpaghettiFunk
    SpaghettiFunk about 3 years
    I have already built the solution working with the json with escape character like the second json you posted. I want to be able to get all the different field in details with separated object, like it should be and not to have one only big String object and probably use regex expression to get the fields in the details. Is this possible?