Why json parsing fails when read from a request but not from a direct String

268 Views Asked by At

I am trying out some code using the shelf package and json_serializable for handling JSON. I have 2 classes and a handler method all shown below (error handling omitted).

@freezed
abstract class User with _$User {
  @JsonSerializable(explicitToJson: true)
  factory User({
    @required int id,
    @JsonKey(name: 'USERNAME') @required String username,
    @required String role,
  }) = _User;
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

@freezed
abstract class _AddUserArgs with _$_AddUserArgs {
  @JsonSerializable(explicitToJson: true)
  factory _AddUserArgs({
    @required User user,
  }) = _AddUserArgs_Freezed;
  factory _AddUserArgs.fromJson(Map<String, dynamic> json) =>
      _$_AddUserArgsFromJson(json);
}

  FutureOr<shelf.Response> _handleAddUser(
    shelf.Request r,
  ) async {
    try {
      // Attempt to call service method.

      final parsedJson = await r.readAsString();

      final _AddUserArgs args = _AddUserArgs.fromJson(
        jsonDecode(
          parsedJson,
        ),
      );
      final AddUserResult result = await exampleService.addUser(
        user: args.user,
      );
      return _rpcResp.Ok(
        json: jsonEncode(
          result.toJson(),
        ),
      );
    }
}

The issue that I am having, and that I really don't quite understand is that whenever this endpoint gets hit I keep getting the following error.

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

When attempting to diagnose the error, I printed the parsedJson to the console and pasted into the handler directly to try to find out why the JSON parsing is failing when using the parsed data as shown below and it worked without any issues.

    try {
      // Attempt to call service method.

      final parsedJson = "{\"user\":{\"id\":0,\"USERNAME\":\"testuser\",\"role\":\"Kind.user()\"}}";

      final _AddUserArgs args = _AddUserArgs.fromJson(
        jsonDecode(
          parsedJson,
        ),
      );
      final AddUserResult result = await exampleService.addUser(
        user: args.user,
      );
      return _rpcResp.Ok(
        json: jsonEncode(
          result.toJson(),
        ),
      );
    }

I am really stumped on why this would happen. I would think that the JSON String printed to console and used in the example above and what is being read from the request are exactly the same, So why is the JSON parsing throwing the error with the request data and not the exact same string?

UPDATE:

The only way I could find to get this to work is by using the code below (which really makes no sense to me at all).

    try {
      // Attempt to call service method.

      final parsedJson = await r.readAsString();
      final allJson = jsonDecode(parsedJson);
      final _AddUserArgs args = _AddUserArgs.fromJson(
        jsonDecode(allJson),
      );
      final AddUserResult result = await exampleService.addUser(
        user: args.user,
      );
      return _rpcResp.Ok(
        json: jsonEncode(
          result.toJson(),
        ),
      );
    }

I figured that since these are nested classes, jsonDecode needs to be called on each of them for some reason, so I tried adding this code to the generated deserialization code.

_$_AddUserArgs_Freezed _$_$_AddUserArgs_FreezedFromJson(
    Map<String, dynamic> json) {
  return _$_AddUserArgs_Freezed(
    user: json['user'] == null
        ? null
        : User.fromJson(
            jsonDecode(json['user']) as Map<String, dynamic>,
          ),
  );
}

but that didn't work out. I can see that the first jsonDecode call returns dynamic, which fixes the issue.

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

I am pretty baffled by this behavior overall. Why can't I simply cast the String to dynamic or Map ? Better yet, why would I have to? jsonDecode returns dynamic. Also, when I used a raw String, none of these issues even occurred.

UPDATE 2;

Even more confused now by the fact that I attempted to at least abstract the 2 json calls that seem to be needed into a function called decode as shown below.

  dynamic decode(dynamic json) {
    final newJson = jsonDecode(json);
    return jsonDecode(newJson);
  }

Now I get this error

type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String'

I am 100% sure that there is something that I am doing wrong here or just plain don't understand. I'm not sure exactly what that is though.

1

There are 1 best solutions below

0
On

The issue ended up being a bug in my code, not sure how common it is, but just in case anyone else runs into it - There was more than one call to the jsonEncode function happening, which was causing the json to be formatted improperly.