Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON generation not working for use of generic class #766

Open
norpan opened this issue Sep 21, 2022 · 10 comments
Open

JSON generation not working for use of generic class #766

norpan opened this issue Sep 21, 2022 · 10 comments
Assignees
Labels
bug Something isn't working

Comments

@norpan
Copy link

norpan commented Sep 21, 2022

Describe the bug
JSON generation fails when using the generic class in another class. I've followed the instructions at https://pub.dev/packages/freezed#deserializing-generic-classes I think.

I get the following output:

[SEVERE] json_serializable on lib/src/bug.dart:

RangeError (index): Invalid value: Only valid value is 0: -1

To Reproduce
pubspec.yaml

environment:
  sdk: '>=2.18.1 <3.0.0'

dev_dependencies:
  build_runner: ^2.2.1
  freezed: ^2.1.1
  json_serializable: ^6.4.0
dependencies:
  freezed_annotation: ^2.1.0
  json_annotation: ^4.7.0

bug.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'bug.freezed.dart';
part 'bug.g.dart';

@freezed
class Parent with _$Parent {
  const factory Parent({
    required ApiResponse<String> child,
  }) = _Parent;

  factory Parent.fromJson(Map<String, dynamic> json) => _$ParentFromJson(json);
}

@Freezed(genericArgumentFactories: true)
class ApiResponse<T> with _$ApiResponse<T> {
  const factory ApiResponse.data(T data) = ApiResponseData;

  factory ApiResponse.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$ApiResponseFromJson(json, fromJsonT);
}
@norpan norpan added bug Something isn't working needs triage labels Sep 21, 2022
@norpan
Copy link
Author

norpan commented Sep 22, 2022

It seems to be an issue with json_serializable when generating the toJson. It tries to find the matching type but compares the type T from _$ApiResonse with the type T from ApiResponse and finds them not equal.

It seems to be because of the use of mixin, because if I move the generated mixin into the ApiResponse class, everything works.

There is no error if you @override toJson from the mixin. I'm not sure what it means to override a mixin, but at least it works.

@Freezed(genericArgumentFactories: true)
class ApiResponse<T> with _$ApiResponse<T> {
  const factory ApiResponse.data(T data) = ApiResponseData;

  factory ApiResponse.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$ApiResponseFromJson(json, fromJsonT);

  @override
  Map<String, dynamic> toJson(Object? Function(T) toJsonT) =>
      throw _privateConstructorUsedError;
}

@BenjiFarquhar
Copy link

BenjiFarquhar commented Sep 25, 2022

Related

I have confirmed that when running the build runner, I get RangeError (index): Invalid value: Only valid value is 0: -1 in any class that contains a field that is the type of my freezed generic class that has a fromJson, unless I manually add toJson (still doesn't work, probably due to my implementation of it, just gets rid of the error)

@tovidd
Copy link

tovidd commented Sep 19, 2023

RangeError (index): Invalid value: Only valid value is 0: -1 appears when directly use generic type as a variable type. It safe if you use generic type that wrapped inside List or something else.

Not caused error

@Freezed(genericArgumentFactories: true)
class FilterNormalSectionModel<T> with _$FilterNormalSectionModel<T> {
  const factory FilterNormalSectionModel({
    required int id,
    required String title,
    required List<T> filters,
    List<T>? extendFilters,
  }) = _FilterNormalSectionModel;

  factory FilterNormalSectionModel.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$FilterNormalSectionModelFromJson(json, fromJsonT);
}

Caused error

@Freezed(genericArgumentFactories: true)
class FilterPriceSectionModel<T> with _$FilterPriceSectionModel<T> {
  const factory FilterPriceSectionModel({
    required int id,
    required String title,
    required T lowerPrice,
    required T upperPrice,
  }) = _FilterPriceSectionModel;

  factory FilterPriceSectionModel.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$FilterPriceSectionModelFromJson(json, fromJsonT);

  @override
  Map<String, dynamic> toJson(Object? Function(T) toJsonT) =>
      super.toJson(toJsonT);
}

@ztaylor54
Copy link

ztaylor54 commented Nov 14, 2023

Any updates here? I am running into this issue as well. My use case is slightly difference from the above, but still deals with nested generics.

inner.dart (codegen works fine):

import 'package:freezed_annotation/freezed_annotation.dart';

part 'inner.freezed.dart';
part 'inner.g.dart';

@Freezed(genericArgumentFactories: true)
class Inner<T> with _$Inner<T> {
  const factory Inner({
    required T data,
  }) = _Inner<T>;

  factory Inner.fromJson(
          Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
      _$InnerFromJson(json, fromJsonT);
}

outer.dart:

import 'path/to/inner.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'outer.freezed.dart';
part 'outer.g.dart';

@Freezed(genericArgumentFactories: true)
class Outer<T, U> with _$Outer<T, U> {
  const factory Outer({
    required T firstType,
    required Inner<U> secondType,
  }) = _Outer<T, U>;

  factory Outer.fromJson(
    Map<String, Object?> json,
    T Function(Object?) fromJsonT,
    U Function(Object?) fromJsonU,
  ) =>
      _$OuterFromJson(json, fromJsonT, fromJsonU);
}

During codegen the Inner class has no issues, but the Outer class fails with a range error:

flutter pub run build_runner build --delete-conflicting-outputs
Deprecated. Use `dart run` instead.
[INFO] Generating build script completed, took 167ms
[INFO] Reading cached asset graph completed, took 70ms
[INFO] Checking for updates since last build completed, took 848ms
[WARNING] json_serializable on path/to/outer.dart:
The version constraint "^4.7.0" on json_annotation allows versions before 4.8.1 which is not allowed.
[SEVERE] json_serializable on path/to/outer.dart:

RangeError (index): Invalid value: Only valid value is 0: -1
[INFO] Running build completed, took 1.1s
[INFO] Caching finalized dependency graph completed, took 53ms
[SEVERE] Failed after 1.1s
make: *** [freezed] Error 1

Note that if I change the type of secondType to either U or List<U>, the code generates just fine with no issues.

I've seen this mentioned by @rrousselGit here: google/json_serializable.dart#870 (comment), but I am unclear on the solution (if any) to the issue. Thanks in advance!

@ztaylor54
Copy link

I created a test repo to make this easy to reproduce, and have also figured out a temporary workaround. There are more details in the readme, but here's the TL;DR:

To make codegen work for a nested freezed class with generics, simply comment out the import for the nested class.

Using my example above, comment out the line:

import 'path/to/inner.dart';

Run codegen, then uncomment the line. That should fix syntax errors, but freezed won't have generated the proper code for the secondType field. So we must go into outer.freezed.dart and add the correct type signatures, as well as outer.g.dart and fix the to/fromJson functions to deal with the nested Inner<U> type.

You can see the differences in my example repository:

The resulting code works as expected, but obviously this isn't a great workaround because it gets overwritten by subsequent code generation.

Hopefully this helps pinpoint the issue!

@xzhorikx
Copy link

@ztaylor54 you legend, thanks for putting everything in one place!

Confirming having the same issue running

environment:
  sdk: '>=3.1.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1
  
dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.4.6
  freezed: ^2.4.5
  json_serializable: ^6.7.1

Having the same issue with putting generic ListingResponse within other freezed classes. The structure is below:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'listing_response.freezed.dart';
part 'listing_response.g.dart';

@Freezed(genericArgumentFactories: true)
class ListingResponse<T> with _$ListingResponse<T> {
  const factory ListingResponse(final T data) = _ListingResponse<T>;

  factory ListingResponse.fromJson(
    Map<String, dynamic> json,
    T Function(Object?) fromJsonT,
  ) =>
      _$ListingResponseFromJson(json, fromJsonT);
}

Wrapping class

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:myapp/listing_response.dart';

part 'comment_response.freezed.dart';
part 'comment_response.g.dart';

@freezed
class CommentResponse with _$CommentResponse {
  const factory CommentResponse({
    @JsonKey(name: "data") required ListingResponse<String>? data,
  }) = _CommentResponse;

  factory CommentResponse.fromJson(Map<String, dynamic> json) =>
      _$CommentResponseFromJson(json);
}

Removing the import 'package:myapp/listing_response.dart'; before the generation works fine. However, I'm always running the build_runner in the background, and going back and forth with commenting-out imports doesn't work to me.

dart run build_runner watch --use-polling-watcher --delete-conflicting-outputs

Please fix this issue asap.

@dwkim891220
Copy link

I had the same problem, but I solved it after reading this comment #766 (comment)

solved issue after remove import line of child class
I hope the problem is resolved as soon as this issue
thanks @ztaylor54

@asaworld
Copy link

asaworld commented Jul 3, 2024

I am having this issue with a none generic version of Outer from above. It is a pretty common use case - any custom generic contained within a freezed class. Outer in my case looks like this:

`
import 'path/to/inner.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'outer.freezed.dart';
part 'outer.g.dart';

@freezed
class Outer with _$Outer {
const factory Outer({
required Inner secondType,
}) = _Outer;

factory Outer.fromJson(Map<String, Object?> json) => _$OuterFromJson(json);
}
`

Could someone please look at solving this one.

@dleurs
Copy link

dleurs commented Oct 24, 2024

Still no update on this problem ?
I am also having issue

RangeError (index): Invalid value: Only valid value is 0: -1

@TekExplorer
Copy link

TekExplorer commented Oct 25, 2024

The issue can be worked around by adding

class Foo<T> with _$Foo<T> {
  ...
  toJson(toJsonT, /*other generics*/);
}

To the generic class itself.

This is a bug with how Json serializable tries and fails to notice the toJson in the mixin.

This adds the function to the interface directly, so that the generator cannot miss it.

No, you do not need a body. The mixin supplies that. We're just declaring that it exists.
you may or may not need to specify the types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

10 participants