Frage

Not too familiar with JSON yet and have come across a an issue that not obvious to me.

The api I'm querying returns a standard response object with the result of the processed command/api request embedded within a data object within the json.

So the response comes back like the following for all requests on the API the data component changes dependent on whats been requested.

ObjectType1 response

{
    "data": { 
        "person" : { 
            "id" : 21, 
            "name" : "Json can be annoying at times"
        }
    },
    "message" : "",
    "result" : "success"
}

or another request on api will return the following List of

ObjectType2 response

{
    "data": { 
        "1234" : {
            "id": 1234,
            "title" : "Terminator"

        },
        "3245" : { 
            "id" : 3245, 
            "name" : "Terminator 2"
        }
    },
    "message" : "",
    "result" : "success"
}

I would like to have a custom JsonConverter that pulls out the response into an object as such

public class MyResponse {

    [JsonProperty(PropertyName = "data")]
    public string Data { get; set; }

    [JsonProperty(PropertyName = "message")]
    public string Message { get; set; }

    [JsonProperty(PropertyName = "status")]
    public string Status { get; set; }
}

or

public class MyResponse<T> : class T {
    public T Data { get; set; }

    public string Message { get; set; }

    public string Status { get; set; }
}

And then from there i can act on the Status / message within a generic method then return a json string back to the calling method in my library. From which the json string returned can be processed properly according to the request.

Any ideas how to deserialize data's child object back into a string or even better if i passed the method a generic type T how could i deserialize the json into both objects.

EDIT

Posted answer below for those looking to do something similar

Cheers

War es hilfreich?

Lösung

Thank you to those that offered some help, but I eventually came up with the answer I was looking for that would deserialize my object into a proper type offered via generics.

Here is my MyCustomResponse object

public class MyCustomResponse 
{
    [JsonProperty(PropertyName = "data")]   
    public object Data { get; set; }

    [JsonProperty(PropertyName = "message")]
    public string Message { get; set; }

    [JsonProperty(PropertyName = "result")]
    public string Result { get; set; }
}

The custom JsonConverter ended up like so, I look for the property in the json string "data" then convert it to an object of type T

public class MyCustomResponseConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(MyCustomResponse));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
        PropertyInfo[] props = objectType.GetProperties();

        JObject jo = JObject.Load(reader);
        foreach ( JProperty jp in jo.Properties() )
        {
            PropertyInfo prop = props.FirstOrDefault(pi =>
                pi.CanWrite && string.Equals(pi.Name, jp.Name, StringComparison.OrdinalIgnoreCase));

            if ( prop != null )
            {
                // Convert data object to what was passed in at T
                if ( jp.Name == "data" )
                    prop.SetValue(instance, jo.SelectToken("data").ToObject(typeof(T)));
                else
                    prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
            }
        }

        return instance;
    }

    public override bool CanWrite { get { return false; } }

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

To use the above I created a Generic method that looks like the following and allows me to pass the command to run, extra query string and also the Type to convert 'Data' object to:

private async Task<T> GenericApiRequestAsync<T>(string command, string query)
{
    HttpClient client = new HttpClient();

    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    Uri uri = new Uri(string.Format("{0}/api/{1}/?cmd={2}{3}", apiUrl, apiKey, command, query));

    try {   
        HttpResponseMessage response = await client.GetAsync(uri);                
        response.EnsureSuccessStatusCode();

        var responseContent = await response.Content.ReadAsStringAsync();

        // Convert responseContent via MyCustomResponseConverter
        var myCustomResponse = 
                await Task.Factory.StartNew(() =>
                                       JsonConvert.DeserializeObject<MyCustomResponse(
                                           responseContent, 
                                           new MyCustomResponseConverter<T>()
                                       ));

        return (T)myCustomResponse.Data;
    }
    catch(Exception ex)
    {
        ... 
    }
}

Then to use the actual GenericApiRequestAsync method i simply pass it the command, query and type for the Data object to be converted into, whatever it maybe.

public async Task<Person> GetPersonAsync(int id)
{
    return await GenericApiRequestAsync<Person>("person.byid", string.Format("&id={0}", id));
}

public async Task<IDictionary<string, ObjectType2>> GetObjectType2ListAsync(string name)
{
    return await GenericApiRequestAsync<IDictionary<string, ObjectType2>>("show.byname", string.Format("&name={0}", name));
}

Ended up a simple solution but complex in getting there. It removes the need to process the data object a second time into in the final object as well.

Hope this solution can help others out there that come across similar JSON structures, if anyone sees a simpler way to implement the converter, i'm happy to accept any input.

Cheers

Andere Tipps

For serialization/deserialization of JSON objects, take a look at Json.NET. You can include it as a Nuget package and use built in methods such as JsonConvert.SerializeObject(Object object) and JsonConvert.DeserializeObject(string value, Type type).

You can control the name of the JSON properties by decorating your models with JsonProperty attributes. For example:

public class MyResponse {
    [JsonProperty(PropertyName = "data")]
    public string Data { get; set; }

    [JsonProperty(PropertyName = "message")]
    public string Message { get; set; }

    [JsonProperty(PropertyName = "status")]
    public string Status { get; set; }
}

Two entities:

public class Response
{
    public Dictionary<string, Data> data { get; set; }
    public string message { get; set; }
    public string result { get; set; }
}

public class Data
{
    public int id { get; set; }
    public string title { get; set; }
}

The response code is:

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    Response response = (Response) serializer.Deserialize<Response>(jsonString);

As you can see there is no additional packages

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top