I am trying to parse into different items a JSON file that has items with the following example content:
{
"PM00000001": { "description": "Manufacturing","cost": -1,"group":"Manufacturing","WeldAngleDegrees": 60},
"PM00000010": {"description": "Plate Roll","cost": 90,"unit": "hr","group": "Roll","setup": 0.5,"speed": 0.4},
"PM00000011": {"description": "Weld SAW","cost": 90,"unit": "hr","group": "Weld","width": 0.5,"thickness": 50}
}
Each item has a description, cost and group. The rest of the attributes depend on the group. In the example above Manufacturing has "WeldAngleDegrees", Roll has setup and speed, and Weld has width and thickness.
I am trying to use JSON.NET to parse this file.
Right now I am doing this:
string text = System.IO.File.ReadAllText(ofd.FileName);
Dictionary<string, Item> deserializedProduct = JsonConvert.DeserializeObject<Dictionary<string, Item>>(text, new ItemConverter());
with
public class Item
{
public string description { get; set; }
public double cost { get; set; }
public string group { get; set; }
}
public class ManufacturingItem : Item
{
public string WeldAngleDegrees { get; set; }
}
public class ItemConverter : CustomCreationConverter<Item>
{
public override Item Create(Type objectType)
{
return new ManufacturingItem();
}
}
Is there a way in ItemConverter to figure out which "group" the item belongs to to create the correct item type?
Is there an easier way to do this?
Instead of deriving your ItemConverter from CustomCreationConverter<T>, derive it from JsonConverter; then you will have access to the JSON via the reader. You can load the object data into a JObject, then inspect the group property to determine which class to create. Here is how the code might look:
public class ItemConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Item);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string group = (string)jo["group"];
if (group == "Manufacturing")
{
return jo.ToObject<ManufacturingItem>();
}
else if (group == "Roll")
{
return jo.ToObject<RollItem>();
}
else if (group == "Weld")
{
return jo.ToObject<WeldItem>();
}
throw new JsonSerializationException("Unexpected item (group) type");
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Fiddle: https://dotnetfiddle.net/8ZIubu
Just deserialize your json to a dictionary, and interpret the values according to the value of group.
var dict = JsonConvert.DeserializeObject<Dictionary<string, MyItem>>(json);
public class MyItem
{
public string Description { get; set; }
public int Cost { get; set; }
public string Group { get; set; }
public int WeldAngleDegrees { get; set; }
public string Unit { get; set; }
public double Width { get; set; }
public int Thickness { get; set; }
public double Speed { get; set; }
public double Setup { get; set; }
}
Related
I have a very similar issue to this question here, except my application is in C#, and I can't figure out how to convert the solution unfortunately. I am trying to deserialize a JSON result that looks like this:
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77],
],
"last":1619118000
}
I use the following classes:
public class ResponseBase
{
[JsonProperty(PropertyName = "error")]
public List<string> Error;
}
public class OHLCResponse : ResponseBase
{
[JsonProperty("result")]
public OHLCResult Result;
}
public class OHLCResult
{
[JsonProperty("pair_names")]
public Dictionary<string, OHLC[]> GetHistory;
[JsonProperty("last")]
public long Last;
}
.... and then finally the guts of it:
public class OHLC
{
public int Time;
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
public decimal Vwap;
public decimal Volume;
public int Count;
}
I have a standard deserializer class which works for all other calls I am using to the same API, but I cannot get this call to work. When I retrieve OHLCResponse object,I don't get an error, and "Result.Last" is always populated, but the expected array of OHLC items in "Result.GetHistory" is always empty/null. I know that the data has been returned successfully since I can see the data in the variable returned from the WebRequest that I am then passing to the deserializer function, so it must be that I have these classes laid out incorrectly I guess.
Can anyone see what I'm doing wrong?
Many thanks in advance, Dave
The object you posted isn't valid JSON. The outside curly braces are missing. So I am going to assume it should look like this:
{
"error": [],
"result": {
"MANAEUR": [
[1619042400, "1.11200", "1.13488", "1.08341", "1.10077", "1.09896", "58878.56534370", 137],
[1619046000, "1.09767", "1.12276", "1.08490", "1.11097", "1.10456", "25343.25910419", 77],
],
"last": 1619118000
}
}
Anonymous Deserialization
The first method you could do, which may be a tad kludgy since you have to deserialize twice, is use anonymous deserialization.
Let's start by defining some models:
public sealed class OHLCModel
{
public long Time { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Vwap { get; set; }
public decimal Volume { get; set; }
public int Count { get; set; }
}
public sealed class ResultModel
{
[JsonIgnore]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
public sealed class RootModel
{
[JsonProperty("error")]
public List<string> Error { get; set; }
[JsonProperty("result")]
public ResultModel Result { get; set; }
}
As you can see we are ignoring the Manaeur object when serialization happens.
To make this method work, we'd do this:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
// First, just grab the object that has the mixed arrays.
// This creates a "template" of the format of the target object
var dto = JsonConvert.DeserializeAnonymousType(json, new
{
Result = new
{
Manaeur = new List<List<object>>()
}
});
// Next, deserialize the rest of it
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
// transfer the DTO using a Select statement
fullObject.Result.Manaeur = dto.Result.Manaeur.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
This isn't the most ideal solution as you are tightly coupling to the model in a few spots. The ideal way to do this would be to make a custom JsonSerializer.
Use a Custom JsonConverter
First thing we do is change your ResultModel to look like this:
public sealed class ResultModel
{
[JsonConverter(typeof(ManaeurJsonConverter)), JsonProperty("MANAEUR")]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
Then implement a JsonConverter:
public sealed class ManaeurJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => false; // this will never get called
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var lst = JArray.Load(reader).ToObject<List<List<object>>>();
return lst.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ // we don't need to write
throw new NotImplementedException();
}
}
You can then simply call it as so:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
Your JSON is not valid, I had to modify it to make it valid JSON (https://jsonformatter.org/). I added the root brackets and removed the comma delimter after the second inner array entry.
Valid JSON:
{
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77]
],
"last":1619118000
}
}
After updating the JSON, I used Visual Studio's 'Paste Special' to generate C# objects from the JSON. The following classes were created.
public class RootObject
{
[JsonProperty("error")]
public object[] Error { get; set; }
[JsonProperty("result")]
public Result Result { get; set; }
}
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
With the above JSON and classes, I used the following to deserialize the JSON.
string json = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
EDIT:
To handle the MANAEUR property where the key label can be different.
Create a JsonConverter...
public class ManaeurConverter : JsonConverter
{
private Dictionary<string, string> propertyMappings { get; set; }
public ManaeurConverter()
{
this.propertyMappings = new Dictionary<string, string>
{
{"NOTMANAEUR","MANAEUR"}
};
}
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 instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
if (!propertyMappings.TryGetValue(jp.Name, out var name))
name = jp.Name;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override bool CanWrite => false;
}
... Add the JsonConverter attribute to the class...
[JsonConverter(typeof(ManaeurConverter))]
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
... and parse like so...
string json_Manaeur = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
string json_Not_Manaeur = "{\"error\":[],\"result\":{\"NOTMANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var objManaeur = JsonConvert.DeserializeObject<RootObject>(json_Manaeur);
var objNotManaeur = JsonConvert.DeserializeObject<RootObject>(json_Not_Manaeur);
I am trying to consume an external web service and I am using .NET Core and the Flurl framework. I get a response from the service like below:
[
"Successful Request: 96 Results",
[
{
"eventdate":"2019-10-18",
"name":"",
"url":"",
"info":"",
"showtime":null,
"url_tix":"",
"event_owner":"xxx",
"follow_url":"xxx",
"event_image":"xxx",
"venue":"xxx",
"city":"xxx",
"country":"xxx",
"state":""
}
]
]
and I have a C# entity definition like below:
public class ServiceResponce
{
public Event[] Events { get; set; }
}
public class Event
{
[JsonProperty("eventdate")]
public DateTimeOffset Eventdate { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("info")]
public string Info { get; set; }
[JsonProperty("showtime")]
public object Showtime { get; set; }
[JsonProperty("url_tix")]
public object UrlTix { get; set; }
[JsonProperty("event_owner")]
public string EventOwner { get; set; }
[JsonProperty("follow_url")]
public Uri FollowUrl { get; set; }
[JsonProperty("event_image")]
public object EventImage { get; set; }
[JsonProperty("venue")]
public string Venue { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("state")]
public string State { get; set; }
}
When I tried to call the Flurl method to consume the web service like below:
var result = await serviceUrl.GetJsonAsync<ServiceResponce>();
I got the error mentioned below:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type
'xxx.ServiceResponce' because the type requires a JSON object (e.g.
{"name":"value"}) to deserialize correctly. To fix this error either
change the JSON to a JSON object (e.g. {"name":"value"}) or change the
deserialized type to an array or a type that implements a collection
interface (e.g. ICollection, IList) like List that can be
deserialized from a JSON array. JsonArrayAttribute can also be added
to the type to force it to deserialize from a JSON array. Path '',
line 1, position 1.
Do you have any solution for that? Any help always is welcome.
The problem here is the JSON response is actually an array of mixed types. The first element of the array is a string, and the second element is an array of event objects. You will need a custom JsonConverter to deserialize this JSON.
Here is the code you would need for the converter:
class ServiceResponceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ServiceResponce));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JArray ja = JArray.Load(reader);
ServiceResponce resp = new ServiceResponce();
resp.Events = ja[1].ToObject<Event[]>(serializer);
return resp;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add a [JsonConverter] attribute to the ServiceResponce class to tie it to the converter:
[JsonConverter(typeof(ServiceResponceConverter))]
public class ServiceResponce
{
public Event[] Events { get; set; }
}
Now you can deserialize to the ServiceResponce class as normal and it will work properly.
Optional: If you also want to capture the "Successful Request: 96 Results" string from the response, add
public string ResultString { get; set; }
to the ServiceResponce class and add the following line to the the ReadJson method of the converter:
resp.ResultString = (string)ja[0];
Working demo here: https://dotnetfiddle.net/opPUmX
I think the problem is in the Json object, I have generated a class with 'Newtonsoft.Json', if you can try this code:
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using MyNameSpace;
//
// var event = Event.FromJson(jsonString);
namespace MyNameSpace
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class EventClass
{
[JsonProperty("eventdate")]
public DateTimeOffset Eventdate { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("info")]
public string Info { get; set; }
[JsonProperty("showtime")]
public object Showtime { get; set; }
[JsonProperty("url_tix")]
public string UrlTix { get; set; }
[JsonProperty("event_owner")]
public string EventOwner { get; set; }
[JsonProperty("follow_url")]
public string FollowUrl { get; set; }
[JsonProperty("event_image")]
public string EventImage { get; set; }
[JsonProperty("venue")]
public string Venue { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
[JsonProperty("state")]
public string State { get; set; }
}
public partial struct EventUnion
{
public EventClass[] EventClassArray;
public string String;
public static implicit operator EventUnion(EventClass[] EventClassArray) => new EventUnion { EventClassArray = EventClassArray };
public static implicit operator EventUnion(string String) => new EventUnion { String = String };
}
public class Event
{
public static EventUnion[] FromJson(string json) => JsonConvert.DeserializeObject<EventUnion[]>(json, MyNameSpace.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this EventUnion[] self) => JsonConvert.SerializeObject(self, MyNameSpace.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
EventUnionConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class EventUnionConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(EventUnion) || t == typeof(EventUnion?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
case JsonToken.Date:
var stringValue = serializer.Deserialize<string>(reader);
return new EventUnion { String = stringValue };
case JsonToken.StartArray:
var arrayValue = serializer.Deserialize<EventClass[]>(reader);
return new EventUnion { EventClassArray = arrayValue };
}
throw new Exception("Cannot unmarshal type EventUnion");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
var value = (EventUnion)untypedValue;
if (value.String != null)
{
serializer.Serialize(writer, value.String);
return;
}
if (value.EventClassArray != null)
{
serializer.Serialize(writer, value.EventClassArray);
return;
}
throw new Exception("Cannot marshal type EventUnion");
}
public static readonly EventUnionConverter Singleton = new EventUnionConverter();
}
}
I am reading data from a graph database and getting the response as a dynamic object. I go through the results and try to deserialize them as so:
var e = results.GetEnumerator();
while (e.MoveNext())
{
var serialized = JsonConvert.SerializeObject(e.Current);
// {"FlagCalculateclientside":[false],"Description":["Some detailed info"], "Name": ["MyDetailedEntity"]}
var val = JsonConvert.DeserializeObject<MyObject>(serialized);
}
public class MyObject
{
public bool FlagCalculateclientside { get; set; }
public string Description { get; set; }
public string Name { get; set; }
}
But I get the following error:
Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: [. Path 'FlagCalculateclientside', line 1, position 28.
at Newtonsoft.Json.JsonTextReader.ReadAsBoolean()
at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
...
I guess this is because the values are in arrays, but only a single value per key was expected.
Any idea how to fix this?
Your model doesn't match your JSON, all of the properties are arrays, in other words they are surround with [...]. To fix, change the model to this:
public class MyObject
{
public List<bool> FlagCalculateclientside { get; set; }
public List<string> Description { get; set; }
public List<string> Name { get; set; }
}
An alternative would be to use a custom converter, for example:
public class ArrayConverter<T> : JsonConverter<T>
{
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
//This isn't the best code but shows you what you need to do.
return token.ToObject<List<T>>().First();
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And change your model to this:
public class MyObject
{
[JsonConverter(typeof(ArrayConverter<bool>))]
public bool FlagCalculateclientside { get; set; }
[JsonConverter(typeof(ArrayConverter<string>))]
public string Description { get; set; }
[JsonConverter(typeof(ArrayConverter<string>))]
public string Name { get; set; }
}
I have been working with the project, where i have to make external RESTful service call to get some data.
The problem i'm facing here is, the response i'm getting from the service is different on different scenario. For example.
On one scenario, i'm getting below response
{
"id":3000056,
"posted_date":"2016-04-15T07:16:47+00:00",
"current_status":"initialized",
"customer":{
"name" : "George",
"lastName" : "Mike"
},
"application_address":{
"addressLine1" : "Lin1",
"addressLine2" : "Lin2",
}
}
In the other scenario, im getting below response
{
"id":3000057,
"posted_date":"2016-04-15T07:16:47+00:00",
"current_status":"initialized",
"customer":[],
"application_address":[]
}
The problem here is, i have below model, and i'm deserializing it by newtonsoft deserailization.
public class Response
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("posted_date")]
public DateTime PostedDate { get; set; }
[JsonProperty("current_status")]
public string CurrentStatus { get; set; }
[JsonProperty("customer")]
public Customer Customer { get; set; }
[JsonProperty("application_address")]
public ApplicationAddress ApplicationAddress { get; set; }
}
public Class Customer
{
public string name { get; set; }
public string lastName { get; set; }
}
public classs ApplicationAddress
{
public string addreesLine1{ get; set; }
public string addreesLine1{ get; set; }
}
For the first response, it will desrialize. But for the second response, the response is not getting deserialized as the response contains [] for Customer and ApplicationAddrees object. While deserializing, it is treating as a array, but actually it is not.
Note : Below code i'm using for Deserializing.
Response response = JsonConvert.DeserializeObject(result);
Is there any configuration we can do before serializing? Is newtonsoft facilitate that feature?
Thanks .
If you are sure that there will be no arrays in this properties, than you can consider using JsonConverter like this:
public class FakeArrayToNullConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return null;
}
return token.ToObject<T>();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And then put additional attribure to your model:
[JsonProperty("customer")]
[JsonConverter(typeof(FakeArrayToNullConverter<Customer>))]
public Customer Customers { get; set; }
[JsonProperty("application_address")]
[JsonConverter(typeof(FakeArrayToNullConverter<ApplicationAddress>))]
public ApplicationAddress ApplicationAddressList { get; set; }
And when in your JSON string for this properties it will be an array [], you will simply deserialize it with null object.
You can't instruct deserialize to handle "[]" as something different as it represents an array (are you sure you never will get customers and addresses in those arrays??)
So you could deserialize to an anonymous type and then map it to your structure.
This is just a guess but can you check if this works:
public class ApplicationAddress
{
private readonly string[] _array = new string[2];
public string this[int index]
{
get { return _array[index]; }
set { _array[index] = value; }
}
public string addreesLine1
{
get { return this[0]; }
set { this[0] = value; }
}
public string addreesLine2
{
get { return this[1]; }
set { this[1] = value; }
}
}
Querying the Stack Overflow websockets with 155-questions-active I get the following (malformatted) JSON:
{
"action":"155-questions-active",
"data":
"{
\"siteBaseHostAddress\":\"stackoverflow.com\",
\"id\":23747905,
\"titleEncodedFancy\":\"Load sqlite extension in Django\",
\"bodySummary\":\"I have built a sqlite <snip>\",
\"tags\":[\"django\",\"sqlite\",\"pysqlite\"],
\"lastActivityDate\":1400544795,
\"url\":\"http://stackoverflow.com/questions/23747905/<snip>\",
\"ownerUrl\":\"http://stackoverflow.com/users/1311165/pro-chats\",
\"ownerDisplayName\":\"Pro Chats\",
\"apiSiteParameter\":\"stackoverflow\"
}"
}
After applying some fixes
private string MakeJsonCapable(string input)
{
input = input.Trim();
input = input.Replace("data\":\"", "data\":");
input = input.Remove(input.LastIndexOf("\""), 1);
input = input.Replace("\\", string.Empty);
return input;
}
I get to this result:
{
"action": "155-questions-active",
"data": {
"siteBaseHostAddress": "stackoverflow.com",
"id": 23747905,
"titleEncodedFancy": "Load sqlite extension in Django",
"bodySummary": "I have built a sqlite <snip>",
"tags": [
"django",
"sqlite",
"pysqlite"
],
"lastActivityDate": 1400544795,
"url": "http:\/\/stackoverflow.com\/questions\/23747905\/<snip>",
"ownerUrl": "http:\/\/stackoverflow.com\/users\/1311165\/pro-chats",
"ownerDisplayName": "Pro Chats",
"apiSiteParameter": "stackoverflow"
}
}
Which is now acceptable JSON (I'm using some online JSON format tool to verify this) that gets parsed perfectly by JSON.NET.
The problem occurs when a value (so far I've only seen it in bodySummary but I suspect titleEncodedFancy is also likely to have this) contains a ". The literal value that is being passed before making it Json-able is \\\"Compliant Solution\\\": 3 backslashes and an accent.
Note that this is the literal value and does not include any backslashes from the debugger: this is taken directly from the textview; the watch variable shows 7 backslashes.
Obviously this is a problem because now my bodySummary contains an unescaped " which will corrupt the deserializing. For this reason I can't create a custom JsonConverter to escape them myself either since it won't get the right values in the first place.
How can I remove the unwanted backslashes that appear in front of the accents that signify the start and end of a field's name and its value?
Alternatively: maybe I am parsing the data field incorrectly in the first place. If so: what is the correct way?
What you have here is data that has been serialized to a string, placed inside another object and then serialized a second time. To get everything back out correctly, you can reverse the process. Define two classes, one for the outer serialization and one for the inner:
class Outer
{
public string Action { get; set; }
public string Data { get; set; }
}
class Inner
{
public string SiteBaseHostAddress { get; set; }
public int Id { get; set; }
public string TitleEncodedFancy { get; set; }
public string BodySummary { get; set; }
public string[] Tags { get; set; }
public int LastActivityDate { get; set; }
public string Url { get; set; }
public string OwnerUrl { get; set; }
public string OwnerDisplayName { get; set; }
public string ApiSiteParameter { get; set; }
}
Then deserialize like this:
Outer outer = JsonConvert.DeserializeObject<Outer>(json);
Inner inner = JsonConvert.DeserializeObject<Inner>(outer.Data);
When you do this, do NOT apply the "fixes" to the input string. Let the JSON parser do its job.
EDIT
If you want to keep the parent-child relationship, you'll need a custom JsonConverter to handle the deserialization of the child object. To do this, you first need to change the definition of the outer class to this:
class Outer
{
public string Action { get; set; }
[JsonConverter(typeof(InnerConverter))]
public Inner Data { get; set; }
}
Create the InnerConverter class like this:
class InnerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Inner));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return JsonConvert.DeserializeObject<Inner>(token.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And finally, you can deserialize like this:
Outer outer = JsonConvert.DeserializeObject<Outer>(json);
Following Brian Rogers' suggestion I created a simple converter which handles it all for me:
Response
public sealed class Response
{
[JsonProperty("action")]
public string Action { get; internal set; }
[JsonProperty("data")]
[JsonConverter(typeof (DataConverter))]
public Data Data { get; internal set; }
}
Data
public sealed class Data
{
[JsonProperty("siteBaseHostAddress")]
public string SiteBaseHostAddress { get; internal set; }
[JsonProperty("id")]
public string Id { get; internal set; }
[JsonProperty("titleEncodedFancy")]
public string TitleEncodedFancy { get; internal set; }
[JsonProperty("bodySummary")]
public string BodySummary { get; internal set; }
[JsonProperty("tags")]
public IEnumerable<string> Tags { get; internal set; }
[JsonProperty("lastActivityDate")]
[JsonConverter(typeof (EpochTimeConverter))]
public DateTime LastActivityDate { get; internal set; }
[JsonProperty("url")]
[JsonConverter(typeof (UriConverter))]
public Uri QuestionUrl { get; internal set; }
[JsonProperty("ownerUrl")]
[JsonConverter(typeof (UriConverter))]
public Uri OwnerUrl { get; internal set; }
[JsonProperty("ownerDisplayName")]
public string OwnerDisplayName { get; internal set; }
[JsonProperty("apiSiteParameter")]
public string ApiSiteParameter { get; internal set; }
}
DataConverter
internal sealed class DataConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var value = reader.Value as string;
return JsonConvert.DeserializeObject<Data>(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Now I can deserialize it entirely with
var responseObject = JsonConvert.DeserializeObject<Response>(result);