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.56k stars 400 forks source link

includeIf #1114

Open AlexanderFarkas opened 2 years ago

AlexanderFarkas commented 2 years ago

Is your feature request related to a problem? Please describe. PATCH REST requests, for example, are hard to do.

Photo's null is meaningful. If we send photo == null, then the photo should be deleted on the server and replaced by a placeholder.

Also, if we want to update only the name, photo should remain untouched.

@JsonSerializable(includeIfNull: false)
class PatchUser {
  final String name;
  final String? photo;

  PatchUser(this.photo, this.name);
}

That wouldn't work, 'cause null has meaning.

class Wrapper<T> {
  final T value;

  Wrapper(this.value);

  T toJson() => value;
}

@JsonSerializable(includeIfNull: false)
class PatchUser {
  final String name;
  final String? photo;

  PatchUser(this.photo, this.name);
}

That wouldn't also, because includeIfNull applies after toJson is called.

Describe the solution you'd like

@JsonSerializable()
class PatchUser {
  final String name;

  @JsonKey(includeIf: _skipPhotoIf)
  final Wrapper<String?>? photo;

  PatchUser(this.photo, this.name);
}

bool _includeIf(Wrapper<String?>? value) => value != null;

Also for the whole thing:

@JsonSerializable(includeIf: _includeIf)
class PatchUser {
  final Wrapper<String>? name;
  final Wrapper<String?>? photo;

  PatchUser(this.photo, this.name);
}

bool _includeIf(Object? value) => !(value is Wrapper && value == null);

Additional Context If this enhancement is welcome, I will be ready to make a PR.

lutes1 commented 1 year ago

Any news on that? Sounds like a no-brainer

kevmoo commented 1 year ago

Would LOVE To see the suggested generated output for the input examples.

AlexanderFarkas commented 1 year ago

Input:

class Optional<T extends Object?> {
  final T? value;
  Optional(this.value);

 T? toJson() => value;
}

@JsonSerializable(includeIf: includeIf)
class PatchUser {
  final Optional<String>? photoUrl;
  final String? name;

  PatchUser({required this.photoUrl, required this.name});
}

bool includeIf(Object? object) => object != null;

Output:

Map<String, dynamic> toJson() {
  return {
    if (includeIf(this.name)) "name": this.name,
    if (includeIf(this.photoUrl)) "photoUrl": this.photoUrl.toJson(),
  }
}

So, it's basically includeIfNull, but it's applied BEFORE any toJson is called.

More examples:

final user = PatchUser(
  photoUrl: null,
  name: "Username",
);
print(user.toJson()); // {"name": "Username"}
final user = PatchUser(
  photoUrl: Optional(null),
  name: "Username",
);
print(user.toJson()); // {"photo": null, "name": "Username"}
AlexanderFarkas commented 1 year ago

Actually, having includeIfNull alternative applied BEFORE serialization would solve the problem.

jointhejourney commented 10 months ago

Absolutely great suggestion! Stumbled upon this issue while looking up for a way to properly support PATCH requests that need a differentiation between an omitted field versus explicitly setting it to null.