When using a specific .ctor via JsonConstructor
for deserializing IList<ISomeInterface>
properties, the parameter names must match the original Json names and the JsonProperty
mapping on those properties are not used.
SpokenLanguages
parameter is always null since it does not match spoken_languages
, but there is a JsonProperty
mapping it:
public partial class AClass : ISomeBase
{
public AClass() { }
[JsonConstructor]
public AClass(IList<SysType> SysTypes, IList<ProductionCountry> production_countries, IList<SpokenLanguage> SpokenLanguages)
{
this.Genres = SysTypes?.ToList<IGenre>();
this.ProductionCountries = production_countries?.ToList<IProductionCountry>();
this.SpokenLanguages = SpokenLanguages?.ToList<ISpokenLanguage>();
}
public int Id { get; set; }
public IList<IGenre> Genres { get; set; }
[JsonProperty("production_countries")]
public IList<IProductionCountry> ProductionCountries { get; set; }
[JsonProperty("spoken_languages")]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
}
Is this just a "limitation" of how Json.Net
calls the constructor or is there something I am missing.
FYI: I am code generating all this via Rosyln and am not looking at generating a JsonConverter
for each type for this...
When Json.NET invokes a parameterized constructor, it matches JSON properties to constructor arguments by name, using an ordinal case-ignoring match. However, for JSON properties that also correspond to type members, which name does it use - the member name, or the override type member name specified by JsonPropertyAttribute.PropertyName
?
It appears you are hoping it matches on both, since your argument naming conventions are inconsistent:
The constructor argument production_countries
matches the overridden property name:
[JsonProperty("production_countries")]
public IList<IProductionCountry> ProductionCountries { get; set; }
The constructor argument IList<SpokenLanguage> SpokenLanguages
matches the reflected name rather than the overridden property name:
[JsonProperty("spoken_languages")]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
IList<SysType> SysTypes
matches neither (is this a typo in the question?)
However, what matters is the property name in the JSON file itself and the constructor argument name as shown in JsonSerializerInternalReader.ResolvePropertyAndCreatorValues()
. A simplified version of the algorithm is as follows:
(The implementation becomes complex when a JSON property matches both and developers expect that, for instance, [JsonProperty(Required = Required.Always)]
added to the member should be respected when set in the constructor.)
Thus the constructor argument production_countries
will match a value named "production_countries"
in the JSON, while the constructor argument SpokenLanguages
will not match a JSON value named "spoken_languages"
.
So, how to deserialize your type successfully? Firstly, you could mark the constructor parameters with [JsonProperty(overrideName)]
to override the constructor name used during deserialization:
public partial class AClass : ISomeBase
{
public AClass() { }
[JsonConstructor]
public AClass([JsonProperty("Genres")] IList<SysType> SysTypes, IList<ProductionCountry> production_countries, [JsonProperty("spoken_languages")] IList<SpokenLanguage> SpokenLanguages)
{
this.Genres = SysTypes == null ? null : SysTypes.Cast<IGenre>().ToList();
this.ProductionCountries = production_countries == null ? null : production_countries.Cast<IProductionCountry>().ToList();
this.SpokenLanguages = SpokenLanguages == null ? null : SpokenLanguages.Cast<ISpokenLanguage>().ToList();
}
Secondly, since you seem to be using the constructor to deserialize items in collections containing interfaces as concrete objects, you could consider using a single generic converter based on CustomCreationConverter
as an ItemConverter
:
public partial class AClass : ISomeBase
{
public AClass() { }
public int Id { get; set; }
[JsonProperty(ItemConverterType = typeof(CustomCreationConverter<IGenre, SysType>))]
public IList<IGenre> Genres { get; set; }
[JsonProperty("production_countries", ItemConverterType = typeof(CustomCreationConverter<IProductionCountry, ProductionCountry>))]
public IList<IProductionCountry> ProductionCountries { get; set; }
[JsonProperty("spoken_languages", ItemConverterType = typeof(CustomCreationConverter<ISpokenLanguage, SpokenLanguage>))]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
}
public class CustomCreationConverter<T, TSerialized> : CustomCreationConverter<T> where TSerialized : T, new()
{
public override T Create(Type objectType)
{
return new TSerialized();
}
}
Example fiddle showing both options.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With