While implementing a simple credential store based on JSON files I ran into a problem where I need to do a seemingly unnecessary roundtrip back to JSON for Dart's oauth2.Credentials.fromJson()
constructor to work.
Background: I am following the oauth2 package's Authorization Code Grant example but with the difference that instead of a single Credentials
object, I want to save a list of Credentials
.
Here is a stripped-down version of my credential store:
import 'dart:convert';
import 'dart:io';
import 'package:oauth2/oauth2.dart';
class CredentialStore {
List<Credentials> get credentials {
// credentials.json would contain a JSON array of objects created with oauth2.Credentials.toJson()
final file = File('credentials.json');
final jsonString = file.readAsStringSync();
final List cred = jsonDecode(jsonString);
return List<Credentials>.from(cred.map((e) => Credentials.fromJson(e)));
}
}
The last line is adapted from this answer and is accepted by the compiler but fails like this on runtime:
Unhandled exception:
type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String'
The code, however, runs successfully, if I take a round-trip back to JSON when mapping, like so:
return List<Credentials>.from(cred.map((e) => Credentials.fromJson(jsonEncode(e))));
This seems unnecessary and looks bad to me. Is this essentially a problem with the Credentials.fromJson()
implementation in that it cannot accept maps already parsed from JSON or is it possible to rewrite my get credentials
implementation in a way that avoids encoding back to JSON?
CodePudding user response:
The Credentials
class exposes the Credentials() constructor, which is what fromJson
also uses.
If you take a look at the implementation of the fromJson
factory, it does some basic validation on the JSON object (i.e. checks that it's valid JSON and the required fields are present) and passes the parsed data to Credentials()
.
Since your data comes from Credentials.toJson()
that you control yourself, it's probably safe to forgo the validation and use the constructor directly:
import 'dart:convert';
import 'dart:io';
import 'package:oauth2/oauth2.dart';
class CredentialStore {
List<Credentials> get credentials {
final file = File('credentials.json');
final jsonString = file.readAsStringSync();
final List cred = jsonDecode(jsonString);
return List<Credentials>.from(cred.map((parsed) {
var tokenEndpoint = Uri.parse(parsed['tokenEndpoint']);
var expiration = DateTime.fromMillisecondsSinceEpoch(parsed['expiration']);
return Credentials(
parsed['accessToken'],
refreshToken: parsed['refreshToken'],
idToken: parsed['idToken'],
tokenEndpoint: tokenEndpoint,
scopes: (parsed['scopes'] as List).map((scope) => scope as String),
expiration: expiration
);
}));
}
}
Regarding your compiler error: in the answer you've linked, the fromJson
method there operates on the result of a JSON decoded Map
, whereas the Credentials.fromJson()
factory operates on a String and as such does not expect the value passed to it to be already decoded.