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

Nested Freezed class not converted TO JSON #86

Closed
ResoDev opened this issue Mar 3, 2020 · 21 comments
Closed

Nested Freezed class not converted TO JSON #86

ResoDev opened this issue Mar 3, 2020 · 21 comments

Comments

@ResoDev
Copy link

ResoDev commented Mar 3, 2020

👋
Given two freezed classes where one is "nested" in the other.

import 'package:freezed_annotation/freezed_annotation.dart';

part 'contrived_example.g.dart';
part 'contrived_example.freezed.dart';

@freezed
abstract class TopLevel with _$TopLevel {
  const factory TopLevel(
    String niceString,
    Nested doesNotSerializeButDoesDeserialize,
  ) = _TopLevel;
  factory TopLevel.fromJson(Map<String, dynamic> json) =>
      _$TopLevelFromJson(json);
}

@freezed
abstract class Nested with _$Nested {
  const factory Nested(int niceInteger) = _Nested;
  factory Nested.fromJson(Map<String, dynamic> json) => _$NestedFromJson(json);
}

The generated code for json_serializable doesn't serialize the nested object. DEserialization works as expected.

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'contrived_example.dart';

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

_$_TopLevel _$_$_TopLevelFromJson(Map<String, dynamic> json) {
  return _$_TopLevel(
    json['niceString'] as String,
    json['doesNotSerializeButDoesDeserialize'] == null
        ? null
        : Nested.fromJson(
            json['doesNotSerializeButDoesDeserialize'] as Map<String, dynamic>),
  );
}

Map<String, dynamic> _$_$_TopLevelToJson(_$_TopLevel instance) =>
    <String, dynamic>{
      'niceString': instance.niceString,
      // 👇 should have toJson() or map((element) => element.toJson()) for Lists
      'doesNotSerializeButDoesDeserialize':
          instance.doesNotSerializeButDoesDeserialize,
    };

_$_Nested _$_$_NestedFromJson(Map<String, dynamic> json) {
  return _$_Nested(
    json['niceInteger'] as int,
  );
}

Map<String, dynamic> _$_$_NestedToJson(_$_Nested instance) => <String, dynamic>{
      'niceInteger': instance.niceInteger,
    };

At first, I thought that it's an issue with json_serializable but if that library is used by itself, it serializes nested objects marked with @JsonSerializable just fine.

@rrousselGit
Copy link
Owner

I have a very deeply nested object and it works fine for me.

The generated code you gave is generated by json_serializable, so I have no control over it.
Is freezed correctly adding @JsonSerializable annotation on Nested?
If it does, then it's unlikely to be a bug with freezed

@ResoDev
Copy link
Author

ResoDev commented Mar 3, 2020

The _$_Nested is annotated.

@JsonSerializable()
class _$_Nested implements _Nested {...}

@rrousselGit
Copy link
Owner

Then it may be an issue with build_runner.

Try regenerating the sources (potentially deleting .dart_tool) and see if it works.

In any case, freezed does nothing besides adding this annotation. So if it's there, then I can't do anything 🙃

@ResoDev
Copy link
Author

ResoDev commented Mar 3, 2020

Whoops! Sorry to break it to you, Rémi, but the bug is probably on your side 😅

I replicated the non-serializing behavior with regular classes too. All it took for the nested class to serialize was @JsonSerializable(explicitToJson: true) on the top-level class.

@rrousselGit
Copy link
Owner

I mean it depends on what encoder you use.

The documentation of explicitToJson explicitly says that it's not needed using dart:convert.

If you need it, it's your job to add it or your encoder's job

@ResoDev
Copy link
Author

ResoDev commented Mar 3, 2020

OK, pardon me 🤦‍♂️ I'll leave the build.yaml set up here for any time travelers from the future.

targets:
  $default:
    builders:
      json_serializable:
        options:
          explicit_to_json: true

Thank you!

@ResoDev ResoDev closed this as completed Mar 3, 2020
@david-legend
Copy link

thanks, resoDev and Remi, I am from the future and this conversation helped me

@theweiweiway
Copy link

theweiweiway commented Jul 23, 2020

Works great!

But I was wondering if we can set explicitToJson to true for specific fields, and false for other fields.

I tried annotating each field but that didn't seem to work

Here's my use case:

@freezed
abstract class SearchFilters with _$SearchFilters {
  const factory SearchFilters({
     List<CategoryEnum> category,
     CustomClass customClass,
  }) = _SearchFilters;

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

I want customClass to be explicit to json, but the category to not be explicit to Json. Here's my CategoryEnum class:

@freezed
abstract class CategoryEnum implements _$CategoryEnum {
  const CategoryEnum._();
  const factory CategoryEnum(String label) = _CategoryEnum;
  static const category1 = CategoryEnum("Category 1");
  static const category2 = CategoryEnum("Category 2");

  factory CategoryEnum.fromJson(String val) {
    return CategoryEnum(val);
  }

  @override
  String toString() => this.label;
}

@rrousselGit
Copy link
Owner

I don't think you can, no

@theweiweiway
Copy link

theweiweiway commented Jul 23, 2020

Thanks for the quick response. I was able to fix this by setting explicitToJson to true, and using @JsonKey(toJson: CategoryEnum.serializeToList) to define a custom toJson function for my enum class

@freezed
abstract class CategoryEnum implements _$CategoryEnum {
  const CategoryEnum._();
  const factory CategoryEnum(String label) = _CategoryEnum;
  static const category1 = CategoryEnum("1");
  static const category2 = CategoryEnum("2");

  factory CategoryEnum.fromJson(String val) {
    return CategoryEnum(val);
  }

  static List<String> serializeToList(List<CategoryEnum> e) {
    return e.map((a) => a.toString()).toList();
  }

  @override
  String toString() => this.label;
}

vanosidor added a commit to vanosidor/notes-ddd that referenced this issue May 22, 2021
@mathiasgodwin
Copy link

mathiasgodwin commented Jun 10, 2021

Hey guys, I think QuickType is worth giving consideration. It thus use Freezed and work with many languages, It converts JSON like magic.

@neiljaywarner
Copy link

@mathiasgodwin i love the idea of quicktype a LOT but it doesn't add the explicityTOJson and it doesn't handle underscores

@neiljaywarner
Copy link

a-ha, the build also handles snake field renaming. good call then @mathiasgodwin

@pishguy
Copy link

pishguy commented Aug 29, 2021

Hi, where is build.yaml to change explicit_to_json value?

@golane-august
Copy link

@pishguy You have to create it on root folder (where pubspec.yaml is), if it's not present yet.

@stact
Copy link

stact commented Nov 2, 2021

Without build.yaml we can specify like this:

@freezed
class TopLevel with _$TopLevel {
  
  TopLevel._();
  // Here 👇 the magic 
  JsonSerializable(explicitToJson: true)
  const factory TopLevel(
    String niceString,
    Nested doesNotSerializeButDoesDeserialize,
  ) = _TopLevel;
  factory TopLevel.fromJson(Map<String, dynamic> json) =>
      _$TopLevelFromJson(json);
}

@freezed
class Nested with _$Nested {
  Nested._();

  const factory Nested(int niceInteger) = _Nested;
  factory Nested.fromJson(Map<String, dynamic> json) => _$NestedFromJson(json);
}

@mrRedSun
Copy link

mrRedSun commented Jul 6, 2022

It's kinda weird it freezed doesn't handle that by default, could anyone provide an example of code that works with nested json with explicit_to_json hack?

@rrousselGit
Copy link
Owner

Well I agree. But the problem isn't Freezed, it's json_serializable.

Anyway, yo enable explicit_to_json, create a build.yaml file next to your pubspec.yaml which contains:

targets:
  $default:
    builders:
      json_serializable:
        options:
          explicit_to_json: true

@mrRedSun
Copy link

mrRedSun commented Jul 6, 2022

It works, just doesn't feel right 😆
Gonna look into json_serializable if they have the ticket already

@rrousselGit
Copy link
Owner

I've requested for json_serializable to enable explicit_to_json by default before. They don't want to
Their reasoning is for lazy JSON de/serialization.

I've alternatively raised google/json_serializable.dart#1126 as a middle ground (preserving explicit_to_json:false by default, but also fixing the issue of invalid JSON output for nested classes)
I haven't received an answer yet. We'll see

@ormoyal
Copy link

ormoyal commented Nov 17, 2022

after trying most of the solutions here and non of them worked for me, I found my own solution.
since I'm using Drift as a local database and I'm implementing their class I needed to add the @JsonSerializable(explicitToJson: true) between the 2 constructors and on top of the Factory one.
like this:

@freezed
class ChargingProfile with _$ChargingProfile implements Insertable<ChargingProfile> {
  const ChargingProfile._();
  @Implements<Insertable<ChargingProfile>>()
  // Here 👇🏼 is the important line 
  @JsonSerializable(explicitToJson: true)
  const factory ChargingProfile({
    final int? profileId,
    final String? name,
    final int? stackLevel,
    final int? operatorId,
    final DateTime? validFromDate,
    final DateTime? validToDate,
    @Default(ProfilePurposeEnum.TxDefaultProfile) final ProfilePurposeEnum profilePurpose,
    final ProfileKindEnum? profileKind,
    final RecurrencyKindEnum? recurrencyKind,
    final ChargingSchedule? chargingSchedule,
  }) = _Profile;

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

  @override
  Map<String, Expression> toColumns(bool nullToAbsent) {
    return ChargingProfilesCompanion(
      profileId: Value(profileId),
      name: Value(name),
      stackLevel: Value(stackLevel),
      operatorId: Value(operatorId),
      validFromDate: Value(validFromDate),
      validToDate: Value(validToDate),
      profilePurpose: Value(profilePurpose),
      profileKind: Value(profileKind),
      recurrencyKind: Value(recurrencyKind),
      chargingSchedule: Value(chargingSchedule),
    ).toColumns(nullToAbsent);
  }
}

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

No branches or pull requests