Failed to desirialize heterogeneous json data - c#

So I am using Json.net to deserialize json data returned by REST APIs to a server, the data structure it returns is:
{ "keyXXX" : [[1,2,3,...]...], "last": 12345 }
The problem is the "key" is also part of data, it is not a field named "key", so I cannot use a class/struct, I had to use a IDictionary<string, int[][]> for the first part, but then the "last" part will throw an exception, because it is a single integer rather than an array of arrays.
This is what I've tried:
var dec = JsonConvert.DeserializeObject<IDictionary<string, int[][]>>(data);
This throws json exception:
Newtonsoft.Json.JsonSerializationException: Error converting value 1501555920 to type 'System.Decimal[][]'. Path 'last'.
//define a class
public class DPInfo
{
public decimal[][] points { get; set; }
public long last { get; set; }
}
var dec = JsonConvert.DeserializeObject<DPInfo>(data);
This will not work because the field name of the array is dynamic, so points will contains nothing after this.
Any way I can fix this?

You will need a custom JsonConverter to solve this. Here is one that should work:
public class DPInfoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DPInfo);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
JProperty points = obj.Properties().FirstOrDefault(p => p.Name != "last");
DPInfo info = new DPInfo
{
key = points.Name, // remove this line if you don't need the key
points = points.Value.ToObject<decimal[][]>(),
last = (long)obj["last"]
};
return info;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use it, just add a [JsonConverter] attribute to your class:
[JsonConverter(typeof(DPInfoConverter))]
public class DPInfo
{
public string key { get; set; } // remove this line if you don't need the key
public decimal[][] points { get; set; }
public long last { get; set; }
}
Then, deserialize as usual, and it should work:
string json = #"
{
""keyXXX"": [[1, 2, 3]],
""last"": 12345
}";
DPInfo info = JsonConvert.DeserializeObject<DPInfo>(json);
Fiddle: https://dotnetfiddle.net/7S6STp

You could use JObject.Parse to parse your string into a dynamic variable. It would at least ensure that the conversion from string to JSON succeeds, but then it would be up to you to validate that there is a value in each property before accessing it.
In your case the statement would look like this:
dynamic data = JObject.Parse("{ 'keyXXX' : [[1,2,3,...]...], 'last': 12345 }");

Related

Concat names of nested values of a json file in c# ex: ` "obj_nested1_nested2" : "text"

I'm looking for a way to transform this c# object:
class BaseClass
{
public string Value1 {get; set;}
public NestedObject nestedObject {get;set;}
}
class NestedObject
{
public string NestedValue1 {get; set;}
}
Into this json:
{
"Value1": "value1",
"NestedObject_NestedValue1": "nestedValue1"
}
By concatening the names of the nested parameters to their parent's name
Using normal serialization, this code: var json= JsonConvert.SerializeObject(baseClass);
Would instead return a json like this one:
{
"Value1": "value1",
"NestedObject": {
"NestedValue1": "nestedValue1"
}
}
I am sceptical about there being a way to deserialize a json like that back to an object tho.
Update:
As some asked what is the reason I'm trying to accomplish this:
The reason I asked this question is because I serialize this object to send as json metadata to a service that only allows referencing top level propreties in a way similar to this:
[Metadata_Value1] would return "value1"
However [Metadata_NestedObject_NestedValue1] doesn't work and there isn't any indication to there being a way to reference nested properties.
Taking this in consideration I hoped there would be some solution that would allow keeping the nested objects in my program but transforming them all to top properties when sending them to this service.
In the service I would then be able to do: [NestedObject_NestedValue1] and get the value "nestedValue1"
You can utilize a custom converter that looks like below :
public class NestedJsonConverter : JsonConverter
{
private readonly Type[] _types;
public NestedJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
writer.WriteStartObject();
void writeNested(JObject target, object source, string prefix)
{
target.Properties().ToList().ForEach(p =>
{
var prop = source.GetType().GetProperty(p.Name);
var value = prop.GetValue(source);
var prefixed = string.IsNullOrEmpty(prefix) ? p.Name : $"{prefix}_{p.Name}";
if (p.Value.Type == JTokenType.Object)
{
writeNested((JObject)p.Value, value, prefixed);
}
else if (p.Value.Type == JTokenType.Array)
{
// you may need a more advanced handling in array scenarios
var arr = (JArray)p.Value;
writer.WritePropertyName(prefixed);
arr.WriteTo(writer);
}
else
{
writer.WritePropertyName(prefixed);
writer.WriteValue(value);
}
}
);
}
writeNested(o, value, "");
writer.WriteEndObject();
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
Dotnetfiddle

Deserializing a list of dynamic properties

My application is consuming an API, and I'm trying to deserialize data of images coming back. The data is formatted like:
{
"images":{
"totalCount":4,
"0":{
"url":"file1.jpg"
},
"1":{
"url":"file2.jpg"
},
"2":{
"url":"file3.jpg"
},
"3":{
"url":"file4.jpg"
}
}
}
I have these model classes:
public class MyViewModel
{
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
public class ImagesViewModel
{
[JsonProperty("totalCount")]
public int TotalCount { get; set; }
public Dictionary<string, ImageViewModel> ListImages { get; set; }
}
public class ImageViewModel
{
[JsonProperty("url")]
public string Url { get; set; }
}
The collection of images isn't really a collection, for some reason it's just a new property for each image. I'm trying to deserialize my object like:
... // create HttpClient object, add headers and such
System.Net.Http.HttpResponseMessage response = await
client.GetAsync(endpointUrl);
var jsonString = response.Content.ReadAsStringAsync();
MyViewModel model =
JsonConvert.DeserializeObject<MyViewModel>(jsonString.Result);
I get back the totalCount property just fine, but the collection of images is coming back null.
Is there a way for me to change my view models so that I can deserialize the json correctly?
Given the formatting of the JSON you will have to go the long route and try to deserialize it using JObjects
//using Newtonsoft.Json.Linq
var jObject = JObject.Parse(jsonString);
var images = jObject.Property("images").Value<JObject>(); ;
var viewModel = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
Going a step further and using a JsonConverter for converting the payload itself actually works as well given that we know now how to convert it.
public class MyViewModelConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(MyViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var jObject = JObject.Load(reader);//<-- Note the use of Load() instead of Parse()
var images = jObject.Property("images").Value<JObject>(); ;
var model = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
return model;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
and decorating the class itself
[JsonConverter(typeof(MyViewModelConverter))]
public class MyViewModel {
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
Deserialization is now as you intended to do before
var jsonString = await response.Content.ReadAsStringAsync();
MyViewModel model = JsonConvert.DeserializeObject<MyViewModel>(jsonString);
.NET Abhors dynamic types. They fly in the face of solid type checking at compile time. That being said, there is support for it:
As the example data is basically just a array of images, any collection could deal with this input.
If you can not even define the types umanbigiously (you might have a array of images and one of strings), the only way is ExpandoObject. It is designed specifically to deal with such cases. It is basically a List[string, object] with some Syntax Sugar, but it also does includes functions like Property Change Notifications.
Sounds like a job for a custom converter!
A custom converter will let you supply your own logic for deserializing specific types. Newtonsoft uses the target class to figure out with type if expects to find in the json and call the appropriate converter.
class ImagesViewModelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ImagesViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
assertToken(JsonToken.StartObject);
var obj = new ImagesViewModel()
{
ListImages = new Dictionary<string, ImageViewModel>()
};
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
assertToken(JsonToken.PropertyName);
var propName = (string)reader.Value;
if (propName.Equals(nameof(ImagesViewModel.TotalCount), StringComparison.InvariantCultureIgnoreCase))
{
reader.Read();
assertToken(JsonToken.Integer);
obj.TotalCount = (int)((Int64)reader.Value);
continue;
}
reader.Read();
var image = serializer.Deserialize<ImageViewModel>(reader); // you can still use normal json deseralization inside a converter
obj.ListImages.Add(propName, image);
}
return obj;
void assertToken(JsonToken token)
{
if (reader.TokenType != token)
throw new Exception(); // might wanna add detailed errors
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // implement if needed
}
}
And then:
var settings = new JsonSerializerSettings()
{
Converters = new[] { new ImagesViewModelConverter() }
};
var obj = JsonConvert.DeserializeObject<MyViewModel>(jsonString, settings);
});
You can even change classes to be easier to handle, given that they no longer need to match the json exactly. You can for example replace the dict with an array and have the converter fill it in order.

How to handle conversion from string to enum when string starts with a number in Json.Net

I am receiving json from a server which I am converting to an object using Json.Net. For one member I am using the StringEnumConverter which works really perfect.
However all of a sudden the server decided to use strings which also can start with a number which results in a JsonSerilizationException - obviously because enums cannot start with a number in .Net.
Now I am trying to find a solution to handle that.My first approach was to add a "_" when Reading the Json (so my enums in the code would have a starting _ when they are followed by a number) and when writing the json I would delete the starting _ (if a number is following). To achieve this I copied the StringEnumConverter into my namespace and tried to change the according part in the WriteJson and ReadJson methods. However I cannot use the StringEnumConverter since there are other dependencies I cannot access in my own namespace.
Is there any elegant solution to this problem?
You can create a JsonConverter and trim the digits from the front
public class Json_34159840
{
public static string JsonStr = #"{""enum"":""1Value"",""name"":""James"",""enum2"":""1""}";
public static void ParseJson()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EnumConverter() }
};
// Later on...
var result = JsonConvert.DeserializeObject<JsonClass>(JsonStr);
Console.WriteLine(result.Enum);
Console.WriteLine(result.Enum2);
Console.WriteLine(result.Name);
}
}
public class EnumConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var str = value.ToString();
if (Regex.IsMatch(str, #"^_"))
{
writer.WriteValue(str.Substring(1));
}
else
{
writer.WriteValue(str);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value.ToString();
if (Regex.IsMatch(value, #"^\d+$"))
{
return Enum.Parse(objectType, value);
}
if (Regex.IsMatch(value, #"^\d+"))
{
value = "_" + value;
}
return Enum.Parse(objectType, value);
}
public override bool CanConvert(Type objectType)
{
//You might want to do a more specific check like
//return objectType == typeof(JsonEnum);
return objectType.IsEnum;
}
}
public enum JsonEnum
{
_0Default,
_1Value
}
public class JsonClass
{
public string Name { get; set; }
public JsonEnum Enum { get; set; }
public JsonEnum Enum2 { get; set; }
}
Hope this helps.
EDIT: Added support for integers :D
A simple strategy is to deserialize the value into a string property and then convert into your own data type (enum or otherwise) via an accessor method or a secondary getter-only property.

mvc6 model binder not picking untagged JSON

I'm trying to receive a JSON in a MVC6 controller. I see my function being triggered when debugging, but the param is always null.
The JSON is like this (it comes from an external software, so I can't change it. Also notice the ContentType is "application/w-xxx-form-urlencoded"):
["String1","String2",["StringInAList1","StringInAList2",...]]
My code looks like this:
[HttpPost]
public void ReceiveJson([FromBody] MyJson json)
{
//stuff
}
public class MyJson
{
public string string1 { get; set; }
public string string2 { get; set; }
public List<string> Data { get; set; }
}
Any ideas of what's wrong here?
Firstly, that JSON is an array, not an object. A JSON object would look like this
{
"MessageName": "String1",
"UserName": "String2",
"AdditionalData": ["StringInAList1","StringInAList2",...]
}
Since non-enumerable c# classes are mapped to JSON objects, asp.net isn't going to be able to deserialize that JSON to your class by default.
Secondly, going by the documentation here, AdditionalData can be any array of strings or nested arrays of the same type, not just a simple array of strings, for instance:
["GameEnded","Risterral",[["Risterral", "Won"],["PlayerName2", "Lost"]]]
A simple List<string> Data won't be able to represent this.
Since asp.net uses Json.NET by default for JSON serialization, the easiest way to represent such a polymorphic array is as a JArray, which can represent any JSON array in memory. You will also need a custom JsonConverter for the top level object. Thus:
public enum HexRequestType
{
Unknown,
Login,
Logout,
SaveDeck,
DraftPack,
DraftCardPicked,
GameStarted,
GameEnded,
Collection,
}
[JsonConverter(typeof(HexRequestConverter))]
public class HexRequest
{
[JsonIgnore]
public HexRequestType RequestType
{
get
{
if (string.IsNullOrEmpty(MessageName))
return HexRequestType.Unknown;
if (MessageName.Equals("DaraftCardPicked", StringComparison.OrdinalIgnoreCase))
return HexRequestType.DraftCardPicked;
try
{
return (HexRequestType)Enum.Parse(typeof(HexRequestType), MessageName, true);
}
catch (Exception)
{
Debug.WriteLine("Unknown request " + MessageName);
return HexRequestType.Unknown;
}
}
}
public string MessageName { get; set; }
public string UserName { get; set; }
public JArray AdditionalData { get; set; }
}
public class HexRequestConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HexRequest);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var array = JToken.Load(reader) as JArray;
if (array == null)
return existingValue;
var request = existingValue as HexRequest ?? new HexRequest();
request.MessageName = (array.Count > 0 ? (string)array[0] : null);
request.UserName = (array.Count > 1 ? (string)array[1] : null);
request.AdditionalData = (JArray)(array.Count > 2 ? array[2] : null) ?? new JArray();
if (array.Count > 3)
{
Debug.WriteLine("array too large");
throw new InvalidOperationException();
}
return request;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var request = (HexRequest)value;
var list = new List<object> { request.MessageName, request.UserName, request.AdditionalData };
serializer.Serialize(writer, list);
}
}
Having done that, you can use Linq and Linq-to-JSON to manually extract the expected data, for instance:
switch (request.RequestType)
{
case HexRequestType.GameEnded:
{
// ["GameEnded","Risterral",[["Risterral", "Won"],["PlayerName2", "Lost"]]]
var results = request.AdditionalData.OfType<JArray>().Select(a => new { Player = (string)a[0], Result = (string)a[1] }).ToList();
Debug.WriteLine(JsonConvert.SerializeObject(results));
// Outputs [{"Player":"Risterral","Result":"Won"},{"Player":"PlayerName2","Result":"Lost"}]
}
break;
// Other cases as needed
default:
break;
}

How to deserialize object that can be an array or a dictionary with Newtonsoft?

I am using an API that returns a json object that I need to deserialize. My problem is that one of the members of those object is sometimes an empty array ("[]") and sometimes a dictionary ("{"1":{...}, "2":{...}}"). I want to deserialize it into either an array or a dictionary, since I don't car about the IDs, I just want a list of all the objects. Here is how I deserialize the object:
var response = JsonConvert.DeserializeObject<Response>(json);
And here is the definition of the Response class:
public class Response
{
[JsonProperty(PropertyName = "variations")]
public Dictionary<int, Variation> Variations { get; set; }
}
It works well when the Response contains a dictionary in it's variations field, but it fails when it contains an empty array. I'm getting an error from Newtonsoft saying that an array cannot be deserialized into a dictionary. If I define the Variations property as an array, it works for empty arrays, but it fails when it is a dictionary. What could I do to either deserialize correctly both possible values, or to ignore empty arrays and set Variations to null when it's an array instead of failing.
Thanks.
Here an alternative solution using JsonConverter
public class Response
{
[JsonProperty(PropertyName = "variations")]
[JsonConverter(typeof(EmptyArrayOrDictionaryConverter))]
public Dictionary<int, Variation> Variations { get; set; }
}
public class EmptyArrayOrDictionaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(Dictionary<string, object>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Object)
{
return token.ToObject(objectType, serializer);
}
else if (token.Type == JTokenType.Array)
{
if (!token.HasValues)
{
// create empty dictionary
return Activator.CreateInstance(objectType);
}
}
throw new JsonSerializationException("Object or empty array expected");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Here is a variation (sorry for the pun) on Carl's example. I had a similar need, but instead of returning a dictionary, I needed an array. The API I am using says it returns an array. However, in the case where there is only one item in the result, it is returned as an object instead!
public class VarationsContainer
{
[JsonIgnore]
public Varation[] Varations
{
get
{
return ParseObjectToArray<Variation>(VariationObject);
}
}
[JsonProperty(PropertyName = "varations")]
public object VarationsObject { get; set; }
protected T[] ParseObjectToArray<T>(object ambiguousObject)
{
var json = ambiguousObject.ToString();
if (String.IsNullOrWhiteSpace(json))
{
return new T[0]; // Could return null here instead.
}
else if (json.TrimStart().StartsWith("["))
{
return JsonConvert.DeserializeObject<T[]>(json);
}
else
{
return new T[1] { JsonConvert.DeserializeObject<T>(json) };
}
}
}
I hope this is useful to some other sad API consumer.
Here is the solution I used :
public Dictionary<int, Variation> Variations
{
get
{
var json = this.VariationsJson.ToString();
if (json.RemoveWhiteSpace() == EmptyJsonArray)
{
return new Dictionary<int, Variation>();
}
else
{
return JsonConvert.DeserializeObject<Dictionary<int, Variation>>(json);
}
}
}
[JsonProperty(PropertyName = "variations")]
public object VariationsJson { get; set; }
Basically, the variations are first deserialized in a basic object. When I want to read the value, I check if the object is an empty array, and if so I return an empty dictionary. If the object is a good dictionary, I deserialize it and return it.

Categories