How to cast an `_InternalLinkedHashMap` created fr

2019-04-16 12:50发布

问题:

Sometimes, when traversing complex json files in Dart it would be nice if we could tell our editor what the expected structure is so that we can make best use of the editor's intelligent code completion features.

As a toy example, consider the script writer.dart:

import 'dart:convert' show json;

main() {
  Map<String, num> myMap = {"a": 1, "b": 2, "c": 3};
  print(json.encode(myMap));
}

Let's say we use writer.dart to create a json file:

dart writer.dart > data.json

We have another script, called reader.dart, that will read this file and ideally interpret the data as a Map<String, num> instance:

import 'dart:io' show File;
import 'dart:convert' show json;

main() async {
  Map<String, num> myMap = json.decode(await File("data.json").readAsString());
}

This script, however, throws the exception type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, num>'.

Naive attempts such as the following also don't work:

var myMap = json.decode(await File("data.json").readAsString()) as Map<String, num>;

Of course we could do something like:

var myMap = Map<String, num>();
(json.decode(await File("data.json").readAsString()) as Map).forEach((k, v) {
  myMap[k] = v;
});

But that's really ugly!

What is the best way to let the editor know what data structure to expect when parsing json?

回答1:

The map returned by json.decode is a mutable Map<String, Object>. In this case, you know that the values are all num instances, but the JSON decoder didn't know that. Even if it did, since the value is mutable, the decoder has to assume someone might want to add any object to the map later.

There are two basic ways to get a map with a stricter type from another map: Copying to a new map (and checking the stricter type while copying), or wrapping/adapting the original map (and checking the stricter type on every access).

You create a new map of whatever type you want by:

Map<String, num> newMap = Map<String, num>.from(originalMap);

The argument to Map.from is a Map<dynamic, dynamic>, so it really accepts any map, and it then copies each entry into a new map with the requested type. If you change the original map after making the copy, the copy is unaffected.

The other option is to wrap using:

Map<String, num> wrappedMap = originalMap.cast<String, num>();

This does nothing up-front. Every time you try to look something up in the wrapped map, it checks the argument type and return value type against the types you asked for. If you change the original map, the wrapped map changes as well (which is another reason it can't just check the types once, it has to check every time in case the value has changed).

The two approaches have different efficiency trade-offs. Copying everything takes space and time. Checking types takes time too. If you plan to use the same map over and over again, or to modify it, then creating a new map is probably more efficient. If not, Map.cast is easier to use.



回答2:

(Although this answer seems obvious in retrospect, I was struggling with this for a while and only figured it out while composing the above question on this site. Just in case it is useful to anyone else, I went ahead and posted the question anyway.)

Just use the from constructor!

var myMap = Map<String, num>.from(json.decode(await File("data.json").readAsString()));


标签: json dart