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

include_if_null to exclude non-nullable fields whose toJson() can return null

Open andcea opened this issue 1 year ago • 7 comments

A use case is when using constructs such as Optional or Option. For example:

import 'package:json_annotation/json_annotation.dart';

part 'example.g.dart';

@JsonSerializable(includeIfNull: false)
class Person {
  final String firstName;

  @JsonKey(includeIfNull: false)
  final Option<String> maybeLastName;

  Person({required this.firstName, required this.maybeLastName});

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

  Map<String, dynamic> toJson() => _$PersonToJson(this);
}

Running

Person(firstName: "John", maybeLastName: None()).toJson()

will return

{"name": "John", "maybeLastName": null}

which is not what I expect. I expect "maybeLastName" to be excluded from the json given that it's null. Expected output:

{"name": "John"}

option.toJson() returns null if it's a None and T if it's a Some().

The specific Option implementation I'm using is from fpdart

andcea avatar Aug 07 '22 17:08 andcea

+1 This is a problem for me too.

sirkalmi avatar Sep 01 '22 11:09 sirkalmi

I am experiencing a similar issue.

In my case, I have an enum with a null json value (to avoid a nullable type within my model class)

@JsonEnum()
enum FoodType {
  @JsonValue(1)
  restaurant,
  @JsonValue(2)
  grocery,
  @JsonValue(null)
  unknown,
}

My Dart model class property:

@JsonKey(includeIfNull: false)
final FoodType brandedType;

This is the generated code for this property

'branded_type': _$FoodTypeEnumMap[instance.brandedType]!,
...
const _$FoodTypeEnumMap = {
  FoodType.restaurant: 1,
  FoodType.grocery: 2,
  FoodType.unknown: null,
};

What's happening in the generator is it's checking for a nullable type on the model class's property type. If the property is not a nullable type (as a Dart object), then it would seem redundant to generate the writeNotNull method because the value appears to not be nullable (as a serialized object).

This can be validated by making the property's type nullable

// --- model ---
@JsonKey(includeIfNull: false)
final FoodType? brandedType;

// --- generated code ---
void writeNotNull(String key, dynamic value) {
    if (value != null) {
      val[key] = value;
    }
  }

writeNotNull('branded_type', _$FoodTypeEnumMap[instance.brandedType]);
...
const _$FoodTypeEnumMap = {
  FoodType.restaurant: 1,
  FoodType.grocery: 2,
  FoodType.unknown: null,
};

This is the similar situation that @andcea is experiencing, but instead of _$EnumMap[...] (where the key's value could return a null value), it would be property.toJson() (where toJson could return a null value). Since we are checking for the null value against the serialized property, and not the Dart model object's current value, the property's type isn't relevant.

The fix for this would be to ignore the nullable typing on the property of the Dart object, and generate the writeNotNull regardless.

mrgnhnt96 avatar Sep 19 '22 16:09 mrgnhnt96

@andcea – I use Dart's notion of nullability. I cannot support an arbitrary Option type like you have. The solution would be to use

  @JsonKey(includeIfNull: false)
  final String? maybeLastName;

@mrgnhnt96 – please file a separate issue for your problem!

kevmoo avatar Sep 20 '22 02:09 kevmoo

@kevmoo this issue wasn't about supporting Option, that was just an example.

The request is for @JsonKey(includeIfNull: false) to support use cases where the type is non-nullable but toJson() can return null.
Right now if toJson() returns null on a field that has includeIfNull: false, it will still be included in the output.

Please re-open this issue as it wasn't completed and I think it's a valid feature request.

andcea avatar Sep 20 '22 08:09 andcea

Understood. It's a bit of a weird case, honestly. Will need to ponder.

kevmoo avatar Sep 20 '22 21:09 kevmoo

Any news on that topic? I also have this use case. I have a list of objects, some are auto generated so I don't want to serialize them.

They have a certain value, so it would be great If I just could return null within the toJson Method to exclude them.

DerDave avatar Jan 09 '24 16:01 DerDave

If I can offer my two cents, I'm running into this same issue and have been trying to debug the situation:

I've made my own class that works like an enum with an internal value, and I'm trying to serialize the value, or exclude it if the property in the structure is null. This is going through a custom toJson method that extracts the string equivalent of the internal value of the "enum":

image

Through a few levels of inheritence, it comes down to this line here:

image

Which, oddly enough is resulting in the word "null", which is different than null, which fails the writeNotNull check in the auto-generated g file.

image

Solution? Instead of accidentally turn null into the string "null", I missed a null-check:

image

Just passing along my experience, in case it helps anyone else.

nwithan8 avatar Feb 13 '24 00:02 nwithan8