Let's suppose I need to call an HTTP API PATCH endpoint whose schema for the body is:
{
...
"type": "object",
"properties": {
"propertyOne": { "type": ["string", "null"] },
"propertyTwo": { "type": "string" }
}
}
According to the schema above, all of the following bodies are valid:
{ "propertyOne": null }
{ "propertyTwo": "test" }
{ "propertyOne": "test", "propertyTwo": "test" }
How do I model such a data object in Dart so that when I call .toJson
on that object, it outputs a Map with propertyOne
set to null
if the property was explicitly set to null
, with propertyOne
set to a string if the property was initialized with a string or excludes propertyOne
if no value was specified when initializing the class?
I'm currently using freezed and have a working model for the request if the schema of the API wouldn't include null
as an allowed value. How I model that is:
@freezed
class PatchRequestBody with _$PatchRequestBody {
const factory PatchRequestBody({
@JsonKey(includeIfNull: false) String? propertyOne,
@JsonKey(includeIfNull: false) String? propertyTwo
}) = _PatchRequestBody;
factory PatchRequestBody.fromJson(Map<String, dynamic> json) =>
_$PatchRequestBodyFromJson(json);
}
The issue comes when I want different behaviours for when a value is specifically set to null versus when a value is not specified.
For the case above, if I run PatchRequestBody(propertyOne: "test").toJson()
I get { "propertyOne": "test" }
which is great. If I run PatchRequestBody(propertyOne: null).toJson()
it outputs an empty object, which is not great but can be fixed by removing the @JsonKey(includeIfNull: false)
annotation. This unfortunately makes the case where I run PatchRequestBody(propertyTwo: "test").toJson()
output { "propertyOne": null, "propertyTwo": "test" }
which is not what I desire.
I tried looking for some solutions and found this Optional class from a package called quiver but it seems like they advise against not using it anymore.
Otherwise I couldn't really find a way to solve or model this.
If you only have a few classes to encode, you could do them by hand, for example:
import 'dart:convert';
void main() {
print(json.encode(PatchRequestBody()));
print(json.encode(PatchRequestBody(propertyTwo: 'test')));
print(json.encode(PatchRequestBody(
propertyOne: 'test',
propertyTwo: 'test',
)));
}
class PatchRequestBody {
const PatchRequestBody({
this.propertyOne,
this.propertyTwo,
});
Map<String, dynamic> toJson() {
if (propertyOne == null && propertyTwo == null) {
// special case if both null - emit first property as null
return <String, dynamic>{'propertyOne': null};
}
final m = <String, dynamic>{};
_addIfNotNull(m, 'propertyOne', propertyOne);
_addIfNotNull(m, 'propertyTwo', propertyTwo);
return m;
}
void _addIfNotNull(Map<String, dynamic> m, String name, String? val) {
if (val != null) {
m[name] = val;
}
}
final String? propertyOne;
final String? propertyTwo;
}