Skip to content

Commit 6ebfb41

Browse files
authored
Merge pull request #10 from twcrews/dev
5.2.3
2 parents f7294d5 + 72f1d4b commit 6ebfb41

12 files changed

Lines changed: 81 additions & 1 deletion

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [5.2.3] - 2026-02-25
9+
10+
### Fixed
11+
12+
Add conditional `[JsonIgnore]` attributes to all nullable properties, preventing them from being serialized when their values are `null`.
13+
14+
### Remarks
15+
16+
This change addresses an oversight in previous versions of the library in which `null` properties were being explicitly serialized. Consumers can still override this with `JsonSerializerOptions.TypeInfoResolver`.
17+
818
## [5.2.2] - 2026-02-25
919

1020
### Fixed
@@ -145,6 +155,7 @@ Additionally, this version aims to be more idiomatic by renaming class propertie
145155

146156
Initial release.
147157

158+
[5.2.3]: https://github.com/twcrews/jsonapi-client/compare/5.2.2...5.2.3
148159
[5.2.2]: https://github.com/twcrews/jsonapi-client/compare/5.2.1...5.2.2
149160
[5.2.1]: https://github.com/twcrews/jsonapi-client/compare/5.2.0...5.2.1
150161
[5.2.0]: https://github.com/twcrews/jsonapi-client/compare/5.1.0...5.2.0

Crews.Web.JsonApiClient.Tests/JsonApiDocumentTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,25 @@ public void SerializesDocumentWithAllProperties()
324324
Assert.Equal("2024", result.RootElement.GetProperty("meta").GetProperty("copyright").GetString());
325325
}
326326

327+
[Fact(DisplayName = "Serialization excludes null properties")]
328+
public void SerializationExcludesNullProperties()
329+
{
330+
TestJsonApiDocument doc = new()
331+
{
332+
Data = JsonSerializer.SerializeToElement(new JsonApiResource { Type = "articles", Id = "1" })
333+
};
334+
335+
string json = JsonSerializer.Serialize(doc, _options);
336+
JsonDocument result = JsonDocument.Parse(json);
337+
338+
Assert.True(result.RootElement.TryGetProperty("data", out _));
339+
Assert.False(result.RootElement.TryGetProperty("jsonapi", out _));
340+
Assert.False(result.RootElement.TryGetProperty("errors", out _));
341+
Assert.False(result.RootElement.TryGetProperty("links", out _));
342+
Assert.False(result.RootElement.TryGetProperty("included", out _));
343+
Assert.False(result.RootElement.TryGetProperty("meta", out _));
344+
}
345+
327346
#endregion
328347

329348
#region Roundtrip Tests

Crews.Web.JsonApiClient/Crews.Web.JsonApiClient.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<PropertyGroup>
1111
<PackageId>Crews.Web.JsonApiClient</PackageId>
12-
<PackageVersion>5.2.2</PackageVersion>
12+
<PackageVersion>5.2.3</PackageVersion>
1313
<Authors>Tommy Crews</Authors>
1414
<Description>
1515
A library containing serialization models and methods for the JSON:API specification.

Crews.Web.JsonApiClient/JsonApiDocument.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,48 @@ public record JsonApiDocument
1212
/// <summary>
1313
/// Gets or sets the <c>jsonapi</c> property of the document.
1414
/// </summary>
15+
/// <seealso href="https://jsonapi.org/format/#document-jsonapi-object"/>
16+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1517
[JsonPropertyName("jsonapi")]
1618
public JsonApiInfo? JsonApi { get; init; }
1719

1820
/// <summary>
1921
/// Gets or sets the primary data payload associated with the document.
2022
/// </summary>
23+
/// <seealso href="https://jsonapi.org/format/#document-jsonapi-object"/>
24+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2125
[JsonPropertyName("data")]
2226
public JsonElement? Data { get; init; }
2327

2428
/// <summary>
2529
/// Gets or sets the collection of errors associated with the document.
2630
/// </summary>
31+
/// <seealso href="https://jsonapi.org/format/#error-objects"/>
32+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2733
[JsonPropertyName("errors")]
2834
public IEnumerable<JsonApiError>? Errors { get; init; }
2935

3036
/// <summary>
3137
/// Gets or sets the <c>links</c> property of the document.
3238
/// </summary>
3339
/// <seealso href="https://jsonapi.org/format/#document-links"/>
40+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3441
[JsonPropertyName("links")]
3542
public Dictionary<string, JsonApiLink>? Links { get; init; }
3643

3744
/// <summary>
3845
/// Gets or sets the <c>included</c> property of the document.
3946
/// </summary>
47+
/// <seealso href="https://jsonapi.org/format/#document-compound-documents"/>
48+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4049
[JsonPropertyName("included")]
4150
public IEnumerable<JsonApiResource>? Included { get; init; }
4251

4352
/// <summary>
4453
/// Gets or sets the <c>meta</c> property of the document.
4554
/// </summary>
4655
/// <seealso href="https://jsonapi.org/format/#document-meta"/>
56+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4757
[JsonPropertyName("meta")]
4858
public JsonObject? Meta { get; init; }
4959

@@ -94,6 +104,8 @@ public record JsonApiDocument<T> : JsonApiDocument where T : JsonApiResource
94104
/// <summary>
95105
/// Gets or sets the primary data payload associated with the document.
96106
/// </summary>
107+
/// <seealso href="https://jsonapi.org/format/#document-jsonapi-object"/>
108+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
97109
[JsonPropertyName("data")]
98110
public new T? Data { get; init; }
99111

@@ -128,6 +140,8 @@ public record JsonApiCollectionDocument<T> : JsonApiDocument where T : JsonApiRe
128140
/// <summary>
129141
/// Gets or sets the primary data payload associated with the document.
130142
/// </summary>
143+
/// <seealso href="https://jsonapi.org/format/#document-jsonapi-object"/>
144+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
131145
[JsonPropertyName("data")]
132146
public new IEnumerable<T>? Data { get; init; }
133147

Crews.Web.JsonApiClient/JsonApiError.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,56 @@ public record JsonApiError
1111
/// <summary>
1212
/// Gets or sets the unique identifier for this particular occurrence of the problem.
1313
/// </summary>
14+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1415
[JsonPropertyName("id")]
1516
public string? Id { get; init; }
1617

1718
/// <summary>
1819
/// Gets or sets the links that provide additional information about the error.
1920
/// </summary>
21+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2022
[JsonPropertyName("links")]
2123
public JsonApiErrorLinksObject? Links { get; init; }
2224

2325
/// <summary>
2426
/// Gets or sets the HTTP status code associated with the error.
2527
/// </summary>
28+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2629
[JsonPropertyName("status")]
2730
public string? Status { get; init; }
2831

2932
/// <summary>
3033
/// Gets or sets the application-specific error code associated with the error.
3134
/// </summary>
35+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3236
[JsonPropertyName("code")]
3337
public string? Code { get; init; }
3438

3539
/// <summary>
3640
/// Gets or sets the title of the error.
3741
/// </summary>
42+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3843
[JsonPropertyName("title")]
3944
public string? Title { get; init; }
4045

4146
/// <summary>
4247
/// Gets or sets the detailed description of the error.
4348
/// </summary>
49+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4450
[JsonPropertyName("detail")]
4551
public string? Detail { get; init; }
4652

4753
/// <summary>
4854
/// Gets or sets the source of the error.
4955
/// </summary>
56+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5057
[JsonPropertyName("source")]
5158
public JsonApiErrorSource? Source { get; init; }
5259

5360
/// <summary>
5461
/// Gets or sets the additional metadata associated with the object.
5562
/// </summary>
63+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5664
[JsonPropertyName("meta")]
5765
public JsonObject? Meta { get; init; }
5866
}

Crews.Web.JsonApiClient/JsonApiErrorLinksObject.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ public record JsonApiErrorLinksObject
1212
/// <summary>
1313
/// Gets or sets a link that provides additional information about the error.
1414
/// </summary>
15+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1516
[JsonPropertyName("about")]
1617
[JsonConverter(typeof(JsonApiLinkConverter))]
1718
public JsonApiLink? About { get; init; }
1819

1920
/// <summary>
2021
/// Gets or sets the link that specifies the type of the error.
2122
/// </summary>
23+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2224
[JsonPropertyName("type")]
2325
[JsonConverter(typeof(JsonApiLinkConverter))]
2426
public JsonApiLink? Type { get; init; }

Crews.Web.JsonApiClient/JsonApiErrorSource.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ public record JsonApiErrorSource
1111
/// <summary>
1212
/// Gets or sets the JSON Pointer that identifies the location within a JSON document that caused the error.
1313
/// </summary>
14+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1415
[JsonPropertyName("pointer")]
1516
public string? Pointer { get; init; }
1617

1718
/// <summary>
1819
/// Gets or sets the URI query parameter value that caused the error.
1920
/// </summary>
21+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2022
[JsonPropertyName("parameter")]
2123
public string? Parameter { get; init; }
2224

2325
/// <summary>
2426
/// Gets or sets the name of a request header that caused the error.
2527
/// </summary>
28+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2629
[JsonPropertyName("header")]
2730
public string? Header { get; init; }
2831
}

Crews.Web.JsonApiClient/JsonApiInfo.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,28 @@ public record JsonApiInfo
1111
/// <summary>
1212
/// Gets or sets the JSON:API version for the document.
1313
/// </summary>
14+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1415
[JsonPropertyName("version")]
1516
public string? Version { get; init; }
1617

1718
/// <summary>
1819
/// Gets or sets the collection of extension URIs associated with the document.
1920
/// </summary>
21+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2022
[JsonPropertyName("ext")]
2123
public IEnumerable<Uri>? Ext { get; init; }
2224

2325
/// <summary>
2426
/// Gets or sets the collection of profile URIs associated with the document.
2527
/// </summary>
28+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2629
[JsonPropertyName("profile")]
2730
public IEnumerable<Uri>? Profile { get; init; }
2831

2932
/// <summary>
3033
/// Gets or sets the collection of additional metadata associated with the object.
3134
/// </summary>
35+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3236
[JsonPropertyName("meta")]
3337
public JsonObject? Meta { get; init; }
3438
}

Crews.Web.JsonApiClient/JsonApiLink.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,43 @@ public record JsonApiLink
2424
/// <summary>
2525
/// Gets or sets the relation type for the link.
2626
/// </summary>
27+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2728
[JsonPropertyName("rel")]
2829
public string? Rel { get; init; }
2930

3031
/// <summary>
3132
/// Gets or sets a link to a resource that provides additional descriptive information about the current object.
3233
/// </summary>
34+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
3335
[JsonPropertyName("describedby")]
3436
[JsonConverter(typeof(JsonApiLinkConverter))]
3537
public JsonApiLink? DescribedBy { get; init; }
3638

3739
/// <summary>
3840
/// Gets or sets the title associated with the object.
3941
/// </summary>
42+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4043
[JsonPropertyName("title")]
4144
public string? Title { get; init; }
4245

4346
/// <summary>
4447
/// Gets or sets the type of the object represented by this instance.
4548
/// </summary>
49+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4650
[JsonPropertyName("type")]
4751
public string? Type { get; init; }
4852

4953
/// <summary>
5054
/// Gets or sets the language of the linked resource, as defined by the hreflang attribute in HTML or XML sitemaps.
5155
/// </summary>
56+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5257
[JsonPropertyName("hreflang")]
5358
public string? HrefLang { get; init; }
5459

5560
/// <summary>
5661
/// Gets or sets metadata about the link.
5762
/// </summary>
63+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
5864
[JsonPropertyName("meta")]
5965
public JsonObject? Meta { get; init; }
6066

Crews.Web.JsonApiClient/JsonApiRelationship.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@ public record JsonApiRelationship
1212
/// <summary>
1313
/// Gets or sets the <c>links</c> property of the relationship object.
1414
/// </summary>
15+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1516
[JsonPropertyName("links")]
1617
public Dictionary<string, JsonApiLink>? Links { get; init; }
1718

1819
/// <summary>
1920
/// Gets or sets the data payload associated with the response or request.
2021
/// </summary>
22+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2123
[JsonPropertyName("data")]
2224
public JsonElement? Data { get; init; }
2325

2426
/// <summary>
2527
/// Gets or sets additional metadata associated with the object.
2628
/// </summary>
29+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2730
[JsonPropertyName("meta")]
2831
public JsonObject? Meta { get; init; }
2932

@@ -44,6 +47,7 @@ public record JsonApiRelationship<T> : JsonApiRelationship where T : JsonApiReso
4447
/// <summary>
4548
/// Gets or sets the data payload associated with the response or request.
4649
/// </summary>
50+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
4751
[JsonPropertyName("data")]
4852
public new T? Data { get; init; }
4953
}
@@ -60,6 +64,7 @@ public record JsonApiCollectionRelationship<T> : JsonApiRelationship where T : I
6064
/// <summary>
6165
/// Gets or sets the data payload associated with the response or request.
6266
/// </summary>
67+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
6368
[JsonPropertyName("data")]
6469
public new T? Data { get; init; }
6570
}

0 commit comments

Comments
 (0)