Home > other >  Dart: Generate list of oauth2.Credentials from JSON without encoding items back to JSON for construc
Dart: Generate list of oauth2.Credentials from JSON without encoding items back to JSON for construc

Time:07-25

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.

  • Related