Reputation: 1890
So setting an enumerable using an accessor works great.
public class SetEnumerableWithAccessor
{
public IEnumerable<string>? Strings { get; set; }
}
string json = JsonSerializer.Serialize(new SetEnumerableWithAccessor
{
Strings = new [] { "test" }
});
JsonSerializer.Deserialize<SetEnumerableWithAccessor>(json);
Setting an enumerable using a constructor fails.
public class SetEnumerableWithConstructor
{
[JsonConstructor]
public SetEnumerableWithConstructor(IEnumerable<string> strings)
{
Strings = strings.ToImmutableList();
}
public ImmutableList<string> Strings { get; }
}
string json = JsonSerializer.Serialize(new SetEnumerableWithConstructor(new string[] { "test" }));
JsonSerializer.Deserialize<SetEnumerableWithConstructor>(json);
This is the exception that is thrown.
System.InvalidOperationException
Each parameter in the deserialization constructor on type 'CertusLogic.Audio.Test.FourierTransformTest+SetEnumerableWithConstructor' must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(Type parentType)
at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo jsonTypeInfo, Nullable`1 actualByteCount)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
Really banging my head on something that feels like it should be pretty straightforward.
Upvotes: 1
Views: 1183
Reputation: 116795
When deserializing a class with a parameterized constructor, Sytem.Text.Json requires that constructor parameters match with serialized properties. From the docs [1]:
The parameter names of a parameterized constructor must match the property names and types. Matching is case-insensitive, and the constructor parameter must match the actual property name even if you use
[JsonPropertyName]
to rename a property.
Thus you must add a constructor to your class with an ImmutableList<string> strings
argument and mark that constructor, and only that constructor, with [JsonConstructor]
:
public class SetEnumerableWithConstructor
{
[JsonConstructor]
public SetEnumerableWithConstructor(ImmutableList<string> strings) => Strings = strings;
public SetEnumerableWithConstructor(IEnumerable<string> strings) => Strings = strings.ToImmutableList();
public ImmutableList<string> Strings { get; }
}
And now your class will be serializable. Demo fiddle here: https://dotnetfiddle.net/wo5TlF.
Note that there is currently an enhancement [JsonSerializer] Relax restrictions on ctor param type to immutable property type matching where reasonable #44428 open asking to loosen the argument type matching restrictions, with nothing scheduled as of April 2023.
[1] The documentation was updated in 2023. At the time this question was asked, the documentation merely stated
The parameter names of a parameterized constructor must match the property names.
Upvotes: 5