问题
Is it possible to define options to JsonConvert.PopulateObject to exclude fields given in a json, that does not exists in the target object's interface implementation?
public interface IElementWriter
{
string Name { get; set; }
}
public interface IElementUpdateWriter : IElementWriter
{
string FirstName { get; set; }
}
public interface IElementInsertWriter : IElementWriter
{
DateTime? CreationDate { get; set; }
}
public class Element:IElementWriter, IElementInsertWriter, IElementUpdateWriter {
public int ID { get; set; }
public string Name { get; set; }
public DateTime? CreationDate { get; set; }
public string FirstName { get; set; }
}
static void Main(string[] args)
{
IElementWriter element = new Element() { ID = 1, Name = "SourceName", CreationDate=DateTime.Today, FirstName="SourceFirstName" };
string json = "{ id:'8', Name:'newName', FirstName:'newFirstName' }";
JsonConvert.PopulateObject(json, element, new JsonSerializerSettings() {
});
Console.WriteLine(JsonConvert.SerializeObject(element));
Console.ReadLine();
}
Result:
{"ID":8,"Name":"newName","CreationDate":"2019-06-05T00:00:00+02:00","FirstName":"newFirstName"}
Required because IElementWriter does not have ID nor FirstName:
{"ID":1,"Name":"newName","CreationDate":"2019-06-05T00:00:00+02:00","FirstName":"SourceFirstName"}
回答1:
There is no simple setting in JsonSerializerSettings
that will cause JsonConvert.PopulateObject() to populate an instance of a derived type as if it were an instance of some base type. To confirm this, you can check the source for JsonSerializerInternalReader.Populate(), which takes as arguments only a reader
and target
and pulls the target's contract directly from its type:
public void Populate(JsonReader reader, object target)
{
Type objectType = target.GetType();
JsonContract contract = Serializer._contractResolver.ResolveContract(objectType);
Thus your options include:
Modify the definition of your
Element
class and add[JsonIgnore]
to the properties you don't want to populate.You probably don't want to do this because it will prevent the properties from ever being serialized or deserialized.
Use a custom contract resolver to ignore all properties of
Element
that are not also properties ofIElementWriter
.This would seem to be the better solution.
Assuming you choose option #2, you can introduce the following custom contract resolver:
public class UpcastingContractResolver<TDerived, TBase> : DefaultContractResolver where TDerived: TBase
{
readonly HashSet<string> baseProperties = ((JsonObjectContract)JsonSerializer.Create().ContractResolver.ResolveContract(typeof(TBase)))
.Properties.Select(p => p.UnderlyingName)
.ToHashSet();
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
if (type == typeof(TDerived) || type.IsSubclassOf(typeof(TDerived)))
{
foreach (var property in properties)
{
if (!baseProperties.Contains(property.UnderlyingName))
property.Ignored = true;
}
}
return properties;
}
}
Then, cache an instance somewhere for performance as suggested by Newtonsoft:
static IContractResolver elementAsElementWriterResolver = new UpcastingContractResolver<Element, IElementWriter>();
And populate your element
as follows:
// Only populate those properties present in IElementWriter
JsonConvert.PopulateObject(json, element, new JsonSerializerSettings
{
ContractResolver = elementAsElementWriterResolver
});
Demo fiddle here.
来源:https://stackoverflow.com/questions/56467203/how-to-exclude-properties-from-jsonconvert-populateobject-that-dont-exist-in-so