一尘不染

如何使用动态(数字)键名反序列化子对象?

c#

我如何在.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"
        }
    }
}

我尝试了下面的代码,但无法正常工作。我收到错误“将值“ test”转换为类型“ ConsoleApplication2.User”时出错。路径“
users.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; }
    }

请提出建议。


阅读 232

收藏
2020-05-19

共1个答案

一尘不染

您有几个问题:

  • 您的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类型的转换器将需要较少的代码。

2020-05-19