我有一个类EntityBase,它是从DynamicObject派生的,没有空的默认构造函数。
// this is not the actual type but a mock to test the behavior with
public class EntityBase : DynamicObject
{
public string EntityName { get; private set; }
private readonly Dictionary<string, object> values = new Dictionary<string, object>();
public EntityBase(string entityName)
{
this.EntityName = entityName;
}
public virtual object this[string fieldname]
{
get
{
if (this.values.ContainsKey(fieldname))
return this.values[fieldname];
return null;
}
set
{
if (this.values.ContainsKey(fieldname))
this.values[fieldname] = value;
else
this.values.Add(fieldname, value);
}
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return this.values.Keys.ToList();
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this[binder.Name] = value;
return true;
}
}我想反序列化的JSON如下所示:
{'Name': 'my first story', 'ToldByUserId': 255 }EntityBase既没有Name属性,也没有ToldByUserId属性。它们应该添加到DynamicObject中。
如果我让DeserializeObject像这样创建对象,那么一切都可以像预期的那样工作:
var story = JsonConvert.DeserializeObject<EntityBase>(JSON);但是,由于我没有空的默认构造函数,并且无法更改类,所以我选择了一个CustomCreationConverter:
public class StoryCreator : CustomCreationConverter<EntityBase>
{
public override EntityBase Create(Type objectType)
{
return new EntityBase("Story");
}
}但
var stroy = JsonConvert.DeserializeObject<EntityBase>(JSON, new StoryCreator());抛出
无法将JSON对象填充到类型'DynamicObjectJson.EntityBase‘上。路径'Name',第1行,位置8。
DeserializeObject似乎对由CustomCreationConverter创建的对象调用了PopulateObject。当我尝试手动执行此操作时,错误保持不变。
JsonConvert.PopulateObject(JSON, new EntityBase("Story"));我进一步假设PopulateObject不检查目标类型是否来自DynamicObject,因此不会返回到TrySetMember。
请注意,我对EntityBase类型定义没有影响,它来自外部库,不能更改。
任何见解都将是非常感谢的!
编辑:添加了一个示例:https://dotnetfiddle.net/EGOCFU
发布于 2018-04-20 01:21:06
在Json.NET对反序列化动态对象(定义为生成JsonDynamicContract的对象)的支持中,您似乎遇到了一些错误或限制:
[JsonConstructor]标记,它也不会被使用。
在这里,预加载所有属性的必要逻辑似乎在JsonSerializerInternalReader.CreateDynamic()中完全缺失。与JsonSerializerInternalReader.CreateNewObject()进行比较,后者指明了所需内容。
由于逻辑看起来相当详细,这可能是一个限制,而不是一个bug。实际上,这方面存在封闭式发行#47,这表明它没有实现:
添加此功能将需要相当多的工作。如果添加了拉请求,欢迎您提交。JsonObjectContract的对象)不同,构造和填充的逻辑完全包含在前面提到的JsonSerializerInternalReader.CreateDynamic()中。
我不明白为什么不能用相当简单的代码重组来实现这一点。你可能是提交问题要求的。如果实现了这一点,您的StoryCreator将按原样工作。在没有#1或#2的情况下,可以创建一个JsonConverter,其逻辑大致以JsonSerializerInternalReader.CreateDynamic()为模型,后者调用指定的创建方法,然后填充动态和非动态属性,如下所示:
public class EntityBaseConverter : ParameterizedDynamicObjectConverterBase<EntityBase>
{
public override EntityBase CreateObject(JObject jObj, Type objectType, JsonSerializer serializer, ICollection<string> usedParameters)
{
var entityName = jObj.GetValue("EntityName", StringComparison.OrdinalIgnoreCase);
if (entityName != null)
{
usedParameters.Add(((JProperty)entityName.Parent).Name);
}
var entityNameString = entityName == null ? "" : entityName.ToString();
if (objectType == typeof(EntityBase))
{
return new EntityBase(entityName == null ? "" : entityName.ToString());
}
else
{
return (EntityBase)Activator.CreateInstance(objectType, new object [] { entityNameString });
}
}
}
public abstract class ParameterizedDynamicObjectConverterBase<T> : JsonConverter where T : DynamicObject
{
public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } // Or possibly return objectType == typeof(T);
public abstract T CreateObject(JObject jObj, Type objectType, JsonSerializer serializer, ICollection<string> usedParameters);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Logic adapted from JsonSerializerInternalReader.CreateDynamic()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L1751
// By James Newton-King https://github.com/JamesNK
var contract = (JsonDynamicContract)serializer.ContractResolver.ResolveContract(objectType);
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var used = new HashSet<string>();
var obj = CreateObject(jObj, objectType, serializer, used);
foreach (var jProperty in jObj.Properties())
{
var memberName = jProperty.Name;
if (used.Contains(memberName))
continue;
// first attempt to find a settable property, otherwise fall back to a dynamic set without type
JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName);
if (property != null && property.Writable && !property.Ignored)
{
var propertyValue = jProperty.Value.ToObject(property.PropertyType, serializer);
property.ValueProvider.SetValue(obj, propertyValue);
}
else
{
object propertyValue;
if (jProperty.Value.Type == JTokenType.Null)
propertyValue = null;
else if (jProperty.Value is JValue)
// Primitive
propertyValue = ((JValue)jProperty.Value).Value;
else
propertyValue = jProperty.Value.ToObject<IDynamicMetaObjectProvider>(serializer);
// Unfortunately the following is not public!
// contract.TrySetMember(obj, memberName, propertyValue);
// So we have to duplicate the logic of what Json.NET has already done.
CallSiteCache.SetValue(memberName, obj, propertyValue);
}
}
return obj;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
internal static class CallSiteCache
{
// Adapted from the answer to
// https://stackoverflow.com/questions/12057516/c-sharp-dynamicobject-dynamic-properties
// by jbtule, https://stackoverflow.com/users/637783/jbtule
// And also
// https://github.com/mgravell/fast-member/blob/master/FastMember/CallSiteCache.cs
// by Marc Gravell, https://github.com/mgravell
private static readonly Dictionary<string, CallSite<Func<CallSite, object, object, object>>> setters
= new Dictionary<string, CallSite<Func<CallSite, object, object, object>>>();
public static void SetValue(string propertyName, object target, object value)
{
CallSite<Func<CallSite, object, object, object>> site;
lock (setters)
{
if (!setters.TryGetValue(propertyName, out site))
{
var binder = Binder.SetMember(CSharpBinderFlags.None,
propertyName, typeof(CallSiteCache),
new List<CSharpArgumentInfo>{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)});
setters[propertyName] = site = CallSite<Func<CallSite, object, object, object>>.Create(binder);
}
}
site.Target(site, target, value);
}
}然后像这样使用它:
var settings = new JsonSerializerSettings
{
Converters = { new EntityBaseConverter() },
};
var stroy = JsonConvert.DeserializeObject<EntityBase>(JSON, settings);由于EntityBase似乎是多个派生类的基类,所以我编写了转换器来处理所有派生类型的EntityBase,假设它们都有一个具有相同签名的参数化构造函数。
注意,我从JSON获取EntityName。如果您希望将其硬编码到"Story",您可以这样做,但仍然应该将EntityName属性的实际名称添加到usedParameters集合中,以防止创建同名的动态属性。
样品工作.Net小提琴这里。
https://stackoverflow.com/questions/49916136
复制相似问题