parsing through a dynamic json - c#

I'm building an import system that will used for importing product from various vendors, and the app has to conform to whatever the vendor gives us, so there's no normalized column. I have a mongodb collection has a mapping of what goes to what
for example, vendor A has sku binded to code but vendor B may call it itemCode
so when I'm parsing my json data, how would I be able to just dynamically tell my app that sku is that field?
I'd like to be able to do like what I'm doing for vendors using xml which is like
doc.LoadXml(content);
XmlNodeList itemPath = doc.SelectNodes(Config.XmlItemPath);
foreach (XmlNode item in itemPath)
{
Console.WriteLine(item[MapToValue("CurrencyCode")]?.InnerText);
}
I haven't seen such a way with Json.NET so I'm sorta lost on how I can easily parse through this data.

Have a look at Json.Net's LINQ-to-JSON API. You could write very similar code with it:
JToken root = JToken.Parse(jsonContent);
IEnumerable<JToken> itemTokens = root.SelectTokens(Config.JsonItemPath);
foreach (JToken item in itemTokens)
{
Console.WriteLine(item[MapToValue("CurrencyCode")]?.ToString());
}
API Reference

I decided to just convert the json to xml seeing as xml has a bit more flexibility in .NET
var doc = JsonConvert.DeserializeXmlNode(content, "root");
XmlNodeList itemPath = doc.SelectNodes(Config.XmlItemPath);
if (itemPath == null) throw new Exception("Invalid XML Path.");
{
foreach (XmlNode row in itemPath)
{
Console.WriteLine(row[GetJsonProperty("Brand")]?.InnerText);
}
}
this does exactly what I want now, I'm open to hear how to do it without converting to xml but this will get me moving onward with my work.

Related

Capture Invalid XML

I have the following code I'm using to parse XML strings that contain collections of objects. I want to capture the XML for any given object as a string if I can't parse it. I want store it and analyze it if it won't parse correctly. I would prefer not to make a radical change, but I can't figure out how to grab that part of the XML which is invalid and capture it. xmlReader.ReadOuterXml() throw an exception when it can't parse. Thanks in advance.
// we want to read each canonical item in the report from oracle separately
while (xmlReader.ReadToFollowing(ROOT_ELEMENT))
{
string itemXml = String.Empty;
try
{
// this gives us the whole segment of xml including the root element tag
itemXml = xmlReader.ReadOuterXml();
xmlReader.
T processedItem = default;
processedItem = reportMapper.Mapper(itemXml);
successfulItems.Add(new ProcessingResult<T>()
{
ProcessedItem = processedItem
});
}

How to read the json data

I have the following json data, I need to read the data from it and them perform some comparisons.
{"expiration": "2013-04-01T00:00:00Z",
"conditions": [
{"bucket": "the-s3-bucket-in-question"},
["starts-with", "$key", "donny/uploads/"],
{"acl": "private"},
["eq", "$Content-Type", "text/plain"],
["starts-with", "x-amz-meta-yourelement", ""],
["content-length-range", 0, 1048576]
]
}
By using the following code I have found the first element
var policy = Encoding.UTF8.GetString(policyByteArray);
JObject obj = JObject.Parse(policy);
string policyexpiration = obj.First.First.Path;
I have used JToken for finding all the conditions but I am getting only one element in that array. Can you please help me to get all the elements present in the conditions.
Following is the way I have used JToken
JToken entireJson = JToken.Parse(policy);
var items = entireJson["conditions"].Value<JArray>()[0];
XmlDocument xdoc = (XmlDocument)JsonConvert.DeserializeXmlNode(items.ToString(), "root");
XmlNode xmlarray = xdoc.GetElementsByTagName("root")[0];
foreach (XmlNode xmlelement in xmlarray)
{
}
I'm not sure why you need the XML tools at all. Iterating the "conditions" in your json structure is simple with Json.NET and you're already very close.
var items = entireJson["conditions"].Value<JArray>();
foreach (JToken condition in items)
{
//do work on one condition here
}
Note that this json structure is a little odd. A single condition can either be an array such as ["starts-with", "$key", "donny/uploads/"] OR an object such as {"bucket": "the-s3-bucket-in-question"}.
Depending what you want to do with the conditions, you may need to distinguish between the two. You can use C#'s is operator with the Json.NET types, like this:
var items = entireJson["conditions"].Value<JArray>();
foreach (JToken condition in items)
{
if (condition is JArray)
{
//do something with the array
}
else if (condition is JObject)
{
//do something with the object
}
}
You are you only getting one element because you are selecting the value at index 0 of the JArray
var items = entireJson["conditions"].Value<JArray>()[0];
Instead of using the Value method, use Values which returns an IEnumerable
var items = entireJson["conditions"].Values<JObject>();
As far as I know DeserializeXmlNode() only accept string that represent JSON object. That's why it worked when you passed only the first value of conditions property :
var items = entireJson["conditions"].Value<JArray>()[0];
XmlDocument xdoc = JsonConvert.DeserializeXmlNode(items.ToString(), "root");
But throwing exception if you pass the entire value or the second value of conditions because both represent JSON array instead of JSON object :
//pass entire value
var items = entireJson["conditions"].Value<JArray>();
//or pass the second value : ["starts-with", "$key", "donny/uploads/"]
var items = entireJson["conditions"].Value<JArray>()[1];
XmlDocument xdoc = JsonConvert.DeserializeXmlNode(items.ToString(), "root");
It isn't clear what kind of XML format you want to create from given JSON string. But just to make it work, you can try to create another JSON object having one property named conditions with value copied from the initial JSON :
var items = entireJson["conditions"].Value<JArray>();
var newObject = string.Format("{{conditions : {0}}}", items.ToString());
XmlDocument xdoc = (XmlDocument)JsonConvert.DeserializeXmlNode(newObject, "root");

Parsing large json

I'm trying to parse a json file and then operate on it to insert data into a SQL Server 2008 Database.
Example:
var sr = new StreamReader("C:\\path to file\file.json");
var json = JsonSerializer.SerializeToString(sr);
var o = JsonObject.Parse(json);
But I always get this error at the second line - "Timeouts are not supported on this stream."
The Json file looks like this:
"main":{
"prg": [
{
"Id": 1,
"name": "A&E",
more fields
}
"prg": [
{
"Id": 2,
"name": "asda",
more fields
}
}
I need to make something like this
foreach (prg in main)
entity.id = prg.id
entity.name = prg.name
How can I do this and why I get that timeout exception?
EDIT: To better understand my question this is how I do for an XML file
XmlDocument sourceDoc = new XmlDocument();
sourceDoc.Load(SourcesElement2); // where SourcesElement2 is the path to my XML
XmlNodeList prg = sourceDoc.GetElementsByTagName("prg");
foreach (XmlNode item in prg)
{
entity.Name= item.SelectSingleNode("name").InnerText;
...
}
I have converted the XML to Json and I want to do same thing. For every "prg" node in the Json File insert a new item in the database
EDIT2:
This is what I've done.
using (
StreamReader stream =
File.OpenText(
"C:\\path\\Sources.json")
)
{
JObject sources = (JObject) JToken.ReadFrom(new JsonTextReader(stream));
var a = sources["on"];
var b = a["sources"];
var c = b["prgs"];
foreach (var item in c)
{
var d= item.SelectToken("prg");
// Here d is null
}
I have the same question as the one from above. For every "prg" node in the Json File insert a new item in the database. How can I do this ? ( path to prg is on/sources/prgs/ )
I don't think you want to serialize the stream.
JsonSerializer.SerializeToString(sr)
You want to deserialize from the stream.
JsonSerializer.Deserialize
You might want to use JsonReader for performance reasons.
Your XML example load the whole file in the memory - you don't want to do that for large documents. reader.Read() pattern is better suited for processing large files.
For everyone with the same problem here is what I've done ( NOTE: this is just an example )
By the way, thank you for everyone who tried to answer my question and I'm sorry for my mistakes.
List<string> d = new List<string>();
using (
StreamReader stream =
File.OpenText(
"C:\\path\\Sources.json")
)
{
JObject sources = (JObject) JToken.ReadFrom(new JsonTextReader(stream));
var a = sources["on"];
var b = a["sources"];
var c = b["prgs"];
foreach (JObject item in c["prg"].ToList())
{
d.Add(item.Value<string>("name"));
}
}
//part below is just for testing
foreach (var VARIABLE in d)
{
Console.WriteLine(VARIABLE);
}
Console.ReadLine();
I would approach it by converting that JSON object into a C# class and then applying logic to the C# object and/or use DataTables
[Note: There are solutions online that show you would to easily pass an Object or List<Object> into a DataTable and then pass it "easily" to SQL]
The first step is still your hiccup in either solution, how do I pull in a large JSON string from filesystem?
if you have the JSON, use json2csharp.com and/or jsonutils.com to retrieve the classes in order to Deserialize it to your object.
StreamReader re = new StreamReader(#"C:\path to file\file.json");
JsonTextReader reader = new JsonTextReader(re);
YourClass DeserializedObject = se.Deserialize<YourClass>(reader);
Console.WriteLine(DeserializeObject.SomeProperty);

converting graph api for xml

I'm having trouble converting a string of json facebook graph api, I used the facebook C# and json.Net.
But at conversion time it returns this error: Name can not begin with the '0 'character, hexadecimal value 0x30.
This is the code:
dynamic result = await _fb.GetTaskAsync ("me / feed");
FBxml JsonConvert.DeserializeXNode string = (result.ToString ()). ToString ();
It looks like there is a problem with portion of the json string as mentioned below (taken from your link http://jsfiddle.net/btripoloni/PaLC2/)
"story_tags": {
"0": [{
"id": "100000866891334",
"name": "Bruno Tripoloni",
"offset": 0,
"length": 15,
"type": "user"}]
},
Json cannot create class that begins with a numeric value such as '0'. Try creating the classes using the link http://json2csharp.com/ you will get an idea.
To solve this problem you can create a dynamic object and go through each properties OR create a JsonConverter and write your code in the ReadJson to convert the "0" to a meaningful name. May be this can help you http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization
If this is not your problem then update the question with more information like class structure of FBxml, call stack of the exception (from which line of the json code is throwing the exception), Json version etc.
As keyr says, the problem is with those JSON properties that have numeric names. In XML names can contain numeric characters but cannot begin with one: XML (see the Well-formedness and error-handling section).
My idea was to recursively parse the JSON with JSON.Net, replacing properties that had numeric names:
var jsonObject = JObject.Parse(json);
foreach (var obj in jsonObject)
{
Process(obj.Value);
}
XDocument document = JsonConvert.DeserializeXNode(jsonObject.ToString());
....
private static void Process(JToken jToken)
{
if (jToken.Type == JTokenType.Property)
{
JProperty property = jToken as JProperty;
int value;
if (int.TryParse(property.Name, out value))
{
JToken token = new JProperty("_" + property.Name, property.Value);
jToken.Replace(token);
}
}
if (jToken.HasValues)
{
//foreach won't work here as the call to jToken.Replace(token) above
//results in the collection modifed error.
for(int i = 0; i < jToken.Values().Count(); i++)
{
JToken obj = jToken.Values().ElementAt(i);
Process(obj);
}
}
}
This seemed to work well, prefixing numeric names with _. At this line:
XDocument document = JsonConvert.DeserializeXNode(jsonObject.ToString());
it crashed with an error saying that invalid/not well formed XML had been created. I don't have the actual error with me, but you can run the above code to replicate it.
I think from here you may need to revisit converting the JSON to XML in the first place. Is this a specific requirement?

Best practice create a C# Object which reflect and generate a Serialized string

This question may be asked or answered before but I feel that none of the hits really apply.
I would like to create a little class with attributes which will correspond to name and attributes in a output familiar to xml stream. The class should help the program to create a xml-alike string.
string test = "<graph caption='SomeHeader' attribute9='#someotherinfo'>" +
"<set name='2004' value='37800' color='AFD8F8' />" +
"<set name='2005' value='21900' color='F6BD0F' />" +
"<set name='2006' value='32900' color='8BBA00' />" +
"<set name='2007' value='39800' color='FF8E46' />" +
"</graph>";
I think you got the idea. I have a static amount of known attributes which will be used in the tags. The only Tags here is set and graph.
I would like to do something like this,
Helper o = new Helper()
List<Tag> tag = new List<Tag>();
foreach (var someitem in somedatabaseresult)
{
tag.Add(New Graph() { Caption = someitem.field , attribute9 = someitem.otherField });
foreach (var detail in someitem)
{
tag.Add(new Set() { name = detail.Year, value = detail.Value color = detail.Color });
}
}
o.Generate(); // Which will create the structure of result sample above
// and for future extension..
// o.GenerateXml();
// o.GenerateJson();
Please remember that this code is pesudo, just taken from my head. A result of that I have some ideas but it take a day to code and test what best (or whorse).
What would be best practice to solve this task?
[EDIT]
This mysterious "Helper" is the (unluckily typed) class who contains a list of Graph, a list of Set and also (what I think about) contains all available attributes per Graph/Set object. The work of the foreach-loops above are mentioned to fill the Helper class with the data.
[EDIT2]
Result here,
https://gist.github.com/1233331
Why not just create a couple of classes: Graph and Set. Graph would have a property of List<Set>.
In your foreach you can then create an instance or Graph and add instances of Set to its list.
When you're done use the XML Serializer to serialize the Graph object out to XML. Nice and easy to then output to another format as well if your needs change later e.g. serialize to JSON.
Edit following comment:
From top my head so may not be 100% correct...
var myGraph = BuildMeAGraph();
var serializer = new XmlSerializer(typeof(Graph));
var writer = XmlWriter.Create("myfile.xml");
serializer.Serialize(writer, myGraph);
But something like that should write it out to a file. If you want the XML in memory then write it out to an XMLTextWriter based on a memory stream instead and then you can write the contents to a string variable or do whatever you need with it.
If you want to create an XML, out of the object tree, then I think, you could try this:
XDocument doc = new XDocument
(
somedatabaseresult.Select
( someitem =>
new XElement("graph",
new XAttribute("Caption", ""),
new XAttribute("attribute9", "#something"),
someitem.Select
(detail =>
new XElement("Set",
new XAttribute("name", "2003"),
new XAttribute("value", "34784"),
new XAttribute("color", "#003300")
)
)
);
//save to file as XML
doc.Save("output.xml");
//save to local variable as XML string
string test = doc.ToString();
I wrote the save value for tags, as you've used in your code. However, I think you would like this:
new XAttribute("name", detail.name),
new XAttribute("value", detail.value),
new XAttribute("color", detail.color)
Or whatever value you want to give to each attribute from the object detail.
Use the XmlSerializer.
I'd use the ExpandoObject, but I don't see the reason for what you are doing.

Categories