From ac47631c859a98f6b3b0c5818a1a23da1ecfe13c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:24:44 +0000 Subject: [PATCH 1/5] Initial plan From 037487c39bc462e4c9f55c2439fa3807ce73c085 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:32:12 +0000 Subject: [PATCH 2/5] Handle JSON null token in collection readers Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CollectionOfNullableStructsReaderWriter.cs | 5 ++ .../JsonCollectionOfReferencesReaderWriter.cs | 5 ++ .../JsonCollectionOfStructsReaderWriter.cs | 5 ++ .../Json/JsonCollectionReaderWriterTest.cs | 54 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs diff --git a/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs b/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs index 63f25b36e98..98a9bd2dc3d 100644 --- a/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs @@ -55,6 +55,11 @@ public JsonCollectionOfNullableStructsReaderWriter(JsonValueReaderWriter public override object FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { + if (manager.CurrentReader.TokenType == JsonTokenType.Null) + { + return null!; + } + IList collection; if (IsReadOnly) { diff --git a/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs b/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs index 323e296d005..9af50cf796d 100644 --- a/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs @@ -36,6 +36,11 @@ public JsonCollectionOfStructsReaderWriter(JsonValueReaderWriter eleme /// public override IEnumerable FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { + if (manager.CurrentReader.TokenType == JsonTokenType.Null) + { + return null!; + } + IList collection; if (IsReadOnly) { diff --git a/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs b/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs new file mode 100644 index 00000000000..19e6fab61d2 --- /dev/null +++ b/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Storage; + +public class JsonCollectionReaderWriterTest +{ + [ConditionalFact] + public void Reads_JSON_null_as_null_for_collection_of_references() + { + var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>(JsonStringReaderWriter.Instance); + + Assert.Null(readerWriter.FromJsonString("null")); + Assert.Equal(["a", "b"], (IEnumerable)readerWriter.FromJsonString("""["a","b"]""")); + } + + [ConditionalFact] + public void Reads_JSON_null_as_null_for_read_only_collection_of_references() + { + var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>( + JsonStringReaderWriter.Instance); + + Assert.Null(readerWriter.FromJsonString("null")); + } + + [ConditionalFact] + public void Reads_JSON_null_as_null_for_array_of_references() + { + var readerWriter = new JsonCollectionOfReferencesReaderWriter(JsonStringReaderWriter.Instance); + + Assert.Null(readerWriter.FromJsonString("null")); + } + + [ConditionalFact] + public void Reads_JSON_null_as_null_for_collection_of_structs() + { + var readerWriter = new JsonCollectionOfStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); + + Assert.Null(readerWriter.FromJsonString("null")); + Assert.Equal([1, 2], (IEnumerable)readerWriter.FromJsonString("[1,2]")); + } + + [ConditionalFact] + public void Reads_JSON_null_as_null_for_collection_of_nullable_structs() + { + var readerWriter = new JsonCollectionOfNullableStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); + + Assert.Null(readerWriter.FromJsonString("null")); + Assert.Equal([1, null, 2], (IEnumerable)readerWriter.FromJsonString("[1,null,2]")); + } +} From 3a016548232387b854cefe63882e73faf0c89ef6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:35:10 +0000 Subject: [PATCH 3/5] Use Fact attribute for new tests Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Storage/Json/JsonCollectionReaderWriterTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs b/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs index 19e6fab61d2..7e042678d2e 100644 --- a/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs +++ b/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Storage; public class JsonCollectionReaderWriterTest { - [ConditionalFact] + [Fact] public void Reads_JSON_null_as_null_for_collection_of_references() { var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>(JsonStringReaderWriter.Instance); @@ -17,7 +17,7 @@ public void Reads_JSON_null_as_null_for_collection_of_references() Assert.Equal(["a", "b"], (IEnumerable)readerWriter.FromJsonString("""["a","b"]""")); } - [ConditionalFact] + [Fact] public void Reads_JSON_null_as_null_for_read_only_collection_of_references() { var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>( @@ -26,7 +26,7 @@ public void Reads_JSON_null_as_null_for_read_only_collection_of_references() Assert.Null(readerWriter.FromJsonString("null")); } - [ConditionalFact] + [Fact] public void Reads_JSON_null_as_null_for_array_of_references() { var readerWriter = new JsonCollectionOfReferencesReaderWriter(JsonStringReaderWriter.Instance); @@ -34,7 +34,7 @@ public void Reads_JSON_null_as_null_for_array_of_references() Assert.Null(readerWriter.FromJsonString("null")); } - [ConditionalFact] + [Fact] public void Reads_JSON_null_as_null_for_collection_of_structs() { var readerWriter = new JsonCollectionOfStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); @@ -43,7 +43,7 @@ public void Reads_JSON_null_as_null_for_collection_of_structs() Assert.Equal([1, 2], (IEnumerable)readerWriter.FromJsonString("[1,2]")); } - [ConditionalFact] + [Fact] public void Reads_JSON_null_as_null_for_collection_of_nullable_structs() { var readerWriter = new JsonCollectionOfNullableStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); From 6327d15b685644f05a2ee4b9fd81511852385533 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:30:55 +0000 Subject: [PATCH 4/5] Fix regression: guard FromJsonString("null") call against null jsonReaderWriter Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CollectionOfNullableStructsReaderWriter.cs | 5 -- .../JsonCollectionOfReferencesReaderWriter.cs | 5 -- .../JsonCollectionOfStructsReaderWriter.cs | 5 -- .../Storage/Json/JsonValueReaderWriter.cs | 4 +- .../JsonTypesTestBase.cs | 70 +++++++++++++++---- .../Json/JsonCollectionReaderWriterTest.cs | 54 -------------- 6 files changed, 60 insertions(+), 83 deletions(-) delete mode 100644 test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs diff --git a/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs b/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs index 98a9bd2dc3d..63f25b36e98 100644 --- a/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCollectionOfNullableStructsReaderWriter.cs @@ -55,11 +55,6 @@ public JsonCollectionOfNullableStructsReaderWriter(JsonValueReaderWriter public override object FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { - if (manager.CurrentReader.TokenType == JsonTokenType.Null) - { - return null!; - } - IList collection; if (IsReadOnly) { diff --git a/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs b/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs index 9af50cf796d..323e296d005 100644 --- a/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonCollectionOfStructsReaderWriter.cs @@ -36,11 +36,6 @@ public JsonCollectionOfStructsReaderWriter(JsonValueReaderWriter eleme /// public override IEnumerable FromJsonTyped(ref Utf8JsonReaderManager manager, object? existingObject = null) { - if (manager.CurrentReader.TokenType == JsonTokenType.Null) - { - return null!; - } - IList collection; if (IsReadOnly) { diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index 94daf881dcb..ba01afe7d51 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -79,7 +79,9 @@ public object FromJsonString(string json, object? existingObject = null) var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)), null); readerManager.MoveNext(); - return FromJson(ref readerManager, existingObject); + return readerManager.CurrentReader.TokenType == JsonTokenType.Null + ? null! + : FromJson(ref readerManager, existingObject); } /// diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index c50aeef9b48..a77eb8527fb 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -2623,6 +2623,45 @@ protected class NullableEnumU64CollectionType public IList EnumU64 { get; set; } = null!; } + [Fact] + public virtual Task Can_read_write_null_value_of_collection_of_string_JSON_values() + => Can_read_and_write_JSON_value?>( + nameof(NullStringCollectionType.String), + null, + """{"Prop":null}""", + mappedCollection: true); + + protected class NullStringCollectionType + { + public List? String { get; set; } + } + + [Fact] + public virtual Task Can_read_write_null_value_of_collection_of_int_JSON_values() + => Can_read_and_write_JSON_value?>( + nameof(NullIntCollectionType.Int32), + null, + """{"Prop":null}""", + mappedCollection: true); + + protected class NullIntCollectionType + { + public List? Int32 { get; set; } + } + + [Fact] + public virtual Task Can_read_write_null_value_of_collection_of_nullable_int_JSON_values() + => Can_read_and_write_JSON_value?>( + nameof(NullNullableIntCollectionType.Int32), + null, + """{"Prop":null}""", + mappedCollection: true); + + protected class NullNullableIntCollectionType + { + public List? Int32 { get; set; } + } + [Fact] public virtual Task Can_read_write_collection_of_sbyte_values_with_converter_as_JSON_string() => Can_read_and_write_JSON_property_value( @@ -3671,16 +3710,18 @@ protected virtual Task Can_read_and_write_JSON_value( using var writer = new Utf8JsonWriter(stream); var jsonReaderWriter = property.GetJsonValueReaderWriter() - ?? property.GetTypeMapping().JsonValueReaderWriter!; + ?? property.GetTypeMapping().JsonValueReaderWriter; - var toString = value == null ? null : jsonReaderWriter.ToJsonString(value); - var fromString = toString == null ? null : jsonReaderWriter.FromJsonString(toString, existingObject); + var toString = value == null ? null : jsonReaderWriter!.ToJsonString(value); + var fromString = value == null + ? jsonReaderWriter?.FromJsonString("null") + : jsonReaderWriter!.FromJsonString(toString!, existingObject); Assert.Equal(value, fromString); - var actual = ToJsonPropertyString(jsonReaderWriter, value); + var actual = ToJsonPropertyString(jsonReaderWriter!, value); Assert.Equal(json, actual); - var fromJson = FromJsonPropertyString(jsonReaderWriter, actual, existingObject); + var fromJson = FromJsonPropertyString(jsonReaderWriter!, actual, existingObject); if (existingObject != null) { Assert.Same(fromJson, existingObject); @@ -3717,15 +3758,18 @@ protected virtual Task Can_read_and_write_JSON_value( Assert.Equal(elementNullable, element.IsNullable); - var comparer = element.GetValueComparer()!; - var elementReaderWriter = element.GetJsonValueReaderWriter()!; - foreach (var item in (IEnumerable)value!) + if (value != null) { - Assert.True(comparer.Equals(item, comparer.Snapshot(item))); - Assert.True( - comparer.Equals( - item, FromJsonPropertyString( - elementReaderWriter, ToJsonPropertyString(elementReaderWriter, item)))); + var comparer = element.GetValueComparer()!; + var elementReaderWriter = element.GetJsonValueReaderWriter()!; + foreach (var item in (IEnumerable)value) + { + Assert.True(comparer.Equals(item, comparer.Snapshot(item))); + Assert.True( + comparer.Equals( + item, FromJsonPropertyString( + elementReaderWriter, ToJsonPropertyString(elementReaderWriter, item)))); + } } AssertElementFacets(element, facets); diff --git a/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs b/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs deleted file mode 100644 index 7e042678d2e..00000000000 --- a/test/EFCore.Tests/Storage/Json/JsonCollectionReaderWriterTest.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.ObjectModel; -using Microsoft.EntityFrameworkCore.Storage.Json; - -namespace Microsoft.EntityFrameworkCore.Storage; - -public class JsonCollectionReaderWriterTest -{ - [Fact] - public void Reads_JSON_null_as_null_for_collection_of_references() - { - var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>(JsonStringReaderWriter.Instance); - - Assert.Null(readerWriter.FromJsonString("null")); - Assert.Equal(["a", "b"], (IEnumerable)readerWriter.FromJsonString("""["a","b"]""")); - } - - [Fact] - public void Reads_JSON_null_as_null_for_read_only_collection_of_references() - { - var readerWriter = new JsonCollectionOfReferencesReaderWriter, string>( - JsonStringReaderWriter.Instance); - - Assert.Null(readerWriter.FromJsonString("null")); - } - - [Fact] - public void Reads_JSON_null_as_null_for_array_of_references() - { - var readerWriter = new JsonCollectionOfReferencesReaderWriter(JsonStringReaderWriter.Instance); - - Assert.Null(readerWriter.FromJsonString("null")); - } - - [Fact] - public void Reads_JSON_null_as_null_for_collection_of_structs() - { - var readerWriter = new JsonCollectionOfStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); - - Assert.Null(readerWriter.FromJsonString("null")); - Assert.Equal([1, 2], (IEnumerable)readerWriter.FromJsonString("[1,2]")); - } - - [Fact] - public void Reads_JSON_null_as_null_for_collection_of_nullable_structs() - { - var readerWriter = new JsonCollectionOfNullableStructsReaderWriter, int>(JsonInt32ReaderWriter.Instance); - - Assert.Null(readerWriter.FromJsonString("null")); - Assert.Equal([1, null, 2], (IEnumerable)readerWriter.FromJsonString("[1,null,2]")); - } -} From 21d97f74f80f40817f4dfcff231ccb504db28222 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:35:58 +0000 Subject: [PATCH 5/5] Fix return type of FromJsonString to object? and update API baseline Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- src/EFCore/EFCore.baseline.json | 2 +- src/EFCore/Storage/Json/JsonValueReaderWriter.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 7c9812b71e2..7758e38cca6 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -17294,7 +17294,7 @@ "Member": "abstract object FromJson(ref Microsoft.EntityFrameworkCore.Storage.Json.Utf8JsonReaderManager manager, object? existingObject = null);" }, { - "Member": "object FromJsonString(string json, object? existingObject = null);" + "Member": "object? FromJsonString(string json, object? existingObject = null);" }, { "Member": "abstract void ToJson(System.Text.Json.Utf8JsonWriter writer, object? value);" diff --git a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs index ba01afe7d51..c70814cb8cd 100644 --- a/src/EFCore/Storage/Json/JsonValueReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonValueReaderWriter.cs @@ -70,7 +70,7 @@ internal JsonValueReaderWriter() /// The JSON to parse. /// Can be used to update an existing object, rather than create a new one. /// The read value. - public object FromJsonString(string json, object? existingObject = null) + public object? FromJsonString(string json, object? existingObject = null) { if (string.IsNullOrWhiteSpace(json)) { @@ -80,7 +80,7 @@ public object FromJsonString(string json, object? existingObject = null) var readerManager = new Utf8JsonReaderManager(new JsonReaderData(Encoding.UTF8.GetBytes(json)), null); readerManager.MoveNext(); return readerManager.CurrentReader.TokenType == JsonTokenType.Null - ? null! + ? null : FromJson(ref readerManager, existingObject); }