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

Use toJson() instead of .name if enums have toJson(). #596

Merged
merged 2 commits into from Jul 5, 2023

Conversation

kzrnm
Copy link
Contributor

@kzrnm kzrnm commented Jul 3, 2023

Vesion 7.0.7 has breaking change.
I want to use toJson() if it exists.


enum ToJsonEnum {
  plus(1),
  minus(-1),
  ;

  const ToJsonEnum(this.value);

  final int value;

  int toJson() => value;
}

@RestApi()
abstract class TestQueryParamEnumToJson {
  @GET('/test')
  Future<void> getTest(@Query('test') ToJsonEnum? status);
}

7.0.6 and 7.0.8 generates final queryParameters = <String, dynamic>{r'test': status?.toJson()};.
7.0.7 generates final queryParameters = <String, dynamic>{r'test': status?.name};.

source_gen@1.4.0 requires Dart 3.0.
@trevorwang trevorwang merged commit a11998e into trevorwang:master Jul 5, 2023
2 checks passed
@trevorwang
Copy link
Owner

Thanks for the great work.

@knyghtryda
Copy link

@trevorwang @kzrnm This breaks things if you're using freezed to generate toJson() for enums. Even changing the build order doesn't fix it, it will always default to .name rather than .toJson()

@kzrnm
Copy link
Contributor Author

kzrnm commented Jul 14, 2023

@knyghtryda Could you take a example? My code is working as expected.

notice: freezed doesn't build Enum.toJson() or Enum.fromJson().

enum_json.dart

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

part 'enum_json.freezed.dart';
part 'enum_json.g.dart';

enum ByName {
  jp,
  zh,
  kr,
}

@JsonEnum(valueField: 'value')
enum ByValue {
  plus(1),
  minus(-1),
  ;

  const ByValue(this.value);

  final int value;

  int toJson() => value;
}

@freezed
class HasEnum with _$HasEnum {
  @JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
  const factory HasEnum({
    required ByName byName,
    required ByValue byValue,
  }) = _HasEnum;

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

@RestApi()
abstract class TestQueryParamEnumToJson {
  @GET('/test')
  Future<void> getTest({
    @Query('country') required ByName byName,
    @Query('sign') required ByValue byValue,
  });

  @POST('/test')
  Future<void> petTest(@Body() HasEnum params);
}
enum_json.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'enum_json.dart';

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

_$_HasEnum _$$_HasEnumFromJson(Map<String, dynamic> json) => _$_HasEnum(
      byName: $enumDecode(_$ByNameEnumMap, json['by_name']),
      byValue: $enumDecode(_$ByValueEnumMap, json['by_value']),
    );

Map<String, dynamic> _$$_HasEnumToJson(_$_HasEnum instance) =>
    <String, dynamic>{
      'by_name': _$ByNameEnumMap[instance.byName]!,
      'by_value': instance.byValue.toJson(),
    };

const _$ByNameEnumMap = {
  ByName.jp: 'jp',
  ByName.zh: 'zh',
  ByName.kr: 'kr',
};

const _$ByValueEnumMap = {
  ByValue.plus: 1,
  ByValue.minus: -1,
};

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers

class _TestQueryParamEnumToJson implements TestQueryParamEnumToJson {
  _TestQueryParamEnumToJson(
    this._dio, {
    this.baseUrl,
  });

  final Dio _dio;

  String? baseUrl;

  @override
  Future<void> getTest({
    required ByName byName,
    required ByValue byValue,
  }) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{
      r'country': byName.name,
      r'sign': byValue.toJson(),
    };
    final _headers = <String, dynamic>{};
    final Map<String, dynamic>? _data = null;
    await _dio.fetch<void>(_setStreamType<void>(Options(
      method: 'GET',
      headers: _headers,
      extra: _extra,
    )
        .compose(
          _dio.options,
          '/test',
          queryParameters: queryParameters,
          data: _data,
        )
        .copyWith(
            baseUrl: _combineBaseUrls(
          _dio.options.baseUrl,
          baseUrl,
        ))));
  }

  @override
  Future<void> petTest(HasEnum params) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _headers = <String, dynamic>{};
    final _data = <String, dynamic>{};
    _data.addAll(params.toJson());
    await _dio.fetch<void>(_setStreamType<void>(Options(
      method: 'POST',
      headers: _headers,
      extra: _extra,
    )
        .compose(
          _dio.options,
          '/test',
          queryParameters: queryParameters,
          data: _data,
        )
        .copyWith(
            baseUrl: _combineBaseUrls(
          _dio.options.baseUrl,
          baseUrl,
        ))));
  }

  RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    return requestOptions;
  }

  String _combineBaseUrls(
    String dioBaseUrl,
    String? baseUrl,
  ) {
    if (baseUrl == null || baseUrl.trim().isEmpty) {
      return dioBaseUrl;
    }

    final url = Uri.parse(baseUrl);

    if (url.isAbsolute) {
      return url.toString();
    }

    return Uri.parse(dioBaseUrl).resolveUri(url).toString();
  }
}
enum_json.freezed.dart
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark

part of 'enum_json.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');

HasEnum _$HasEnumFromJson(Map<String, dynamic> json) {
  return _HasEnum.fromJson(json);
}

/// @nodoc
mixin _$HasEnum {
  ByName get byName => throw _privateConstructorUsedError;
  ByValue get byValue => throw _privateConstructorUsedError;

  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
  @JsonKey(ignore: true)
  $HasEnumCopyWith<HasEnum> get copyWith => throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $HasEnumCopyWith<$Res> {
  factory $HasEnumCopyWith(HasEnum value, $Res Function(HasEnum) then) =
      _$HasEnumCopyWithImpl<$Res, HasEnum>;
  @useResult
  $Res call({ByName byName, ByValue byValue});
}

/// @nodoc
class _$HasEnumCopyWithImpl<$Res, $Val extends HasEnum>
    implements $HasEnumCopyWith<$Res> {
  _$HasEnumCopyWithImpl(this._value, this._then);

  // ignore: unused_field
  final $Val _value;
  // ignore: unused_field
  final $Res Function($Val) _then;

  @pragma('vm:prefer-inline')
  @override
  $Res call({
    Object? byName = null,
    Object? byValue = null,
  }) {
    return _then(_value.copyWith(
      byName: null == byName
          ? _value.byName
          : byName // ignore: cast_nullable_to_non_nullable
              as ByName,
      byValue: null == byValue
          ? _value.byValue
          : byValue // ignore: cast_nullable_to_non_nullable
              as ByValue,
    ) as $Val);
  }
}

/// @nodoc
abstract class _$$_HasEnumCopyWith<$Res> implements $HasEnumCopyWith<$Res> {
  factory _$$_HasEnumCopyWith(
          _$_HasEnum value, $Res Function(_$_HasEnum) then) =
      __$$_HasEnumCopyWithImpl<$Res>;
  @override
  @useResult
  $Res call({ByName byName, ByValue byValue});
}

/// @nodoc
class __$$_HasEnumCopyWithImpl<$Res>
    extends _$HasEnumCopyWithImpl<$Res, _$_HasEnum>
    implements _$$_HasEnumCopyWith<$Res> {
  __$$_HasEnumCopyWithImpl(_$_HasEnum _value, $Res Function(_$_HasEnum) _then)
      : super(_value, _then);

  @pragma('vm:prefer-inline')
  @override
  $Res call({
    Object? byName = null,
    Object? byValue = null,
  }) {
    return _then(_$_HasEnum(
      byName: null == byName
          ? _value.byName
          : byName // ignore: cast_nullable_to_non_nullable
              as ByName,
      byValue: null == byValue
          ? _value.byValue
          : byValue // ignore: cast_nullable_to_non_nullable
              as ByValue,
    ));
  }
}

/// @nodoc

@JsonSerializable(fieldRename: FieldRename.snake, explicitToJson: true)
class _$_HasEnum implements _HasEnum {
  const _$_HasEnum({required this.byName, required this.byValue});

  factory _$_HasEnum.fromJson(Map<String, dynamic> json) =>
      _$$_HasEnumFromJson(json);

  @override
  final ByName byName;
  @override
  final ByValue byValue;

  @override
  String toString() {
    return 'HasEnum(byName: $byName, byValue: $byValue)';
  }

  @override
  bool operator ==(dynamic other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is _$_HasEnum &&
            (identical(other.byName, byName) || other.byName == byName) &&
            (identical(other.byValue, byValue) || other.byValue == byValue));
  }

  @JsonKey(ignore: true)
  @override
  int get hashCode => Object.hash(runtimeType, byName, byValue);

  @JsonKey(ignore: true)
  @override
  @pragma('vm:prefer-inline')
  _$$_HasEnumCopyWith<_$_HasEnum> get copyWith =>
      __$$_HasEnumCopyWithImpl<_$_HasEnum>(this, _$identity);

  @override
  Map<String, dynamic> toJson() {
    return _$$_HasEnumToJson(
      this,
    );
  }
}

abstract class _HasEnum implements HasEnum {
  const factory _HasEnum(
      {required final ByName byName,
      required final ByValue byValue}) = _$_HasEnum;

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

  @override
  ByName get byName;
  @override
  ByValue get byValue;
  @override
  @JsonKey(ignore: true)
  _$$_HasEnumCopyWith<_$_HasEnum> get copyWith =>
      throw _privateConstructorUsedError;
}

@knyghtryda
Copy link

knyghtryda commented Jul 14, 2023

@kzrnm Yup, your code is definitely working as expected. I had some toJson()'s living in extensions somewhere that weren't being picked up. I moved all the toJson()'s into the enums and now its functional. I'm still not sure why the old versions worked and the new one didn't though.

@sagar-tide
Copy link

This doesn't work if I am using @JsonValue from json_serializable package. It still uses .name. @trevorwang

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants