Deserialize the JSON where the values are field names with JSON.NET

眉间皱痕 提交于 2019-12-18 06:57:07

问题


I have a very undesirable situation which requires me to deserialize the JSON where the values are field names with JSON.NET. Assuming that I have the following JSON which is very properly structured:

{
    "name": "tugberk",
    "roles": [
        { "id": "1", "name": "admin" },
        { "id": "2", "name": "guest" }
    ]
}

It's very easy to deserialize this with JSON.NET to a CLR object:

class Program
{
    static void Main(string[] args)
    {
        var camelCaseSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };

        var text = File.ReadAllText("user_normal.txt");
        var obj = JsonConvert.DeserializeObject<User>(text, camelCaseSettings);
    }
}

public class User
{
    public string Name { get; set; }
    public Role[] Roles { get; set; }
}

public class Role
{
    public int Id { get; set; }
    public string Name { get; set; }
}

However, in my current case, I have the following horrible JSON which is equivalent to above JSON in terms of values:

{
    "name": "tugberk",
    "roles": {
        "1": { "name": "admin" },
        "2": { "name": "guest" }
    }
}

As you can see, roles field is not an array; it's an object which contains other values as objects with it's unique keys as their field names (which is horrible). What's the best way to deserialize this JSON to above User class with JSON.NET?


回答1:


You can create a custom JsonConverter which serializes/deserializes Role[]. You can then decorate your Roles property with the JsonConverterAttribute like this:

public class User
{
    public string Name { get; set; }
    [JsonConverter(typeof(RolesConverter))]
    public Role[] Roles { get; set; }
}

In your converter class you are able to read an object and return an array instead. Your converter class may look like this:

class RolesConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Role[]);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // deserialize as object
        var roles = serializer.Deserialize<JObject>(reader);
        var result = new List<Role>();

        // create an array out of the properties
        foreach (JProperty property in roles.Properties())
        {
            var role = property.Value.ToObject<Role>();
            role.Id = int.Parse(property.Name);
            result.Add(role);
        }

        return result.ToArray();
    }


    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}



回答2:


There are a few options available to you. You could have a custom JsonConverter and serialize it manually. Since by the time of writing this fero provided an answer based on this, I'll give you an alternative, requiring two surrogate classes:

public class JsonUser
{
    public string Name { get; set; }
    public Dictionary<int, JsonRole> Roles { get; set; }
}

public class JsonRole
{
    public string Name { get; set; }
}

And in your Role class:

public static implicit operator User(JsonUser user)
{
    return new User
        {
            Name = user.Name,
            Roles = user.Roles
                        .Select(kvp => new Role { Id = kvp.Key, Name = kvp.Value.Name})
                        .ToArray()
        };
}

Which can be used like this:

User jsonUser = JsonConvert.DeserializeObject<JsonUser>(json);

Now, this is done at the expense of creating an intermediate object and probably isn't suited for most cases.

For the sake of completeness, I'll include my version of the JsonConverter solution:

public class UserRolesConverter : JsonConverter
{

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof (Role[]);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<JObject>(reader)
                         .Properties()
                         .Select(p => new Role
                             {
                                 Id = Int32.Parse(p.Name),
                                 Name = (string) p.Value["name"]
                             })
                         .ToArray();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public class User
{
    public string Name { get; set; }
    [JsonConverter(typeof(UserRolesConverter))]
    public Role[] Roles { get; set; }
}

var jsonUser = JsonConvert.DeserializeObject<User>(json);



回答3:


Hm you can try:

dynamic jObject = JObject.Parse(text);
List<User> users = new List<User>();

foreach(dynamic dUser in jObject)
{
    List<Role> roles = new List<Role>();
    User user = new User();
    user.Name = dUser.name;
    foreach(PropertyInfo info in dUser.GetType().GetProperties())
    {
        Role role = new Role();
        role.Id = info.Name;
        role.Name = dUser[info.Name].name;
        roles.Ad(role);
    }
    user.Roles = roles.ToArray();
}


来源:https://stackoverflow.com/questions/17745814/deserialize-the-json-where-the-values-are-field-names-with-json-net

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!