Search code examples
c#json-deserializationsystem.reflection

Using reflection on object created from json


I have found people asking similar questions but couldn't quite find the solution from the answers to those.

I have the following string: "{"message":"Validation failed. 1 error found.","errorCode":"E04300","developerHint":"Inspect validation errors and correct your request.","logId":"7612fd90f484abda-CPH","httpStatusCode":400,"errors":{"customerNumber":{"errors":[{"propertyName":"customerNumber","errorMessage":"Customer number exists","errorCode":"E06010","inputValue":29926638,"developerHint":"Customer number 29926638 already exists"}]}},"logTime":"2022-10-28T12:29:27","errorCount":1}"

I deserialize it to an object:

var obj = JsonConvert.DeserializeObject(json)

When I call obj.Dump() in LinqPad I get this: enter image description here

So obviously the data is there in my object. Only I can't find it using obj.GetType().GetProperty("message").GetValue(obj, null)

Any ideas how to get at the data in this object?

I have tried

obj.GetType().GetCustomAttributes();
obj.GetType().GetFields();
obj.GetType().GetMembers();
obj.GetType().GetProperties();

can't seem to find anything.

I am aware that the obvious answer would be to just create the class that fits the data and deserialize to that class, but I am specifically trying to avoid that.


Solution

  • You need to be getting the JProperty or the KeyValuePair<string, JToken> from it

    In either scenario, you get the data from it via its Value property.

    Use of .GetType() is not going to work, as that won't be accessing the internal collection that actually houses the data you want. As mentioned previously a JObject is a niche wrapper around what you can imagine as an internal dictionary

    This being said, if you know the structure of the JSON file upfront, deserialize it to a class object as you can leverage a lot of great features from the library, including annotations to finely control deserialization, full use of reflection on those objects, etc.

    Deserializing to a JObject is useful when your JSON structure can be variable, with the drawback of needing to know the exact field "keys" to access. If you don't, and have to always fallback on looping through the JObject, just know you are introducing that extra sacrifice of time and space complexity that come with loops. Its not the end of the world, but unnecessary under most circumstances as no one likes unpredicable JSON structure and typically will not create an API that does something like that

    Update, may deserialize with generics

    It may take some setup but for example:

    1. setup an interface that all of these subclasses will implement, even if the interface is completely blank
    2. define the classes for each of these error objects that compose the error responses
    3. Create a "root" class that the response will deserialize to and have it take a generic parameter. Constrain the generic to the interface you created. Now within this root class, specify a property of type T
    public interface IAllErrorObjs
    {
        //...
    }
    
    public class ErrorOne : IAllErrorObjs 
    { 
        //... 
    }
    
    public class ErrorTwo : IAllErrorObjs 
    { 
        //... 
    }
    
    public class ResponseRoot<T> where T : IAllErrorObjs
    {
        public T ErrorObj { get; set; } //T can be ErrorOne, ErrorTwo, etc
    }
    
    1. Create your Task to hit the API, and deserialize using this setup, return the specific object you want from it. Again take notice of the use of generic constraints here
    private static readonly HttpClient _client = new HttpClient();
    
    public async Task<T> MakeTheRequestAsync<T>() where T : IAllErrorObjs
    {
        string requestUrl = "url here";
    
        HttpRequestMessage request = new HttpRequestMessage
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(requestUrl)
        };
    
        using (HttpResponseMessage response = await _client.SendAsync(request).ConfigureAwait(false))
        {
            if (!response.IsSuccessStatusCode) throw new Exception($"Request failed: {response.StatusCode}");
    
            string body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            ResponseRoot<T> rsp = JsonConvert.DeserializeObject<ResponseRoot<T>>(body);
    
            return rsp.ErrorObj;
        }
    }
    

    Type constraints is one of the major powerhouses here, because it allows you to deserialize to a class that simply contains the specific generic class you want. Its also extensible as any new error subclasses will simply have to implement the constraining interface. Its "dynamic" enough without sacrificing type safety

    You can think of it as a sort of wrapping and unwrapping a generic in order to allow for polymorphic deserialization