Is there a way to deserialize my JSON without using a wrapper class for each item?

半城伤御伤魂 提交于 2019-12-11 04:28:47

问题


I am developing an ASP.NET application using JSON.NET that interfaces with a third-party REST API. The API returns JSON in the following format:

[
    {
        user: {
            id: 12345,
            first_name: "John",
            last_name: "Smith",
            created_at: "11/12/13Z00:00:00"
        }
    },
    {
        user: {
            id: 12346,
            first_name: "Bob",
            last_name: "Adams",
            created_at: "12/12/13Z00:00:00"
        }
    }
]

The user field is redundant, but I do not have control over the API so I have to live with it. I am currently using the following code to deserialize the JSON:

// Deserialize
var responseBody = JsonConvert.DeserializeObject<IEnumerable<UserWrapper>>(responseString);

// Access Properties
foreach (var userWrapper in responseBody)
{
    var firstName = userWrapper.User.FirstName
}

// Model classes
public class UserWrapper
{
    [JsonProperty(PropertyName = "user")]
    public User User { get; set; }
}

public class User
{
    [JsonProperty(PropertyName = "id")]
    public string ID { get; set; }

    [JsonProperty(PropertyName = "first_name")]
    public string FirstName { get; set; }

    [JsonProperty(PropertyName = "last_name")]
    public string LastName { get; set; }

    [JsonProperty(PropertyName = "created_at")]
    public DateTime CreatedAt { get; set; }
}

Having the wrapper class is a little ugly. So my question is:

Is there a way to eliminate the wrapper class using JSON.NET?


回答1:


Yes, you can do this by creating a custom converter for your User class and using that during deserialization. The converter code might look something like this:

class UserConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(User));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject wrapper = JObject.Load(reader);
        return wrapper["user"].ToObject<User>();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("user");
        writer.WriteRawValue(JsonConvert.SerializeObject(value));
        writer.WriteEndObject();
    }
}

Keep the User class exactly as you defined it (with all the JsonProperty decorations). You can then deserialize your JSON and access properties like this:

var users = JsonConvert.DeserializeObject<List<User>>(responseString, new UserConverter());

foreach (var user in users)
{
    var firstName = user.FirstName
    // etc...
}

The UserWrapper class is no longer needed and can be deleted.

EDIT

Important note: the above solution will NOT work if you decorate your User class with the [JsonConverter] attribute. This is because the custom converter as written uses a second copy of the serializer to handle the User deserialization once we've gone down a level to get to the real user data. If you decorate the class, then all copies of the serializer will try to use the converter and you'll end up with a self-referencing loop.

If you would rather use the [JsonConverter] attribute, then the custom converter class will have to handle deserialization of all the properties of the User class manually. Taking this approach, the code would look like this instead:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    JObject wrapper = JObject.Load(reader);
    JToken user = wrapper["user"];
    return new User
    {
        ID = user["id"].Value<string>(),
        FirstName = user["first_name"].Value<string>(),
        LastName = user["last_name"].Value<string>(),
        CreatedAt = user["created_at"].Value<DateTime>()
    };
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    User user = (User)value;
    writer.WriteStartObject();
    writer.WritePropertyName("user");
    writer.WriteStartObject();
    writer.WritePropertyName("id");
    writer.WriteValue(user.ID);
    writer.WritePropertyName("first_name");
    writer.WriteValue(user.FirstName);
    writer.WritePropertyName("last_name");
    writer.WriteValue(user.LastName);
    writer.WritePropertyName("created_at");
    writer.WriteValue(user.CreatedAt);
    writer.WriteEndObject();
    writer.WriteEndObject();
}

The obvious problem with this approach is that you sacrifice a little bit of maintainability: if you ever add more properties to your User class, you have to remember to change your UserConverter to match.



来源:https://stackoverflow.com/questions/17934114/is-there-a-way-to-deserialize-my-json-without-using-a-wrapper-class-for-each-ite

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