如何反序列化具有动态(数字)键名的子对象

本文关键字:对象 数字 反序列化 动态 | 更新日期: 2023-09-27 18:13:35

如何在。net中使用newtonsoft json.net反序列化json结构

{
    "users" : {
        "parentname":"test",
        "100034" : {
            "name"  : "tom",
            "state" : "WA",
            "id"    : "cedf-c56f-18a4-4b1"
        },
        "10045" : {
            "name"  : "steve",
            "state" : "NY",
            "id"    : "ebb2-92bf-3062-7774"
        },
        "12345" : {
            "name"  : "mike",
            "state" : "MA",
            "id"    : "fb60-b34f-6dc8-aaf7"
        }
    }
}

我尝试下面的代码,但它不工作。我得到了错误'错误转换值"测试"类型'ConsoleApplication2.User'。路径的用户。Parentname ',第5行,位置35.'

class Program
    {
        static void Main(string[] args)
        {
            string json = @"
        {
            ""users"": {
                ""parentname"":""test"",
                ""10045"": {
                    ""name"": ""steve"",
                    ""state"": ""NY"",
                    ""id"": ""ebb2-92bf-3062-7774""
                }
            }
        }";
            RootObject root = JsonConvert.DeserializeObject<RootObject>(json);
        }
    }
    class RootObject
    {
        public string ParentName { get; set; }
        public Dictionary<string, User> users { get; set; }
    }
    class User
    {
        public string name { get; set; }
        public string state { get; set; }
        public string id { get; set; }
        public string ParentName { get; set; }
    }

请建议。

如何反序列化具有动态(数字)键名的子对象

你有几个问题:

  • 你的JSON有一个额外的嵌套级别,根对象包含一个属性"users":

    {
        "users" : { ... }
    }
    
  • 您的"users"对象混合了已知和未知的属性名称。用已知和未知字段反序列化json的问题解决了类似的情况,但是在您的情况下,您的未知属性总是有一个固定的模式,它们的值应该被反序列化到poco的字典中——特别是User类。因此,这里的答案不能完全满足您的需求,内置功能[JsonExtensionData]也不能满足您的需求。

以下转换器允许将未知属性反序列化到类型的容器中,而不是到任意类型的字典中:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class JsonTypedExtensionDataAttribute : Attribute
{
}
public class TypedExtensionDataConverter<TObject> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(TObject).IsAssignableFrom(objectType);
    }
    JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
    {
        try
        {
            return contract.Properties.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any()).Single();
        }
        catch (InvalidOperationException ex)
        {
            throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
        }
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var jObj = JObject.Load(reader);
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
        var extensionJsonProperty = GetExtensionJsonProperty(contract);
        var extensionJProperty = (JProperty)null;
        for (int i = jObj.Count - 1; i >= 0; i--)
        {
            var property = (JProperty)jObj.AsList()[i];
            if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
            {
                if (extensionJProperty == null)
                {
                    extensionJProperty = new JProperty(extensionJsonProperty.PropertyName, new JObject());
                    jObj.Add(extensionJProperty);
                }
                ((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
            }
        }
        var value = existingValue ?? contract.DefaultCreator();
        using (var subReader = jObj.CreateReader())
            serializer.Populate(subReader, value);
        return value;
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
        var extensionJsonProperty = GetExtensionJsonProperty(contract);
        JObject jObj;
        using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
        {
            jObj = JObject.FromObject(value, serializer);
        }
        var extensionValue = (jObj[extensionJsonProperty.PropertyName] as JObject).RemoveFromLowestPossibleParent();
        if (extensionValue != null)
        {
            for (int i = extensionValue.Count - 1; i >= 0; i--)
            {
                var property = (JProperty)extensionValue.AsList()[i];
                jObj.Add(property.RemoveFromLowestPossibleParent());
            }
        }
        jObj.WriteTo(writer);
    }
    [ThreadStatic]
    static bool disabled;
    // Disables the converter in a thread-safe manner.
    bool Disabled { get { return disabled; } set { disabled = value; } }
    public override bool CanWrite { get { return !Disabled; } }
    public override bool CanRead { get { return !Disabled; } }
}
public struct PushValue<T> : IDisposable
{
    Action<T> setValue;
    T oldValue;
    public PushValue(T value, Func<T> getValue, Action<T> setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }
    #region IDisposable Members
    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }
    #endregion
}
public static class JsonExtensions
{
    public static TJToken RemoveFromLowestPossibleParent<TJToken>(this TJToken node) where TJToken : JToken
    {
        if (node == null)
            return null;
        var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
        if (contained != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (node.Parent is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
    public static IList<JToken> AsList(this IList<JToken> container) { return container; }
}

然后在你的类中使用它,如下所示:

class RootObject
{
    [JsonProperty("users")]
    public Users Users { get; set; }
}
[JsonConverter(typeof(TypedExtensionDataConverter<Users>))]
class Users
{
    public Users()
    {
        this.UserTable = new Dictionary<string, User>();
    }
    [JsonProperty("parentname")]
    public string ParentName { get; set; }
    [JsonTypedExtensionData]
    public Dictionary<string, User> UserTable { get; set; }
}
class User
{
    public string name { get; set; }
    public string state { get; set; }
    public string id { get; set; }
}

我以一种相当通用的方式编写了转换器,因此它可以被重用。为Users类型硬编码的转换器将需要更少的代码。

Json必须是这样的:

{
   "ParentName":"test",
   "users":{
      "10045":{
         "name":"steve",
         "state":"NY",
         "id":"ebb2-92bf-3062-7774",
         "ParentName":"someOtherName"
      }
   }
} 

为了用你给定的类结构反序列化它:

class RootObject
{
   public string ParentName { get; set; }
   public Dictionary<string, User> users { get; set; }
}
class User
{
   public string name { get; set; }
   public string state { get; set; }
   public string id { get; set; }
   public string ParentName { get; set; }
} 

现在你可以反序列化Json字符串:

var root = JsonConvert.DeserializeObject<RootObject>(json);