google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.55k stars 395 forks source link

IMap: _TypeError (type 'String' is not a subtype of type 'num / bool / int' in type cast) #1332

Closed JoanSchi closed 9 months ago

JoanSchi commented 1 year ago

Hi,

If json_serializable is used with IMap a Type error occurs when string is converted in the into bool / int/ double (num) or duration (FromJson). However DateTime, BigInt or Uri for example are parsed as expected.

json_serializable: ^6.7.0 fast_immutable_collections: ^9.1.5

FromJson Looking to the generated code some types are not parsed correctly with the function FromJson:

Type error IMap<X, String>.fromJson X = int: (value) => value as bool, X = bool: (value) => value as bool, X double: (value) => (value as num).toDouble(),

Works good IMap<X, String>.fromJson X: (value) => DateTime.parse(value as String), X: (value) => BigInt.parse(value as String),

ToJson I do not known if it is necessary, but not everything is converted to string:

Not converted to string (problem?): double/int/bool: (value) => value (Should this not be a String?)

Converted to string (works good) DateTime: (value) => value.toIso8601String() BigInt: (value) => value.toString(),

If this is resolved, I can resolve https://github.com/marcglasberg/fast_immutable_collections/issues/58.

Thank you.

Example

import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:imap_test/example.dart';

void main() {

  final SerializableExample serializable = SerializableExample(

      // Works as espected:

      iMap: {DateTime(2023, 6, 25): 'IMap<DateTime,String>'}.lock,
      iEnumMap: {TestEnum.testValue: 'IMap<Enum,String>'}.lock,
      iStringMap: {'stringKey': 'IMap<String,String>'}.lock,
      iBigIntMap: {BigInt.from(8) : 'IMap<BigInt,String>'}.lock,

      iUriMap: {Uri(
    scheme: 'https',
    host: 'dart.dev',
    path: '/guides/libraries/library-tour',
    fragment: 'numbers') : 'Imap wit uri'}.lock,

      // Bug value as Type -> error

      // iDoubleMap :  {5.5: 'IMap<double,String>'}.lock,
      // iDurationMap: {Duration(milliseconds:200): 'IMap<Duration,String>'}.lock,
      // iIntMap: {2: 'IMap<Int,String>'}.lock,
      // iboolMap: {true : 'IMap<bool,String>'}.lock,
      );

  final Map<String, dynamic> json = serializable.toJson();

  final fromJsonTestMap = SerializableExample.fromJson(json);

  print('Output test serializableExample:\n ${fromJsonTestMap.toString()}');

}

SerializableExample


import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'example.g.dart';

enum TestEnum {
  testValue,
}

//  BigInt, bool, DateTime, double, Duration, Enum, int, Iterable, List, Map, num, Object, Record, Set, String, Uri
@JsonSerializable()
class SerializableExample {
  /// The generated code assumes these values exist in JSON.
  final IMap<DateTime, String> iMap;
  final IMap<TestEnum, String> iEnumMap;
  final IMap<String, String> iStringMap;
  final IMap<BigInt, String> iBigIntMap;
  final IMap<bool, String> iboolMap;
  final IMap<int, String> iIntMap;
  final IMap<Duration, String> iDurationMap;
  final IMap<double, String> iDoubleMap;
  final IMap<Object, String> iObjectMap;
final IMap<Uri, String> iUriMap;
  final Map<int, String> intMap;

  SerializableExample({
     this.iMap = const IMapConst({}),
     this.iEnumMap= const IMapConst({}),
     this.iStringMap = const IMapConst({}),
     this.iBigIntMap = const IMapConst({}),
     this.iboolMap = const IMapConst({}),
     this.iIntMap = const IMapConst({}),
    this.iDurationMap = const IMapConst({}),
     this.iDoubleMap = const IMapConst({}),
     this.iObjectMap = const IMapConst({}),
     this.iUriMap = const IMapConst({}),
     this.intMap = const{}
  });

  /// Connect the generated [_$PersonFromJson] function to the `fromJson`
  /// factory.
  factory SerializableExample.fromJson(Map<String, dynamic> json) =>
      _$SerializableExampleFromJson(json);

  /// Connect the generated [_$PersonToJson] function to the `toJson` method.
  Map<String, dynamic> toJson() => _$SerializableExampleToJson(this);

  @override
  String toString() {
    return 'SerializableExample(iMap: $iMap, iEnumMap: $iEnumMap, iStringMap: $iStringMap, iBigIntMap: $iBigIntMap, iboolMap: $iboolMap, iIntMap: $iIntMap, iDurationMap: $iDurationMap, iDoubleMap: $iDoubleMap, iObjectMap: $iObjectMap, iUriMap: $iUriMap, intMap: $intMap)';
  }
}

Generator code

part of 'example.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

SerializableExample _$SerializableExampleFromJson(Map<String, dynamic> json) =>
    SerializableExample(
      iMap: json['iMap'] == null
          ? const IMapConst({})
          : IMap<DateTime, String>.fromJson(
              json['iMap'] as Map<String, dynamic>,
              (value) => DateTime.parse(value as String),
              (value) => value as String),
      iEnumMap: json['iEnumMap'] == null
          ? const IMapConst({})
          : IMap<TestEnum, String>.fromJson(
              json['iEnumMap'] as Map<String, dynamic>,
              (value) => $enumDecode(_$TestEnumEnumMap, value),
              (value) => value as String),
      iStringMap: json['iStringMap'] == null
          ? const IMapConst({})
          : IMap<String, String>.fromJson(
              json['iStringMap'] as Map<String, dynamic>,
              (value) => value as String,
              (value) => value as String),
      iBigIntMap: json['iBigIntMap'] == null
          ? const IMapConst({})
          : IMap<BigInt, String>.fromJson(
              json['iBigIntMap'] as Map<String, dynamic>,
              (value) => BigInt.parse(value as String),
              (value) => value as String),
      iboolMap: json['iboolMap'] == null
          ? const IMapConst({})
          : IMap<bool, String>.fromJson(
              json['iboolMap'] as Map<String, dynamic>,
              (value) => value as bool,
              (value) => value as String),
      iIntMap: json['iIntMap'] == null
          ? const IMapConst({})
          : IMap<int, String>.fromJson(json['iIntMap'] as Map<String, dynamic>,
              (value) => value as int, (value) => value as String),
      iDurationMap: json['iDurationMap'] == null
          ? const IMapConst({})
          : IMap<Duration, String>.fromJson(
              json['iDurationMap'] as Map<String, dynamic>,
              (value) => Duration(microseconds: value as int),
              (value) => value as String),
      iDoubleMap: json['iDoubleMap'] == null
          ? const IMapConst({})
          : IMap<double, String>.fromJson(
              json['iDoubleMap'] as Map<String, dynamic>,
              (value) => (value as num).toDouble(),
              (value) => value as String),
      iObjectMap: json['iObjectMap'] == null
          ? const IMapConst({})
          : IMap<Object, String>.fromJson(
              json['iObjectMap'] as Map<String, dynamic>,
              (value) => value as Object,
              (value) => value as String),
      iUriMap: json['iUriMap'] == null
          ? const IMapConst({})
          : IMap<Uri, String>.fromJson(
              json['iUriMap'] as Map<String, dynamic>,
              (value) => Uri.parse(value as String),
              (value) => value as String),
      intMap: (json['intMap'] as Map<String, dynamic>?)?.map(
            (k, e) => MapEntry(int.parse(k), e as String),
          ) ??
          const {},
    );

Map<String, dynamic> _$SerializableExampleToJson(
        SerializableExample instance) =>
    <String, dynamic>{
      'iMap': instance.iMap.toJson(
        (value) => value.toIso8601String(),
        (value) => value,
      ),
      'iEnumMap': instance.iEnumMap.toJson(
        (value) => _$TestEnumEnumMap[value]!,
        (value) => value,
      ),
      'iStringMap': instance.iStringMap.toJson(
        (value) => value,
        (value) => value,
      ),
      'iBigIntMap': instance.iBigIntMap.toJson(
        (value) => value.toString(),
        (value) => value,
      ),
      'iboolMap': instance.iboolMap.toJson(
        (value) => value,
        (value) => value,
      ),
      'iIntMap': instance.iIntMap.toJson(
        (value) => value,
        (value) => value,
      ),
      'iDurationMap': instance.iDurationMap.toJson(
        (value) => value.inMicroseconds,
        (value) => value,
      ),
      'iDoubleMap': instance.iDoubleMap.toJson(
        (value) => value,
        (value) => value,
      ),
      'iObjectMap': instance.iObjectMap.toJson(
        (value) => value,
        (value) => value,
      ),
      'iUriMap': instance.iUriMap.toJson(
        (value) => value.toString(),
        (value) => value,
      ),
      'intMap': instance.intMap.map((k, e) => MapEntry(k.toString(), e)),
    };

const _$TestEnumEnumMap = {
  TestEnum.testValue: 'testValue',
};
TimWhiting commented 1 year ago

I believe this commit changed the behavior such that a portion of the workaround is no longer necessary: https://github.com/google/json_serializable.dart/commit/5422fd47d0aec9fc7e9f0adbaa0ac368b067cde2

It is a partial fix for https://github.com/google/json_serializable.dart/issues/396. My PR is out of date. I'd have to look into it some more to see exactly how much of that issue it addresses.