How to handle error in Json.Net parsing - c#

I'm using Json.Net for Json deserialization. On occasion the Json string I read is not correct (which I can't fix since I don't produce it). In particular, at one specific place, where there should be a string, sometimes there is a serialized object instead. Json.Net then complains, not surprisingly, about finding an object where it expected a string.
I found I can intercept this by using Error in JsonSerializerSettings and also make Json.Net ignore the problem by setting ErrorContext.Handled. But I want to do even more. If I could look at the serialised object I could figure out what the string should be and in theory supply the correct answer. In practice I can't figure our how to do so. Especially, in the error handler:
How do I access the string that the parser tripped up on (Note that the parser can successfully continue if ErrorContext.Handled is set, so it can determine start and end of the problem string correctly)?
How do I supply the correct string back to the parser, or alternatively get access to the currently parsed object so I can set the value manually?
[Edit]
As requested a simplified example:
The incorrect Json string I get to parse:
{
"id": 2623,
"name": {
"a": 39,
"b": 0.49053320637463277,
"c": "cai5z+A=",
"name": "22"
},
"children": [
{
"id": 3742,
"name": {
"a": 37,
"b": 0.19319664789046936,
"c": "Me/KKPY=",
"name": "50"
},
"children": [
{
"id": 1551,
"name": {
"a": 47,
"b": 0.6935373953047849,
"c": "qkGkMwY=",
"name": "9"
},
"children": []
},
{
"id": 4087,
"name": {
"a": 5,
"b": 0.42905938319352427,
"c": "VQ+yH6o=",
"name": "84"
},
"children": []
},
{
"id": 614,
"name": {
"a": 19,
"b": 0.7610801005554758,
"c": "czjTK1s=",
"name": "11"
},
"children": []
}
]
},
{
"id": 3382,
"name": {
"a": 9,
"b": 0.36416331043660793,
"c": "lnoHrd0=",
"name": "59"
},
"children": [
{
"id": 4354,
"name": {
"a": 17,
"b": 0.8741648112769075,
"c": "CD2i2I0=",
"name": "24"
},
"children": []
},
{
"id": 2533,
"name": {
"a": 52,
"b": 0.8839575992356788,
"c": "BxFEzVI=",
"name": "60"
},
"children": []
},
{
"id": 5733,
"name": {
"a": 4,
"b": 0.7230552787534219,
"c": "Un7lJGM=",
"name": "30"
},
"children": []
}
]
},
{
"id": 9614,
"name": {
"a": 81,
"b": 0.4015882813379114,
"c": "dKgyRZk=",
"name": "63"
},
"children": [
{
"id": 7831,
"name": {
"a": 81,
"b": 0.2784254314743101,
"c": "xZur64o=",
"name": "94"
},
"children": []
},
{
"id": 6293,
"name": {
"a": 73,
"b": 0.32629523068959604,
"c": "lMkosP4=",
"name": "93"
},
"children": []
},
{
"id": 5253,
"name": {
"a": 13,
"b": 0.19240453242901923,
"c": "oOPZ3tA=",
"name": "5"
},
"children": []
}
]
}
]
}
And here to class to parse it into:
class Node
{
[JsonProperty]
int id;
[JsonProperty]
string name;
[JsonProperty]
List<Node> children;
}
As you can see it expects a string name but sometimes incorrectly gets a serialised object (which contains the string in question as a member). This only happens in some JSON strings but not others, so I can't just change the class definition of Node to match.
[Edit 2]
A "correct" Json string according to my API would look like this:
{
"id": 2623,
"name": "22",
"children": [
{
"id": 3742,
"name": "50",
"children": [
{
"id": 1551,
"name": "9",
"children": []
},
{
"id": 4087,
"name":"84",
"children": []
},
...

Trying to detect errors after the fact and then reparse from the point of the error is going to be problematic, as you have seen. Fortunately, the problem you've described can be solved in a straightforward manner using a custom JsonConverter. The idea is to have the converter read the data into a temporary structure that can handle either form (object or string), query the type, then construct the Node from there.
Here is the code for the converter:
class NodeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Node));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Node node = new Node();
node.id = (int)jo["id"];
JToken name = jo["name"];
if (name.Type == JTokenType.String)
{
// The name is a string at the current level
node.name = (string)name;
}
else
{
// The name is one level down inside an object
node.name = (string)name["name"];
}
node.children = jo["children"].ToObject<List<Node>>(serializer);
return node;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the converter, add a [JsonConverter] attribute to your Node class like this:
[JsonConverter(typeof(NodeConverter))]
class Node
{
public int id { get; set; }
public string name { get; set; }
public List<Node> children { get; set; }
}
Then you can deserialize as normal:
Node node = JsonConvert.DeserializeObject<Node>(json);
Here is a full demo showing the converter in action. For illustration purposes, I've created a new JSON string that contains a combination of the "good" and "bad" nodes you described in your question.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""id"": 2623,
""name"": {
""a"": 39,
""b"": 0.49053320637463277,
""c"": ""cai5z+A="",
""name"": ""22""
},
""children"": [
{
""id"": 3741,
""name"": ""50"",
""children"": [
{
""id"": 1550,
""name"": ""9"",
""children"": []
},
{
""id"": 4088,
""name"": {
""a"": 5,
""b"": 0.42905938319352427,
""c"": ""VQ+yH6o="",
""name"": ""85""
},
""children"": []
}
]
},
{
""id"": 3742,
""name"": {
""a"": 37,
""b"": 0.19319664789046936,
""c"": ""Me/KKPY="",
""name"": ""51""
},
""children"": [
{
""id"": 1551,
""name"": {
""a"": 47,
""b"": 0.6935373953047849,
""c"": ""qkGkMwY="",
""name"": ""10""
},
""children"": []
},
{
""id"": 4087,
""name"": ""84"",
""children"": []
}
]
}
]
}";
Node node = JsonConvert.DeserializeObject<Node>(json);
DumpNode(node, "");
}
private static void DumpNode(Node node, string indent)
{
Console.WriteLine(indent + "id = " + node.id + ", name = " + node.name);
foreach(Node child in node.children)
{
DumpNode(child, indent + " ");
}
}
}
Output:
id = 2623, name = 22
id = 3741, name = 50
id = 1550, name = 9
id = 4088, name = 85
id = 3742, name = 51
id = 1551, name = 10
id = 4087, name = 84

Related

Deserializing Json to Obj with two same variables in Json

Does someone know how to handle JSON string deserialize in C#, which has values string&List simultaneously?
Example:
{
"data": {
"productStream": {
"edges": [{
"node": {
"attributeList": {
"edges": [{
"node": {
"selectAttributeValueTranslations": [{
"value": {
"name": [{
"value": "test"
},
{
"value": null
}
]
}
}]
}
},
{
"node": {
"attribute": {
"code": "example"
},
"imageAttributeValueTranslations": [{
"value": {
"name": "test.jpg",
"extension": "jpg"
}
}]
}
}
]
}
}
}]
}
}
}
And when I'm creating an object to deserialize it I have no idea how to declare this name.
public List Name doesn't work for "name" = "example.jpg". Otherwise public string Name doesn't work for "name"[ value = "example"]
I had to add another class based on JSON Data which contains a value for all attributes and the second class contains a value for only one attribute.

Filtering Nested array with linq

I have an array like this coming from API response and I want to filter by nested property products to returns product with only id 11
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
},
{
"id": "14",
"status": null
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
},
{
"id": "28",
"status": null
}
]
},
{
"name": "123",
"products": [
{
"id": "48",
"status": null
}
]
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
the resulting output should look like this,
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "123",
"products": []
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
I'm trying to return filter output with the C# LINQ method. but, getting an error when filtering a nested array with the following method
"Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type"
assets.Select(asset => asset.products.Where(product => product.id == "11")).ToArray();
You can just remove all items from every products array, whose id doesn't equal 11. Parse JSON to JObject, iterate over assets array, find products with id different from 11 and remove them form original array
var json = JObject.Parse(jsonString);
foreach (var asset in json["assets"])
{
var productsArray = asset["products"] as JArray;
if (productsArray == null)
continue;
var productsToRemove = productsArray
.Where(o => o?["id"]?.Value<int>() != 11)
.ToList();
foreach (var product in productsToRemove)
productsArray.Remove(product);
}
Console.WriteLine(json);
Instead of Select and Applying a, Where try to remove the Products that you do not want.
Following is a sample code.
class Program
{
static void Main(string[] args)
{
var json = #"
{
'assets': [
{
'name': 'abc',
'products': [
{
'id': '11',
'status': true
},
{
'id': '14',
'status': null
}
]
},
{
'name': 'xyz',
'products': [
{
'id': '11',
'status': true
},
{
'id': '28',
'status': null
}
]
},
{
'name': '123',
'products': [
{
'id': '48',
'status': null
}
]
},
{
'name': '456',
'products': [
{
'id': '11',
'status': true
}
]
}
]}";
var root = JsonConvert.DeserializeObject<Root>(json);
var assets = root.Assets;
assets.ForEach(a =>
{
a.Products.RemoveAll(p => p.Id != 11);
});
}
}
public partial class Root
{
[JsonProperty("assets")]
public List<Asset> Assets { get; set; }
}
public partial class Asset
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("products")]
public List<Product> Products { get; set; }
}
public partial class Product
{
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("status")]
public bool? Status { get; set; }
}
To filter the list, you will need to create a new list with products whose ID == YourId. Following linq query does the necessary steps to create the list you want.
Filter out any assets that dont have any product with ID == 11. This is necessary to skip nulls in the new list that will be generated (Where statement)
Create a collection / list of new assets that only have the products whose ID == 11 (Select statement)
var list = assets.Where(x => x.Products.Where(y => y.Id.Equals("11")).Count() > 0)
.Select(asset => {
return new Asset() {
Name = asset.Name,
Products = asset.Products.Where(x => x.Id == "11").ToList()
};
}).ToList();
// Create the RootObject that holds a list of all the arrays if you want.
Rootobject newAssetCollection = new Rootobject() { Assets = list };
Below is the Json that was printed
Console.WriteLine(JsonConvert.SerializeObject(newAssetCollection, Formatting.Indented));
{
"assets": [
{
"name": "abc",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "xyz",
"products": [
{
"id": "11",
"status": true
}
]
},
{
"name": "456",
"products": [
{
"id": "11",
"status": true
}
]
}
]
}

jsonResult with collectionname

Hi I would like to use the json collection from the code below in an angular repeater.
I need to give the collection a name but i dont know how
this is the code to generate the JSON
public JsonResult GetProducts()
{
var result = db.Products.ToList();
var list = JsonConvert.SerializeObject(result, Formatting.None, new JsonSerializerSettings(){ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});
return Json(list,JsonRequestBehavior.AllowGet);
}
and this is the result
[{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 10006,
"Description": "TEST",
"Name": "TEST",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-11T14:52:57.677"
},
{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 20005,
"Description": "TEST2",
"Name": "TEST2",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-12T12:02:10.593"
}]
and I would like it to be like the code below so I can use the products tag to iterate .
{
"products": [{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 10006,
"Description": "TEST",
"Name": "TEST",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-11T14:52:57.677"
},
{
"Category": {
"ID": 4,
"Name": "TEST"
},
"OrderDetails": [],
"ID": 20005,
"Description": "TEST2",
"Name": "TEST2",
"Price": 3.20,
"PictureUrl":"",
"CategoryId": 4,
"AddedToShop": "2016-12-12T12:02:10.593"
}]}
If you are using WebAPI controllers you don't need to send JsonResult, all you need to do is create a class and send it back to client:
public class MyClass
{
public List<Product> Products { get; set; }
}
And in your controller:
public MyClass Get() {
return new MyClass {
Products = db.Products.ToList()
};
}
OR:
Simply create your object and then pass it in the serializer:
JsonConvert.SerializeObject( { "products": result }, Formatting.None, new JsonSerializerSettings(){ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore})
You can use
var list = JsonConvert.SerializeObject(new { products = result }, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore });
One solution for your problem is, define a seperate class and define a property for that class as either a collection or list of the type "db.Products"(Use the appropriate class name for this object as you have in your code) and when you serialize that object you will get it as you have asked for
class ProductList
{
public Collection<YourType> Products{get; set;}
}
OR
class ProductList
{
public List<YourType> Products{get; set;}
}

Deserialize JSON string to object that's also in the JSON

{
"Cats": [
{
"Name": "Max",
"Color": "Brown"
},
{
"Name": "Cherry",
"Color": "Black"
}
],
"Owners": [
{
"Name": "Tom",
"Cat": "Max"
},
{
"Name": "Cindy",
"Cat": "Cherry"
}
]
}
Instead of getting the string value of Tom's Cat, and then doing a second operation to get the cat's color, is it possible to deserialize this JSON so that I can just do:
var tomsCatsColor = Rootobject.Owners[0].Cat.Color;
You can give the user a cat identifier, this will look something like:
{
"Cats": [
{
"Name": "Max",
"Color": "Brown"
},
{
"Name": "Cherry",
"Color": "Black"
}
],
"Owners": [
{
"Name": "Tom",
"Cat": 0
},
{
"Name": "Cindy",
"Cat": 1
}
]
}
var tomsCatsColor = Rootobject.Cats[Rootobject.Owners[0].Cat].Color;

How can I display a property value infront of each JSON array item?

Here is the JSON string that I am working with.
{
"id": 1,
"title": "A Test",
"items": [
{
"id": 157,
"title": "some article",
"type": "Article"
},
{
"id": 153,
"title": "some other article",
"type": "Article"
}
]
}
I am using Json.Net for serialization. Is there anyway that I can format the JSON like this before displaying?
{
"id": 1,
"title": "A Test",
"items": [
"157" : {
"title": "some article",
"type": "Article"
},
"153" : {
"title": "some other article",
"type": "Article"
}
]
}
Thanks in advance.
You can get pretty close to the output you want using Json.Net's LINQ-to-JSON API (JObjects) to transform the original JSON. Here is one way to do it:
public static string Transform(string json)
{
JObject root = JObject.Parse(json);
JObject itemsObj = new JObject();
foreach (JObject item in root["items"])
{
JToken id = item["id"];
id.Parent.Remove();
itemsObj.Add(id.ToString(), item);
}
root["items"].Parent.Remove();
root.Add("items", itemsObj);
return root.ToString();
}
If you pass your original JSON to this method, you will get the following output:
{
"id": 1,
"title": "A Test",
"items": {
"157": {
"title": "some article",
"type": "Article"
},
"153": {
"title": "some other article",
"type": "Article"
}
}
}
Fiddle: https://dotnetfiddle.net/1di41P

Categories