json_serializable.dart icon indicating copy to clipboard operation
json_serializable.dart copied to clipboard

includeIf

Open AlexanderFarkas opened this issue 2 years ago • 5 comments

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.

AlexanderFarkas avatar Mar 12 '22 15:03 AlexanderFarkas

Any news on that? Sounds like a no-brainer

lutes1 avatar Sep 11 '23 09:09 lutes1

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

kevmoo avatar Sep 12 '23 00:09 kevmoo

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 avatar Sep 12 '23 16:09 AlexanderFarkas

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

AlexanderFarkas avatar Sep 12 '23 16:09 AlexanderFarkas

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.

jointhejourney avatar Dec 26 '23 21:12 jointhejourney