JSON to C#, deserialize property that is either object or array - c#

I have an issue with handling JSON data from an api that I use in my application. The problem is that the JSON contains some properties that are an object when there is an item, and become an array when there are more items. So that's a structure like this:
[
{
"MyObj": {
"Foo": "Bar"
}
},
{
"MyObj": [
{
"Foo": "Bar1"
},
{
"Foo": "Bar2"
}
]
}
]
I've tried several JSON to C# converters, some of them generate a property of type object, the quicktype.io converter generates this:
public class Example
{
[JsonProperty("MyObj")]
public MyObjUnion MyObj { get; set; }
}
public partial class MyObjElement
{
[JsonProperty("Foo")]
public string Foo { get; set; }
}
public struct MyObjUnion
{
public MyObjElement MyObjElement;
public MyObjElement[] MyObjElementArray;
public static implicit operator MyObjUnion(MyObjElement MyObjElement) => new MyObjUnion { MyObjElement = MyObjElement };
public static implicit operator MyObjUnion(MyObjElement[] MyObjElementArray) => new MyObjUnion { MyObjElementArray = MyObjElementArray };
}
internal class MyObjUnionConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(MyObjUnion) || t == typeof(MyObjUnion?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
var objectValue = serializer.Deserialize<MyObjElement>(reader);
return new MyObjUnion { MyObjElement = objectValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<MyObjElement[]>(reader);
return new MyObjUnion { MyObjElementArray = arrayValue };
}
throw new Exception("Cannot unmarshal type MyObjUnion");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (MyObjUnion)untypedValue;
if (value.MyObjElementArray != null)
{
serializer.Serialize(writer, value.MyObjElementArray);
return;
}
if (value.MyObjElement != null)
{
serializer.Serialize(writer, value.MyObjElement);
return;
}
throw new Exception("Cannot marshal type MyObjUnion");
}
public static readonly MyObjUnionConverter Singleton = new MyObjUnionConverter();
}
Although this does work correctly, it's still a bit cumbersome because to get the data you always need to check if it's in the MyObjElement or MyObjElementArray class.
So the question is if there are other, more elegant ways to solve this issue. (Other than changing the API output, which is not mine)

If you know the data structure before hand, I would create a custom data model for the Json file and then deserialise it like so:
CurrencyExchangeRates deserialisedData = JsonConvert.DeserializeObject<CurrencyExchangeRates>(savedData);
foreach (CurrentRate value in deserialisedData.ExchangeRates)
{
rate.ExchangeRates.Add(new CurrentRate { Rate = value.Rate, Timestamp = value.Timestamp });
}
This is how I did that in an application. Hope this helps a little.

You can check and cast each object in your JSON depending the type of object.
For example consider using Newtonsoft.Json library for JSON parsing, you can do the following:
// considering your JSON string
string jsonString = "[{'MyObj':{'Foo':'Bar'}},{'MyObj':[{'Foo':'Bar1'},{'Foo':'Bar2'}]}]";
// parse your JSON into JTokens
var tokens = JToken.Parse(jsonString);
// iterate through all the tokens
foreach (var token in tokens)
{
// your token is a grand child
JToken myObj = token.First.First;
// check if the grand child is array
if (myObj is JArray)
{
// cast the grand child token into MyObj list or array object
IEnumerable<MyObj> objList = myObj.ToObject<List<MyObj>>();
Console.WriteLine("converted to MyObj Array");
}
else if (myObj is JObject) // else if its a non array item
{
// cast the grand child token into MyObj object
MyObj obj = myObj.ToObject<MyObj>();
Console.WriteLine("converted to MyObj");
}
}
// your MyObj Type will look like this:
public class MyObj
{
public string Foo { get; set; }
}

Related

Deserializing JSON into a C# class with dynamic field names

My question is very similar to this one. I tried adapting their solution to fit my needs but can't seem to figure out the solution for my JSON.
Here is an example of the JSON:
{
"0fea8f8a-4169-495d-8307-50bc333fd87d": {
"serviceId": "4cb9125a-1eaa-4bd4-a473-cfccec0f3c63"
},
"0564d078-94f5-4f97-8398-b9f58a51f70b": {
"serviceId": "00000000-0000-0000-0000-000000000000"
},
"f9a165d2-967d-4733-8599-1074270dae2e": {
"serviceId": "00000000-0000-0000-0000-000000000000"
},
"86ccdsbf-e7ad-4851-93ff-6ec817469c1e": {
"serviceId": "00000000-0000-0000-0000-000000000000"
}
}
As you can see, it is a series (not an array) of
Id_1 : {serviceId: Id_2}
I think this can most simply be represented in a C# class as something like this: List<KeyValuePair<string, string>>, basically a List of <Id_1, Id_2>, but I'm open to alternatives.
Here is my attempt at a solution based on the linked post above:
class PolicyMetadata
{
[JsonConverter(typeof(MetadataConverter))]
public KeyValuePair<string,string>[] idPairs { get; set; }
}
class MetadataConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// deserialize as object
var pairs = serializer.Deserialize<JObject>(reader);
var result = new List<KeyValuePair<string, string>>();
// create an array out of the properties
foreach (JProperty property in pairs.Properties())
{
var pair = property.Value.ToObject<KeyValuePair<string, string>>();
result.Add(pair);
}
return result.ToArray();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(KeyValuePair<string, string>[]);
}
}
And I call it here:
var response = JsonConvert.DeserializeObject<PolicyMetadata>(content);
But this resuts in a null value, so I am definitely doing something wrong. I tried placing breakpoints and print statements throughout the ReadJson method, but they were never triggered, so I'm not sure if that code is actually running.
Model:
public class Service
{
public string ServiceId { get; set; }
}
Use dictionary:
static void Main()
{
string json = #"
{
""0fea8f8a-4169-495d-8307-50bc333fd87d"": {
""serviceId"": ""4cb9125a-1eaa-4bd4-a473-cfccec0f3c63""
},
""0564d078-94f5-4f97-8398-b9f58a51f70b"": {
""serviceId"": ""00000000-0000-0000-0000-000000000000""
},
""f9a165d2-967d-4733-8599-1074270dae2e"": {
""serviceId"": ""00000000-0000-0000-0000-000000000000""
},
""86ccdsbf-e7ad-4851-93ff-6ec817469c1e"": {
""serviceId"": ""00000000-0000-0000-0000-000000000000""
}
}";
var result = JsonConvert.DeserializeObject<Dictionary<string, Service>>(json);
foreach (var pair in result)
{
Console.WriteLine("Key: " + pair.Key + " ServiceId: " + pair.Value.ServiceId);
}
}

Deserialize an Anonymous Type From a Collection

I have my serialized JSON in this format:
string json = #"[{"Name": "std_id","Value": "111"}, {"Name": "cust_id","Value": "444"}]"
How do I deserialize it to a single anonymous object like this:
var paramObj = new {"std_id" = 111, "cust_id" = 444}
Since you said the values of the Name and Value properties in your JSON objects can vary, you will not be able to deserialize to an anonymous object. Anonymous types are defined at compile-time, which means you need to know the property names ahead of time to be able to define them. The only way to get around that is code generation, which I think is going to be overkill for this situation. Instead, I would suggest you deserialize into a JObject with a dynamic variable. This will get you pretty close to what you want. Here's how:
string json = #"[
{ ""Name"": ""std_id"", ""Value"": ""111"" },
{ ""Name"": ""cust_id"", ""Value"": ""444"" }
]";
dynamic obj = new JObject(JArray.Parse(json)
.Select(t => new JProperty((string)t["Name"], t["Value"])));
From there, you can access the properties like you would for an anonymous type (assuming you know what they are):
Console.WriteLine(obj.std_id);
Console.WriteLine(obj.cust_id);
If you don't know what the properties are, you can enumerate them like a dictionary:
foreach (var prop in obj)
{
Console.WriteLine(prop.Name + ": " + prop.Value);
}
Fiddle: https://dotnetfiddle.net/MRY2ny
Why anonymous object? you should deserialize to a type like below
public class RootObject
{
public string Name { get; set; }
public string Value { get; set; }
}
Then what you actually have is IEnumerable<RootObjct>. You can use use Linq and select First() from it like
RootObject = RootObjects.FirstOrDefault()
You could deserialize it into a dynamic. Like this:
var serializer = new JavaScriptSerializer();
var deserializedResult = serializer.Deserialize<dynamic>(json);
Reference:
JavaScriptSerializer Class
var definition = new { Name = "" };
string json1 = #"{'Name':'James'}";
var customer1 = JsonConvert.DeserializeAnonymousType(json1, definition);
Console.WriteLine(customer1.Name);
string json2 = #"{'Name':'Mike'}";
var customer2 = JsonConvert.DeserializeAnonymousType(json2, definition);
Console.WriteLine(customer2.Name);
source http://www.newtonsoft.com/json/help/html/DeserializeAnonymousType.htm
I know that this solution isn't perfect, but it works for the example provided by you and returns result that looks like paramObj in your example.
The idea is to create a custom Json converter.
First, let's create a DTO class to present a name-value item of incomming JSON.
public class NameValueJsonItem
{
public string Name { get; set; }
public string Value { get; set; }
}
Converter implementation:
public class DynamicJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token == null || token.Type != JTokenType.Array)
{
return null;
}
List<NameValueJsonItem> parsedJson = token.ToObject<List<NameValueJsonItem>>();
ExpandoObject result = new ExpandoObject();
foreach (NameValueJsonItem item in parsedJson)
{
if (!String.IsNullOrEmpty(item.Name))
{
(result as IDictionary<string, object>)[item.Name] = item.Value;
}
}
return result;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Sure, you can make it more safe by adding some exceptions handling etc inside the method if you want.
You can use this converter like that:
dynamic result = JsonConvert.DeserializeObject<dynamic>(json, new DynamicJsonConverter());
Hope it will help.

Is there a way to use Json.Net to deserialize some json which calls a method to add the items to a collection (there is no property setter)

I have a class that has a collection which is readonly and has no setter.
Is there a way to de-serialize some json and get it call a method on the instance instead of the property setter?
For example:
public class User
{
private ObservableCollection<Movie> _movies;
public string Name { get; set; }
public ReadOnlyCollection<Movie> FavouriteMovies { get; set; }
public void AddMovie(Movie movie) { .. }
//-or-
public void AddMovies(IEnumerable<Movie> movies){ .. }
}
The only way to get things into the _movies backing field is via the method AddMovies. So when trying to deserialize some valid json which has an array of Movies in the json, it will call AddMovie or AddMovies...
Ninja update by PK:
I've forked the fiddle below using a collection of classes instead of simple strings, for a more complex example that now works, based on the answer below.
Use a JsonConverter to do custom conversion of your json. Here's a simple example of how that might work. Given a class like this:
public class MyClass
{
private List<string> backingField;
public string Name { get; set; }
public IReadOnlyCollection<string> MyStrings { get; private set; }
public MyClass()
{
backingField = new List<string>();
MyStrings = backingField.AsReadOnly();
}
public void AddString(string item)
{
backingField.Add(item);
}
}
And JSON like this:
{
"MyStrings": [
"Foo",
"Bar"
],
"Name":"My stuff"
}
You could create a converter to read that json and call AddString to populate the backingField like this:
public class MyClassConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = new MyClass();
var jObj = JObject.Load(reader);
JsonConvert.PopulateObject(jObj.ToString(), obj); // populate fields we don't need any special handling for
var stringsProp = jObj["MyStrings"];
if (stringsProp != null)
{
var strings = stringsProp.ToObject<List<string>>();
foreach (var s in strings)
{
obj.AddString(s);
}
}
return obj;
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(MyClass);
}
}
Now to use it, and keep MyClass ignorant of how it get deserialized, you can simply do something like this:
var m = JsonConvert.DeserializeObject(json, typeof(MyClass), new MyClassConverter()) as MyClass;
Here's a fiddle

Custom JsonConverter WriteJson Does Not Alter Serialization of Sub-properties

I always had the impression that the JSON serializer actually traverses your entire object's tree, and executes the custom JsonConverter's WriteJson function on each interface-typed object that it comes across - not so.
I have the following classes and interfaces:
public interface IAnimal
{
string Name { get; set; }
string Speak();
List<IAnimal> Children { get; set; }
}
public class Cat : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Cat()
{
Children = new List<IAnimal>();
}
public Cat(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Meow";
}
}
public class Dog : IAnimal
{
public string Name { get; set; }
public List<IAnimal> Children { get; set; }
public Dog()
{
Children = new List<IAnimal>();
}
public Dog(string name="") : this()
{
Name = name;
}
public string Speak()
{
return "Arf";
}
}
To avoid the $type property in the JSON, I've written a custom JsonConverter class, whose WriteJson is
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
IAnimal animal = value as IAnimal;
JObject o = (JObject)t;
if (animal != null)
{
if (animal is Dog)
{
o.AddFirst(new JProperty("type", "Dog"));
//o.Find
}
else if (animal is Cat)
{
o.AddFirst(new JProperty("type", "Cat"));
}
foreach(IAnimal childAnimal in animal.Children)
{
// ???
}
o.WriteTo(writer);
}
}
}
In this example, yes, a dog can have cats for children and vice-versa. In the converter, I want to insert the "type" property so that it saves that to the serialization. I have the following setup. (Zoo has only a name and a list of IAnimals. I didn't include it here for brevity and laziness ;))
Zoo hardcodedZoo = new Zoo()
{ Name = "My Zoo",
Animals = new List<IAnimal> { new Dog("Ruff"), new Cat("Cleo"),
new Dog("Rover"){
Children = new List<IAnimal>{ new Dog("Fido"), new Dog("Fluffy")}
} }
};
JsonSerializerSettings settings = new JsonSerializerSettings(){
ContractResolver = new CamelCasePropertyNamesContractResolver() ,
Formatting = Formatting.Indented
};
settings.Converters.Add(new AnimalsConverter());
string serializedHardCodedZoo = JsonConvert.SerializeObject(hardcodedZoo, settings);
serializedHardCodedZoo has the following output after serialization:
{
"name": "My Zoo",
"animals": [
{
"type": "Dog",
"Name": "Ruff",
"Children": []
},
{
"type": "Cat",
"Name": "Cleo",
"Children": []
},
{
"type": "Dog",
"Name": "Rover",
"Children": [
{
"Name": "Fido",
"Children": []
},
{
"Name": "Fluffy",
"Children": []
}
]
}
]
}
The type property shows up on Ruff, Cleo, and Rover, but not for Fido and Fluffy. I guess the WriteJson isn't called recursively. How do I get that type property there?
As an aside, why does it not camel-case IAnimals like I expect it to?
The reason that your converter is not getting applied to your child objects is because JToken.FromObject() uses a new instance of the serializer internally, which does not know about your converter. There is an overload that allows you to pass in the serializer, but if you do so here you will have another problem: since you are inside a converter and you are using JToken.FromObject() to try to serialize the parent object, you will get into an infinite recursive loop. (JToken.FromObject() calls the serializer, which calls your converter, which calls JToken.FromObject(), etc.)
To get around this problem, you must handle the parent object manually. You can do this without much trouble using a bit of reflection to enumerate the parent properties:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
Type type = value.GetType();
jo.Add("type", type.Name);
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value, null);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
jo.WriteTo(writer);
}
Fiddle: https://dotnetfiddle.net/sVWsE4
Here's an idea, instead of doing the reflection on every property, iterate through the normally serialized JObject and then changed the token of properties you're interested in.
That way you can still leverage all the ''JsonIgnore'' attributes and other attractive features built-in.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken jToken = JToken.FromObject(value);
if (jToken.Type == JTokenType.Object)
{
JObject jObject = (JObject)jToken;
...
AddRemoveSerializedProperties(jObject, val);
...
}
...
}
And then
private void AddRemoveSerializedProperties(JObject jObject, MahMan baseContract)
{
jObject.AddFirst(....);
foreach (KeyValuePair<string, JToken> propertyJToken in jObject)
{
if (propertyJToken.Value.Type != JTokenType.Object)
continue;
JToken nestedJObject = propertyJToken.Value;
PropertyInfo clrProperty = baseContract.GetType().GetProperty(propertyJToken.Key);
MahMan nestedObjectValue = clrProperty.GetValue(baseContract) as MahMan;
if(nestedObj != null)
AddRemoveSerializedProperties((JObject)nestedJObject, nestedObjectValue);
}
}
I had this issue using two custom converters for a parent and child type. A simpler method I found is that since an overload of JToken.FromObject() takes a serializer as a parameter, you can pass along the serializer you were given in WriteJson(). However you need to remove your converter from the serializer to avoid a recursive call to it (but add it back in after):
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Converters.Remove(this);
JToken jToken = JToken.FromObject(value, serializer);
serializer.Converters.Add(this);
// Perform any necessary conversions on the object returned
}
Here is a hacky solution to your problem that gets the work done and looks tidy.
public class MyJsonConverter : JsonConverter
{
public const string TypePropertyName = "type";
private bool _dormant = false;
/// <summary>
/// A hack is involved:
/// " JToken.FromObject(value, serializer); " creates amn infinite loop in normal circumstances
/// for that reason before calling it "_dormant = true;" is called.
/// the result is that this JsonConverter will reply false to exactly one "CanConvert()" call.
/// this gap will allow to generate a a basic version without any extra properties, and then add them on the call with " JToken.FromObject(value, serializer); ".
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
_dormant = true;
JToken t = JToken.FromObject(value, serializer);
if (t.Type == JTokenType.Object && value is IContent)
{
JObject o = (JObject)t;
o.AddFirst(new JProperty(TypePropertyName, value.GetType().Name));
o.WriteTo(writer);
}
else
{
t.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
if (_dormant)
{
_dormant = false;
return false;
}
return true;
}
}

How to serialize a collection (with an indexer property) as a dictionary

I have a class, FooCollection let's say, which implements IEnumerable<Foo>, and also provides an indexer by which one can look up a Foo by its name. Functionally it's read-only as a dictionary. But it's not really a dictionary because users do not get to decide on the keys.
Anyway, I want JSON.NET to serialize this object as a JSON dictionary, instead of as an array, which it's doing now. Sticking JsonDictionaryAttribute on it doesn't work: then it does nothing.
Clues?
You're probably going to need to create a custom JsonConverter for your FooCollection class in order to serialize it the way you want. Since you haven't posted any code at all, I'll just make something up for sake of example. Let's say your Foo and FooCollection classes look like this:
class Foo
{
public int Id { get; set; }
public string Name { get; set; }
}
class FooCollection : IEnumerable<Foo>
{
private List<Foo> list = new List<Foo>();
public void Add(Foo foo)
{
list.Add(foo);
}
public Foo this[string name]
{
get { return list.Find(f => f.Name == name); }
}
public IEnumerator<Foo> GetEnumerator()
{
return list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)list).GetEnumerator();
}
}
The following converter would serialize the FooCollection as if it were a dictionary. I'm assuming that you would want the converter to use the value of the Name property as the key for each Foo for purposes of serialization (to match the indexer on the collection), so that is how I implemented it. You can change it to something else by modifying the GetFooKey() method.
class FooCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(FooCollection));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (Foo foo in (FooCollection)value)
{
writer.WritePropertyName(GetFooKey(foo));
serializer.Serialize(writer, foo);
}
writer.WriteEndObject();
}
// Given a Foo, return its unique key to be used during serialization
private string GetFooKey(Foo foo)
{
return foo.Name;
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is an example program that demonstrates how to use the converter.
class Program
{
static void Main(string[] args)
{
FooCollection coll = new FooCollection();
coll.Add(new Foo { Id = 1, Name = "Moe" });
coll.Add(new Foo { Id = 2, Name = "Larry" });
coll.Add(new Foo { Id = 3, Name = "Curly" });
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new FooCollectionConverter());
settings.Formatting = Newtonsoft.Json.Formatting.Indented;
string json = JsonConvert.SerializeObject(coll, settings);
Console.WriteLine(json);
}
}
And here is the output of the above program:
{
"Moe": {
"Id": 1,
"Name": "Moe"
},
"Larry": {
"Id": 2,
"Name": "Larry"
},
"Curly": {
"Id": 3,
"Name": "Curly"
}
}
Fiddle: https://dotnetfiddle.net/wI2iQG

Categories

Resources