dynamically deserialize json into any object passed in. c# - c#

I'm trying to do is deserialize json into an object in c#. What I want to be able to do is pass any object get it's type and deserialize the json into that particular object using the JSON.Net library. Here are the lines of code.
Object someObject1 = someObject;
string result = await content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<someObject1.GetType()>(result);
The last line throws an exception of
operator '<' cannot be applied to operands of type 'method group'
How do I get the data type in the <> without c# complaining. What do I have to do to make this code work? And what knowledge am I missing?

JsonConvert.DeserializeObject<T> needs a compile-time type. You can't pass it a type in run time as you want to do in question (nothing different than declaring a List<T>). You should either deserialize to a generic json object JObject (or to dynamic) or you should create an instance of an object and fill it with json.
You can use the static method PopulateObject (of course if your object's properties match the json you want to deserialize).
JsonConvert.PopulateObject(result, someObject1 );

You can ignore the generic method and use dynamic:
var myObj = (dynamic)JsonConvert.DeserializeObject(result);
However, if the objects aren't of the same type you'll have a hard time distinguishing between the types and probably hit runtime errors.

For anyone bumping into this problem, the newer versions of Newtonsoft JSON have an overload that takes a type as a second argument and where you can pass a dynamic value without jumping through any hoops:
var myObj = JsonConvert.DeserializeObject(string serializedObject, Type deserializedType);

This is the best way to populate an object's fields given JSON data.
This code belongs in the object itself as a method.
public void PopulateFields(string jsonData)
{
var jsonGraph = JObject.Parse(jsonData);
foreach (var prop in this.GetType().GetProperties())
{
try
{
prop.SetValue(this, fields[prop.Name].ToObject(prop.PropertyType), null);
}
catch (Exception e)
{
// deal with the fact that the given
// json does not contain that property
}
}

Related

C# Object - get properties from json within the object

My HTTP response data includes a field that is structured as a Dictionary<string,object>. Accessing the Object in debug mode shows me what I expect to see, i.e.
ValueKind = Object:
{ "someProp" : "someValue",
"anotherProp" : "anotherValue" .. }
=> the object therefore contains the large json formatted data that I need to access. However, I can't figure out how to do that .. The commonly suggested method returns null:
var myValue = Odata.GetType().GetProperty("someProp").GetValue(Odata, null);
which makes me think there is something I should be serializing which I am not and regardless how much I tried, I can't find any methods to work.
Here is some more info, if it is not enough I will add whatever is needed. I broke it down a lot to try to show clearly what I am getting each step):
// assume Odata = the Object value from the dictionary
//perform reflection
var type = Odata.GetType() // this gives System.Text.json.JsonElement
//get type properties at runtime
var props = type.GetProperties()
//returns array of 2 :
//System.Text.Json.ValueKind and System.Text.json.JsonElement
//attempting to access my properties in the JsonElement
var someValue= type.GetProperty("someProp").GetValue(Odata,null) //this gives null
I don't necessarily want an exact solution, but pointing me to the right place to read would also be very useful!
When you do GetType().GetProperty() you're using the GetProperty() method from Type class, you're using reflection. You want to use the GetProperty method of JsonElement class instead:
var myValue = Odata.GetProperty("someProp").GetString();

Newtonsoft JsonConvert vs System.Web.Helpers Json - type cast problem

I've been using System.Web.Helpers.Json to deserialize objects quite successfully up until I received a json with the keys that only differ in case of the letters and the Decode() method throws an ArgumentException. I tried to figure out how to make this class work in case-sensitive way and couldn't, so I decided to go with Newtonsoft library instead. Json.NET works fine case-wise, however the deserialized objects it returns would require a type cast as follows:
[TestMethod]
public void CaseSensitivityTest() {
string json = "{\"e\":\"executionReport\",\"E\":1616877261436}";
dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
string executionReport = result.e;//have to assign to a typed variable for the assert below to work
Assert.AreEqual("executionReport", executionReport);
Assert.IsTrue(1616877261436 == (long)result.E);//or explicitly cast to a type
result = System.Web.Helpers.Json.Decode(json);//System.ArgumentException: An item with the same key has already been added.
Assert.IsTrue(1616877261436 == result.E);//this would've worked without any type cast as in the example below
}
The rest of my code relies heavily on deserialized objects having properly typed properties (e.g. my typical code decimal.Parse(deserializedResponse.price) expects price to be string and not JValue<string>). Here's another comparison:
[TestMethod]
public void TypeCastTest() {
string json = "{\"intValue\":123}";
dynamic webHelpersResult = System.Web.Helpers.Json.Decode(json);
dynamic newtonSoftResult = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
Assert.AreEqual(123, webHelpersResult.intValue);//All good here, I want JsonConvert to work the same way
Assert.AreEqual(123, newtonSoftResult.intValue);//Assert.AreEqual failed. Expected:<123 (System.Int32)>. Actual:<123 (Newtonsoft.Json.Linq.JValue)>.
}
It would be very difficult to refactor adding type casts everywhere, so I would prefer a single point of fix. I need either to make the System.Web.Helpers.Json case-sensitive or Newtonsoft.Json.JsonConvert to return .NET-typed values and not of JValue type. What would be the best way to achieve this? I'm writing a console application running on Windows 7 machine, so all fancy web/WINRT/Xamarin/etc stuff is not always available.
UPDATE
the suggestion of deserializing into ExpandoObject as below:
dynamic newtonSoftResult = JsonConvert.DeserializeObject<ExpandoObject>(json);
seems to work initially, however it fails to deserialize json lists and I couldn't make it backward compatible with System.Web.Helpers.Json.Decode() result:
string single = "{\"s\":\"String1\",\"f\":\"0.00\"}";
string multiple = "[{\"s\":\"String1\",\"f\":\"0.00\"},{\"s\":\"String2\",\"f\":\"1.23\"}]";
var helpersSingle = System.Web.Helpers.Json.Decode(single);
var helpersMultiple = System.Web.Helpers.Json.Decode(multiple);
var newtonSingle = Newtonsoft.Json.JsonConvert.DeserializeObject<ExpandoObject>(single);
var newtonMultiple = JsonConvert.DeserializeObject<ExpandoObject>(multiple);//System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.List`1[System.Object]' to type 'System.Dynamic.ExpandoObject'.
Assert.AreEqual("String1", helpersSingle.s);
Assert.AreEqual("String2", helpersMultiple[1].s);
Assert.IsFalse(helpersSingle is IEnumerable);
Assert.IsFalse(newtonSingle is IEnumerable);//This fails as well as ExpandoObject would implement IEnumerable for its properties
You can use Newtonsoft.Json with the following helper class:
public static class JsonHelper
{
public static object Deserialize(string json)
{
return ToObject(JToken.Parse(json));
}
private static object ToObject(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
var expando = new ExpandoObject() as IDictionary<string, object>;
foreach (JProperty prop in token.Children<JProperty>())
{
expando.Add(prop.Name, ToObject(prop.Value));
}
return expando;
case JTokenType.Array:
return token.Select(ToObject).ToList();
default:
return ((JValue)token).Value;
}
}
}
In your tests you can do:
dynamic result = JsonHelper.Deserialize(json);
The result will either be an ExpandoObject or a List<ExpandoObject> which should work with most of your tests. You will have to make an adjustment for tests that check for IEnumerable since ExpandoObject does implement this interface. If you need to differentiate between a single object or multiple, you could check for IList instead.
Working example here: https://dotnetfiddle.net/n2jI1d
Newtonsoft wraps the JSON Properties.. for various very good reasons I wont get into. You don't need to cast you can just use .Value i.e newtonSoft.intValue.Value. I should point out that you are still parsing the entire JSON string here using dynamic types because you don't use most of it isn't a good excuse to use dynamic typing. I would highly recommend you not use dynamic types but to each there own.
[TestMethod]
public void TypeCastTest() {
string json = "{\"intValue\":123}";
dynamic webHelpersResult = System.Web.Helpers.Json.Decode(json);
dynamic newtonSoftResult = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
Assert.AreEqual(123, webHelpersResult.intValue);
Assert.AreEqual(123, newtonSoftResult.intValue.Value);
}

Deserialize JSON by passing class as string parameter

Suppose I have this code:
public MessageClass(string _paramJson)
{
string[] Messages = new string[] { "MessageA", "MessageB" };
Messages[0] _model0 = JsonConvert.DeserializeObject<Messages[0]>(paramJson);
Messages[1] _model1 = JsonConvert.DeserializeObject<Messages[1]>(paramJson);
}
I know the above won't work, but All I want is to Deserialize the JSON based on a Type passed as string parameter. ie. MessageA, or MessageB.
Even if I define Messages as List<Type> and then do Messages.Add(typeof(MessageA)); for instance, how do I get the type later and passed it on to DeserializedObject<here-some-type-from-my-list>(paramJson) ?
Any help appreciated.
For this to work, you'd have to use reflection to convert MessageA and MessageB to a Type object, then use reflection to dynamically create an instance of the generic method for that type so you can invoke it.
For the first part, see the Type.GetType method documentation.
var type = Type.GetType("Namespace.Prefix.Message1, AssemblyName");
You can skip that second part by using the non-generic version of DeserializeObject:
var message = JsonConvert.DeserializeObject(paramJson, type);

dynamic casting Json doesn't create the same object as an explicit cast

I had this issue I was working through where I needed to deserialize my JSON into a dynamic type and I've got that worked out with this now,
var typeObject = System.Reflection.Assembly.GetExecutingAssembly().GetType("SalesForceEndpoints.Models.BaseAccount");
var accountToCreate = JsonConvert.DeserializeObject(body.Data.ToString(), typeObject);
var result = client.Create(accountToCreate); // This line fails because of the type
I can see that it successfully creates an object of my custom BaseAccount type. However when I am trying to pass it to the API I am using in Salesforce it fails. I tested explicitly casting to the type and it works fine like this,
var stronglyTypedAccountToCreate = JsonConvert.DeserializeObject<BaseAccount>(body.Data.ToString());
var result2 = client.Create(stronglyTypedAccountToCreate); // This succeeds
When looking at both of my objects in Visual Studio they appear to be the same at first glance,
And then I noticed this on my watch under the Type column,
The dynamically cast object is being listed as
object {SalesForceEndpoints.Models.BaseAccount}
and the explicitly cast object is being listed as
SalesForceEndpoints.Models.BaseAccount
I'm almost positive this is what is preventing me from executing my call succesfully, any ideas what is going wrong here and how I can execute the dynamic runtime cast to simulate an explicit compile time cast?
--- EDIT ---
I found out part of the issue. When I was calling into the Create function the create function was trying to extract the generic type and when it was dynamically cast it came through as object here instead of Account.
public IResponse<string> Create<T>(T obj) where T : new()
{
try
{
var typeName = typeof(T).Name.Replace("Base", "");
var result = _api.Create(typeName, obj);
return new SuccessResponse<string>(ResponseResult.Success, result);
}
catch (Exception ex)
{
return new FailureResponse<string>(ResponseResult.Failure, $"Unable to create record. | {ex.Message}");
}
}
Still a little curious if anyone knows why as to how a dynamic cast to an object comes through as object {ModelObject} rather than just ModelObject.
You should change it
var typeName = typeof(T).Name.Replace("Base", "");
to
var typeName = obj.GetType().Name.Replace("Base", "");
Because T will be evaluated as System.Object, you are not passing a known type to generic function and T stays with System.Object. If want to use generics, you should specify T in the compiler time.

C# Get generic non-array type from generic array type

Given the following function;
void SomeFunction<T>(...){
SomeOtherFunction<T>();
}
This works fine, but sometimes the function fails before T passed is an array type, but it mustn't be an array type. These functions have to do with JSON deserialization of a dictionary, but for some reason it doesn't accept the T array argument when the dictionary has only one entry.
In short, I want to do this
void SomeFunction<T>(...){
try {
SomeOtherFunction<T>();
} catch ( Exception e ){
SomeOtherFunction<T arrayless>();
}
}
I've tried a ton of stuff, and I realize the real problem is somewhere else, but I need to temporary fix this so I can work on a real solution in the deserializer. I tried reflection too using the following method;
MethodInfo method = typeof(JToken).GetMethod("ToObject", System.Type.EmptyTypes);
MethodInfo generic = method.MakeGenericMethod(typeof(T).GetElementType().GetGenericTypeDefinition());
object result = generic.Invoke(valueToken, null);
But that doesn't quite work either.
Thank you!
I am not really sure what you are trying to achieve here, but to get the type of the elements in an array, you have to use Type.GetElementType():
void SomeFunction<T>()
{
var type = typeof(T);
if(type.IsArray)
{
var elementType = type.GetElementType();
var method = typeof(Foo).GetMethod("SomeOtherFunction")
.MakeGenericMethod(elementType);
// invoke method
}
else
foo.SomeOtherFunction<T>(...);
}
If I follow you correctly, you want to call one of two generic functions depending on whether the type of the object is an array or not.
How about:
if (typeof(T).ImplementsInterface(typeof(IEnumerable)))
someFunction<T>();
else
someOtherFunction<T>();

Categories