Here's an example of raw JSON data:
{ "Standards": { "1": "1" } }
I want to deserialize the data to:
public class Model
{
public HashSet<String> Standards { get; set; }
}
The Standards field actually has the Dictionary<String, String> type. Somehow the keys and values are always equal. As the types are incompatible, I'm looking for a way to perform a custom deserialization of this field.
A solution based on JSON.NET library is preferred.
P.S.: I have no control over the data serialization process.
You can handle this with a custom JsonConverter. Here is the code you would need:
public class CustomHashSetConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HashSet<string>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new HashSet<string>(jo.Properties().Select(p => p.Name));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
HashSet<string> hashSet = (HashSet<string>)value;
JObject jo = new JObject(hashSet.Select(s => new JProperty(s, s)));
jo.WriteTo(writer);
}
}
To use the converter, add a [JsonConverter] attribute to your model like this:
public class Model
{
[JsonConverter(typeof(CustomHashSetConverter))]
public HashSet<string> Standards { get; set; }
}
Then, just deserialize as normal and it should work:
Model model = JsonConvert.DeserializeObject<Model>(json);
Here is a round-trip demo: https://dotnetfiddle.net/tvHt5Y
Related
I am working on a C# project.
[
{"FirstName":"XYZ","LastName":"SSS"},
{"FirstName":"ABC","LastName":"NNN"}
]
Each row represents an object of class ChildDTO.
I read the above data from the file and am trying to deserialize into a ParentCollection object like below:
string file = System.IO.File.ReadAllText("Children_CA.txt");
ParentCollection pCollection = Newtonsoft.Json.JsonConvert.DeserializeObject<ParentCollection>(file);
My DTO classes are like below:
[Serializable]
public class ParentCollection : CollectionBase
{
public void Add(ChildDTO dto)
{
//List is from Systems.Collections.CollectionBase class
List.Add(dto);
}
}
[Serializable]
public class ChildDTO
{
// properties like FirstName and LastName goes here
}
I cannot change my DTO classes since they are old and already in production from the past 20 years and many applications are using them.
When I see Quick Watch on pCollection, I notice the collection is having objects of type Newtonsoft.Json.Linq.JObject. I am hoping to have objects of type ChildDTO
Please let me know what mistake I am doing.
The problem is that CollectionBase, and by extension your ParentCollection class, are not generic, so Json.Net doesn't know that the items in the collection are supposed to be ChildDTO objects. Since it doesn't know what type the items are, it just uses JObject instead. You can fix the problem using a custom JsonConverter class like this:
class ParentCollectionConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParentCollection);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
ParentCollection pc = new ParentCollection();
JArray array = JArray.Load(reader);
foreach (var item in array)
{
pc.Add(item.ToObject<ChildDTO>());
}
return pc;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, pass it to DeserializeObject<T> like this:
ParentCollection pCollection =
JsonConvert.DeserializeObject<ParentCollection>(json, new ParentCollectionConverter());
Working demo here: https://dotnetfiddle.net/3GM22c
Good morning. If you need the ChildDto's list, please convert it to List. Please refer to the code and image below. Many thanks.
string file = System.IO.File.ReadAllText("Children_CA.txt");
var results1 = JsonConvert.DeserializeObject<List<ChildDTO>>(file);
Temporary note: This is NOT a duplicate of the above mentioned post
Let's say I have a server-side class structure like this.
public class Test
{
// this can be any kind of "Tag"
public object Data { get; set; }
}
public class Other
{
public string Test { get; set; }
}
Now a string like this is coming from let's say the client.
{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }
When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.
I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.
Here is my current deserialization code.
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.
I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.
This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.
public class UntypedListJsonConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
{
return serializer.Deserialize(reader);
}
return serializer.Deserialize<List<object>>(reader);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
}
Usage example:
var settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new[] { new UntypedListJsonConverter() }
};
var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();
Try this:
public class Test
{
public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}
You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.
Let's say I've the following dynamic object:
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
}
If I serialize it using JsonConvert.SerializeObject(new SomeDynamicObject { Text = "hello world" }) it'll return {} instead of { "Text": "hello world" }.
I suspect the issue is that JSON.NET thinks it's a full dynamic object while my case is a dynamic object with declared members.
Is there any serialization settings or built-in converter that could be configured so JSON.NET can serialize both kinds of members?
To avoid confusion
Actual use case: I don't know which will be the types being serialized but I need to cover the whole use case of serializing declared properties of a dynamic object.
That is, I can't use attributes. That's why I'm asking if there's some converter or a serialization setting that can generalize this use case.
Update for non-attribute converter
Since you can't decorate, you lose a lot of power. Once the JsonWriter has converted to a JObject, the dynamic properties appear to be lost.
However, you can always use a little reflection in a custom converter's WriteJson method to serialize non-dynamic types.
public class SomeDynamicObject : DynamicObject
{
public string Text { get; set; }
public DynamicObject DynamicProperty { get; set; }
}
public class CustomDynamicConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
JObject jObject = JObject.Load(reader);
var target = Activator.CreateInstance(objectType);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var properties = value.GetType().GetProperties().Where(x => x.PropertyType != typeof(DynamicObject)).ToList();
JObject o = (JObject)JToken.FromObject(value);
properties.ForEach(x =>
{
o.AddFirst(new JProperty(x.Name, x.GetValue(value)));
});
o.WriteTo(writer);
}
}
If you explicitly decorate your properties with [JsonProperty], the serializer will pick them up, even if the containing type is dynamic.
public class SomeDynamicObject : DynamicObject
{
[JsonProperty]
public string Text { get; set; }
}
when serialized correctly outputs:
{"Text":"hello world"}
I have some bad data coming back from a web service which I cannot change. The service returns a JSON list of customers. Inside this list, each customer also has a list of jobs. But the JSON coming back is a string for the jobs.
So: Jobs: "[]" Instead of Jobs: []
So I defined the class as
[JsonProperty(PropertyName = "JOBS", ItemConverterType = typeof(StringToJobsConverter))]
public List<JobClass> Jobs { get; set; }
I created the class, and created the conversion method inside it as follows:
return JsonConvert.DeserializeObject<List<JobClass>>(existingValue.ToString());
No luck. The error retuned is Could not cast or convert from System.String to System.Collections.Generic.List`1[AppNamespace.JobClass].
Breakpoints in the converter code are never hit. Can anyone see what I'm doing wrong?
UPDATE
I found the issue but don't know how to fix. The converter is being applied to the JobClass inside the list. Not to the List itself. I want the converter applied one time only to the List deserialization. Instead it's applied to each JobClass record inside the list.
string json = #"{str1:""abc"",list:""[1,2,3]"", str2:""def""}";
var temp = JsonConvert.DeserializeObject<Temp>(json);
public class Temp
{
public string str1;
[JsonConverter(typeof(StringConverter<List<int>>))]
public List<int> list;
public string str2;
}
public class StringConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<T>((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
I'm having a bit of trouble deserializing data returned from Facebook using the JSON.NET libraries.
The JSON returned from just a simple wall post looks like:
{
"attachment":{"description":""},
"permalink":"http://www.facebook.com/permalink.php?story_fbid=123456789"
}
The JSON returned for a photo looks like:
"attachment":{
"media":[
{
"href":"http://www.facebook.com/photo.php?fbid=12345",
"alt":"",
"type":"photo",
"src":"http://photos-b.ak.fbcdn.net/hphotos-ak-ash1/12345_s.jpg",
"photo":{"aid":"1234","pid":"1234","fbid":"1234","owner":"1234","index":"12","width":"720","height":"482"}}
],
Everything works great and I have no problems. I've now come across a simple wall post from a mobile client with the following JSON, and deserialization now fails with this one single post:
"attachment":
{
"media":{},
"name":"",
"caption":"",
"description":"",
"properties":{},
"icon":"http://www.facebook.com/images/icons/mobile_app.gif",
"fb_object_type":""
},
"permalink":"http://www.facebook.com/1234"
Here is the class I am deserializing as:
public class FacebookAttachment
{
public string Name { get; set; }
public string Description { get; set; }
public string Href { get; set; }
public FacebookPostType Fb_Object_Type { get; set; }
public string Fb_Object_Id { get; set; }
[JsonConverter(typeof(FacebookMediaJsonConverter))]
public List<FacebookMedia> { get; set; }
public string Permalink { get; set; }
}
Without using the FacebookMediaJsonConverter, I get an error: Cannot deserialize JSON object into type 'System.Collections.Generic.List`1[FacebookMedia]'.
which makes sense, since in the JSON, Media is not a collection.
I found this post which describes a similar problem, so I've attempted to go down this route: Deserialize JSON, sometimes value is an array, sometimes "" (blank string)
My converter looks like:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return serializer.Deserialize<List<FacebookMedia>>(reader);
else
return null;
}
Which works fine, except I now get a new exception:
Inside JsonSerializerInternalReader.cs, CreateValueInternal(): Unexpected token while deserializing object: PropertyName
The value of reader.Value is "permalink". I can clearly see in the switch that there's no case for JsonToken.PropertyName.
Is there something I need to do differently in my converter? Thanks for any help.
A very detailed explanation on how to handle this case is available at "Using a Custom JsonConverter to fix bad JSON results".
To summarize, you can extend the default JSON.NET converter doing
Annotate the property with the issue
[JsonConverter(typeof(SingleValueArrayConverter<OrderItem>))]
public List<OrderItem> items;
Extend the converter to return a list of your desired type even for a single object
public class SingleValueArrayConverter<T> : 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)
{
object retVal = new Object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T));
retVal = new List<T>() { instance };
} else if (reader.TokenType == JsonToken.StartArray) {
retVal = serializer.Deserialize(reader, objectType);
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
As mentioned in the article this extension is not completely general but it works if you are fine with getting a list.
The developer of JSON.NET ended up helping on the projects codeplex site. Here is the solution:
The problem was, when it was a JSON object, I wasn't reading past the attribute. Here is the correct code:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<FacebookMedia>>(reader);
}
else
{
FacebookMedia media = serializer.Deserialize<FacebookMedia>(reader);
return new List<FacebookMedia>(new[] {media});
}
}
James was also kind enough to provide unit tests for the above method.
Based on Camilo Martinez's answer above, this is a more modern, type-safe, leaner and complete approach using the generic version of JsonConverter and C# 8.0 as well as implementing the serialization part. It also throws an exception for tokens other than the two expected according to the question. Code should never do more than required otherwise you run the risk of causing a future bug due to mishandling unexpected data.
internal class SingleObjectOrArrayJsonConverter<T> : JsonConverter<ICollection<T>> where T : class, new()
{
public override void WriteJson(JsonWriter writer, ICollection<T> value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.Count == 1 ? (object)value.Single() : value);
}
public override ICollection<T> ReadJson(JsonReader reader, Type objectType, ICollection<T> existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return reader.TokenType switch
{
JsonToken.StartObject => new Collection<T> {serializer.Deserialize<T>(reader)},
JsonToken.StartArray => serializer.Deserialize<ICollection<T>>(reader),
_ => throw new ArgumentOutOfRangeException($"Converter does not support JSON token type {reader.TokenType}.")
};
}
}
And then decorate the property thus:
[JsonConverter(typeof(SingleObjectOrArrayJsonConverter<OrderItem>))]
public ICollection<OrderItem> items;
I've changed the property type from List<> to ICollection<> as a JSON POCO typically need only be this weaker type, but if List<> is required, then just replaced ICollection and Collection with List in all the above code.
take a look at the System.Runtime.Serialization namespace in the c# framework, it's going to get you to where you want to be very quickly.
If you want, you can check out some example code in this project (not trying to plug my own work but i just finished pretty much exactly what you are doing but with a different source api.
hope it helps.
.Net Framework
using Newtonsoft.Json;
using System.IO;
public Object SingleObjectOrArrayJson(string strJson)
{
if(String.IsNullOrEmpty(strJson))
{
//Example
strJson= #"{
'CPU': 'Intel',
'PSU': '500W',
'Drives': [
'DVD read/writer'
/*(broken)*/,
'500 gigabyte hard drive',
'200 gigabyte hard drive'
]
}";
}
JsonTextReader reader = new JsonTextReader(new StringReader(strJson));
//Initialize Read
reader.Read();
if (reader.TokenType == JsonToken.StartArray)
{
return JsonConvert.DeserializeObject<List<Object>>(strJson);
}
else
{
Object media = JsonConvert.DeserializeObject<Object>(strJson);
return new List<Object>(new[] {media});
}
}
Note:
"Object" must be defined according to the Json attributes of your response
Expounding upon Martinez and mfanto's answer for Newtonsoft. It does work with Newtonsoft:
Here is an example of doing it with an array instead of a list (and correctly named).
public class SingleValueArrayConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject
|| reader.TokenType == JsonToken.String
|| reader.TokenType == JsonToken.Integer)
{
return new T[] { serializer.Deserialize<T>(reader) };
}
return serializer.Deserialize<T[]>(reader);
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Then over the attribute write this:
[JsonProperty("INSURANCE")]
[JsonConverter(typeof(SingleValueArrayConverter<InsuranceInfo>))]
public InsuranceInfo[] InsuranceInfo { get; set; }
Newtonsoft will do the rest for you.
return JsonConvert.DeserializeObject<T>(json);
Cheers to Martinez and mfanto!
Believe it or not, this will work with sub items. (It may even have to.) So... inside of my InsuranceInfo, if I have another object/array hybrid, use this again on that property.
This will also allow you to reserialize the object back to json. When it does reserialize, it will always be an array.
I think you should write your class like this...!!!
public class FacebookAttachment
{
[JsonProperty("attachment")]
public Attachment Attachment { get; set; }
[JsonProperty("permalink")]
public string Permalink { get; set; }
}
public class Attachment
{
[JsonProperty("media")]
public Media Media { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("caption")]
public string Caption { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("properties")]
public Properties Properties { get; set; }
[JsonProperty("icon")]
public string Icon { get; set; }
[JsonProperty("fb_object_type")]
public string FbObjectType { get; set; }
}
public class Media
{
}
public class Properties
{
}