Steve3p0
Steve3p0

Reputation: 2527

Can't map a JSON response to a class object in Flutter

Here is yet another "Expected a value of type 'Map<String, dynamic>', but got one of type 'List'" questions. I've scoured several Stack Overflow posts on the subject including the following:

I've also tried to follow this Flutter cookbook example.

So, I want to simply call an API that returns a JSON list of lists. Each one of those inner lists of strings corresponds to a class object I've defined below called 'Provider'. I have created class factory methods to map the JSON to my 'Provider' class:

I would prefer to use the first factory method, rather than the second, because I would like to reference each string by it's name, not an array index as you will see in the code.

UPDATE:

As @OmiShah pointed out, the serialized JSON I was passing back from my Python API had no schema, so my deserialized JSON response contains no information about the data. So I couldn't make references to the data like so:

json["name"]

So I changed my Python API to serialize a list of strongly typed class objects and pass that I back to flutter as JSON. Here is a sample of what that data looks like now in Python before it's passed back to flutter:

[
    Provider(name='Google Translate API', id='google.translate_api.v3', img_url='ggl.png'),
    Provider(name='Microsoft Translator', id='microsoft.translator.v3', img_url='mcs.png'),
    Provider(name='Amazon Translate API', id='amazon.translate.api.v2', img_url='aws.png'),
]

And this is what that data looks like after it's deserialized by flutter:

final List<dynamic> listProviders = jsonDecode(response.body);

// contents of listProviders
[
    "Provider(name='Google Translate API', id='google.translate_api.v3', img_url='ggl.png')",
    "Provider(name='Microsoft Translator', id='microsoft.translator.v3', img_url='mcs.png')",
    "Provider(name='Amazon Translate API', id='amazon.translate.api.v2', img_url='aws.png')",
]

I then cast it as list of maps (listMap) and then attempt to map each object to my 'Provider' class object (providerMap) and finally I convert providerMap to a list:

final List<dynamic> listProviders = jsonDecode(response.body);
final List<Map<String, dynamic>> listMap = listProviders.cast<Map<String, dynamic>>();
var providerMap = listMap.map<Provider>((json) => Provider.fromJsonMap(json));
List<Provider> providers = providerMap.toList();

I get the following error when I attempt to convert it to a list:

Error: Expected a value of type 'Map<String, dynamic>', but got one of type 'String'

I am relatively new to Flutter. I would like to pass data around as strongly typed objects and I want to do it in a way that makes sense.

The custom class object I created:

class Provider
{
    final String name;
    final String id;
    final String imageUrl;

    Provider( {required this.name, required this.id, required this.imageUrl });

    // Calling this function fails 
    factory Provider.fromJsonMap(Map<String, dynamic> json)
    {
        Provider provider = Provider
        (           
            name:     json["name"]     as String,
            id:       json["id"]       as String,
            imageUrl: json["imageUrl"] as String,
        );

        return provider;
    }

    // Calling this function works
    factory Provider.fromJsonListDynamic(List<dynamic> json)
    {
        Provider provider = Provider
        (
            name:     json[0] as String,
            id:       json[1] as String,
            imageUrl: json[2] as String,
        );

        return provider;
    }
}

And it's called in this Future async function in a PageState class:

class _DetectorPageState extends State<DetectorPage>
{
    ...

    Future<List<Provider>> getMtProviders() async 
    {
        Uri url = Uri.parse(SOME_API_URL);
        http.Response response = await http.get(url);

        if (response.statusCode == 200) 
        {
            // THIS CODE DOES NOT WORK
            final List<dynamic> listProviders = jsonDecode(response.body);
            final List<Map<String, dynamic>> listMap = listProviders.cast<Map<String, dynamic>>();
            var providerMap = listMap.map<Provider>((json) => Provider.fromJsonMap(json));
            List<Provider> providers = providerMap.toList();
        
            // THIS CODE WORKS
            // List<dynamic> listProviders = jsonDecode(response.body);
            // List<Provider> providers = listProviders.map((provider) => Provider.fromJsonListDynamic(provider as List<dynamic>)).toList();

            return providers;
        } 
        else 
        {
            print('Request failed with status: ${response.statusCode}.');
            return [];
        }
    }
}

Upvotes: 2

Views: 439

Answers (1)

Steve3p0
Steve3p0

Reputation: 2527

The problem was with the JSON serialization in my python API. I need to serialize a list of dictionary objects that contain my data and not serialize a list of custom class objects.

providers_dict = \   # Sample Data
[
    {"name": "Google Translate API", "id": "google.translate_api.v3", "img_url": "ggl_translate.png"},
    {"name": "Microsoft Translator", "id": "microsoft.translator.v3", "img_url": "mcs_translate.png"},
    {"name": "Amazon Translate API", "id": "amazon.translate.api.v2", "img_url": "aws_translate.png"},
]

Then you call the json.dumps method to serialize the data into JSON like so:

providers_json = json.dumps(providers_dict)
return providers_json

Upvotes: 1

Related Questions