I am calling a web service that returns JSON with a duplicate node in some circumstances, providing output similar to this:
{
"shipments": [
{
"id": "A000001",
"name": "20141208 140652",
"type": "OUTLET",
"date": "2014-12-08 14:06:52",
"status": "SENT",
"received_at": null,
"created_at": "2014-12-08 14:06:52",
"updated_at": null,
"outlet_id": "SH000064"
},
{
"id": "A000002",
"name": "20141204 122650",
"type": "SUPPLIER",
"date": "2014-12-04 12:26:50",
"outlet_id": "SH000064",
"supplier_id": null,
"status": "RECEIVED",
"outlet_id": "SH000064",
"received_at": "2014-12-04 12:28:43",
"created_at": "2014-12-04 12:26:50",
"updated_at": "2014-12-04 12:28:43"
}
]
}
I am dependent on the provider of the service to fix this and this is not a priority for them so I have to deal with it. To handle this I am converting the JSON to XML, using the JsonReaderWriterFactory, and then removing the duplicate nodes from the resulting XML using the following routine:
protected virtual void RemoveDuplicateChildren(XmlNode node)
{
if (node.NodeType != XmlNodeType.Element || !node.HasChildNodes)
{
return;
}
var xNode = XElement.Load(node.CreateNavigator().ReadSubtree());
var duplicateNames = new List<string>();
foreach (XmlNode child in node.ChildNodes)
{
var isBottom = this.IsBottomElement(child); // Has no XmlNodeType.Element type children
if (!isBottom)
{
this.RemoveDuplicateChildren(child);
}
else
{
var count = xNode.Elements(child.Name).Count();
if (count > 1 && !duplicateNames.Contains(child.Name))
{
duplicateNames.Add(child.Name);
}
}
}
if (duplicateNames.Count > 0)
{
foreach (var duplicate in duplicateNames)
{
var nodeList = node.SelectNodes(duplicate);
if (nodeList.Count > 1)
{
for (int i=1; i<nodeList.Count; i++)
{
node.RemoveChild(nodeList[i]);
}
}
}
}
}
I now in a separate area need to use the DataContractJsonSerializer to deserialise the JSON to a strongly typed object, using the following code:
DataContractJsonSerializer serialiser = new DataContractJsonSerializer(typeof(ShipmentList));
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
var result = serialiser.ReadObject(stream);
This fails when the JSON contains that duplicate node, so I need to implement the same functionality as in the RemoveDuplicateChildren method but stepping through the JSON instead of an XML node, before the deserialisation. I can't use the quick-and-dirty option of using JsonConvert to convert to XML, removing the node with my existing method, then converting back to JSON because of the changes in the JSON that will result from the conversion to and from XML. Is there an equivalent way of navigating through a JSON hierarchy in C# as is provided by the XmlNode class?
UPDATE:
This question has become obfuscated by some of the comments. To clarify, the nodes I want to remove from the JSON are any nodes that are a repeat (by name, the content is irrelevant) at the same level of the same parent, such as the second "outlet_id" of the second "shipments" item in the example above. I need to do this in a generic way without hard coded element names. The RemoveDuplicateChildren method above does exactly what is needed, I'm just asking if there is a class I can use to do exactly the same as that method on a JSON string instead of an XML string.
Related
I've been trying to insert values from one json file into another. These structures have mostly been decided on, so I want to try and make them work as they are.
The problem i've been running into is that the provided paths may not always have the same depth and thus the coded used to insert values into that provided path needs to be flexible enough to deal with that.
These are the json files:
this json holds values, the paths to where those values should be placed in the other json file and when applicable, the index.
[
{
"path": "x_axis.data",
"value": "['','2005','2010','2015','2021','2026']",
"index": null
},
{
"path": "series.data",
"value": "[77.0,70.0,74.0,0.0,0.0,0.0]",
"index": 0
},
{
"path": "series.data",
"value": "[0.0,0.0,0.0,99.0,100.0,100.0]",
"index": 1
}
]
This json is a template structure that will be reused. it' based on the Echarts documentation, since we use that library to show charts.
{
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "shadow"
}
},
"legend": {
"data": [ "One", "Two" ]
},
"grid": {
"left": "3%",
"right": "4%",
"bottom": "3%",
"containLabel": true
},
"xAxis": [
{
"type": "category",
"data": []
}
],
"yAxis": [
{
"type": "value"
}
],
"series": [
{
"name": "One%",
"type": "bar",
"stack": "Stack 1",
"emphasis": {
"focus": "series"
},
"itemStyle": {
"color": "blue"
},
"data": []
},
{
"name": "Two%",
"type": "bar",
"stack": "Stack 1",
"emphasis": {
"focus": "series"
},
"itemStyle": {
"color": "red"
},
"data": []
}
]
}
Currently my method converts the path into a string array with text formatting that matches the names in the template json. I am however unsure how to use it or whether it was the right choice.
Here is my code:
public static string GenerateChart(string templateString, string replacementValuesString)
{
string result = "";
dynamic templateJson = JObject.Parse(templateString);
dynamic replacementJson = JArray.Parse(replacementValuesString);
foreach (var item in replacementJson)
{
string[] pathArr = CreatePathArray(item);
//check for index
if (item.index != null)
{
//TODO: Implement code that handles the index
templateJson.path = item.value;
}
else
{
templateJson.path = item.value;
}
}
result = templateJson.ToString();
return result;
}
I used Newtonsoft to parse the json. My team decided against using models to deserialise the Json because they wanted to be able to only edit the json files for maintenance of the program.
My question to anyone willing to help is how would you go about solving this problem?
If I've failed to explain bits properly please let me know so I can clarify.
Thank you for your help!
Edit 1: I forgot to mention how the path is split into the array. It's split at the period, so "x_axis.data" becomes "["xAxis", "data"]".
Thanks to E. Shcherbo for helping me find a solution
I ended up modifying the code he suggested to fit my project as a few minor things were causing issues. Here's the code I ended up using and will now continue to polish:
var replacementJson = JArray.Parse(replacementValuesString);
JToken templateJson = JObject.Parse(templateString);
foreach (var item in replacementJson)
{
string[] pathArr = CreatePathArray(item);
//The method using ^1 provided by E. Shcherbo didn't work for me here.
//I'm guessing it's because I'm working in an old version of dotnetframework.
var valuePropName = pathArr[pathArr.Length - 1];
//Using this loop to "dive deeply" into the template json as E. put it, we find the correct path and then modify it.
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson == null)
{
Console.WriteLine("Template path given is invalid.");
break;
}
}
// if my item contains a value for index, it will be used. Otherwise it's safe to assume we're not dealing with an array.
if (item["index"] != null)
{
UpdateValueInTemplate(templateJson, item["index"], valuePropName, item["value"]);
}
else
{
UpdateValueInTemplate(templateJson, valuePropName, item["value"]);
}
templateJson = templateJson.Root;
}
First of all you probably don't need dynamic type here, because you know types of your objects at compile time JObject, JArray, JToken, so this is much safer to use them (you can go with var).
I'll describe two approaches I see here (both of them reuse the UpdateValueInTemplate method).
Note: be careful with different edge cases which might be possible depending on what you can and can't assume about your data.
Giving your approach with an array which represents the path, you can just iterate through the array and go deeply and deeply in your templateJson. Something like this
JToken templateJson = JObject.Parse(templateString);
var valuePropName = pathArr[^1]; // ^1 means first from the end
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson is null)
// The path is wrong. Do whatever validation is expected
}
UpdateValueInTemplate(templateJson, valuePropName, item["value"]);
You can also pass your path from replacementJson (without the last property to get an object where the last property is rather than value of the property) into the SelectToken method. Something like this:
var path = item["path"].ToString();
var lastPathSplitter = item.Path.LastIndexOf('.');
var pathWithoutLastProperty = path.Substring(0, lastPathSplitter);
var valPropName = item.Path.Substring(lastPathSplitter + 1);
var jsonToUpdate = templateJson.SelectToken(pathWithoutLastProperty);
if (jsonToUpdate is null)
// The path is wrong. Do whatever validation is expected
UpdateValueInTemplate(jsonToUpdate, valPropName, item["value"]);
Then the UpdateValueInTemplate method can look like this:
void UpdateValueInTemplate(JToken tokenToUpdate, string valPropName, JToken value) {
tokenToUpdate[valPropName] = value;
}
You can also reduce manipulations with your path if you pass the entire path to the SelectToken and then will use yourToken.Parent.Parent[valPropName] = value to update the json.
UPDATE
I didn't notice that you also want to handle cases where the first property of the path can point to the array. So when this is the case it can be handled like this:
JToken templateJson = JObject.Parse(templateString);
var valuePropName = pathArr[^1]; // ^1 means first from the end
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson is null)
{
// The path is wrong. Do whatever validation is expected
}
else if (templateJson.Type == JTokenType.Array)
{
int index = item["index"].ToObject<int>(); // you may want to validate the index presence and format
templateJson = templateJson[index];
}
}
For approach with SelectToken you can either change your replacement json to match the format SelectToken understands or create a new path based on the index when it's not null:
var firstPropSplitter = path.IndexOf('.');
var firstProperty = path.Substring(0, firstPropSplitter);
var restPath = path.Substring(firstPropSplitter + 1);
var index = item["index"];
var newPath = $"{firstProperty}[{index}].{restPath}";
I have a business case where I need to take any incoming JSON payload (so the JSON object will have to be dynamic, not predefined by a C# class) and prepend a given namespace to all its containing keys.
For example if the following payload comes in:
{
"property1": "value1",
"property2": 2,
"property3": true,
"property4": {
"myArray": [
{
"arrayProperty1": "im the first object in array",
"arrayProperty2": "some value"
},
{
"arrayProperty1": "im the second object in array",
"arrayProperty2": "some value"
}
]
}
}
Then it needs to result in the following output:
{
"mynamespace.property1": "value1",
"mynamespace.property2": 2,
"mynamespace.property3": true,
"mynamespace.subObj": {
"mynamespace.myArray": [
{
"mynamespace.arrayProperty1": "im the first object in array",
"mynamespace.arrayProperty2": "some value"
},
{
"mynamespace.arrayProperty1": "im the second object in array",
"mynamespace.arrayProperty2": "some value"
}
]
}
}
Is this possible using C#? I tried searching for any similar question here on stackoverflow but this is the closest I got (they're using javascript): Prepending namespace to all of a JSON object's Keys
You can make a short helper method using Json.Net's LINQ-to-JSON API (JTokens) to accomplish this:
public static string AddPrefixToAllKeys(string json, string prefix)
{
JContainer token = (JContainer)JToken.Parse(json);
// Note: We need to process the descendants in reverse order here
// to ensure we replace child properties before their respective parents
foreach (JProperty prop in token.Descendants().OfType<JProperty>().Reverse().ToList())
{
prop.Replace(new JProperty(prefix + prop.Name, prop.Value));
}
return token.ToString();
}
Then use it like this:
string modifiedJson = AddPrefixToAllKeys(originalJson, "mynamespace.");
Working demo here: https://dotnetfiddle.net/AdkAO7
I have a JSON string like below:
{
"MetaData": {
"ResourcesUsed": 1
},
"Result": [
{
"locations": [
{
"country": "Papua New Guinea",
"city": "Jacquinot Bay",
"locTypeAttributes": {
"localDate": "2018-10-08T04:21:00-07:00",
"utcDate": "2018-10-08T04:21:00-07:00",
},
"point": {
"coordinates": [
151.52,
-5.6
],
"type": "Point"
}
},{
"country": "Papua New Guinea2",
"city": "Jacquinot Bay2",
"locTypeAttributes": {
"localDate": "2018-10-08T04:21:00-07:00",
"utcDate": "2018-10-02T04:21:00-07:00",
},
"point": {
"coordinates": [
151.52,
-5.6
],
"type": "Point"
}
}
]
}
]
}
I converted it to a JSON object using Newtonsoft. What I want to do is to sort the locations array(s) inside the Result array by the utcDate field nested in each locations item. I found the following thread: C# Sort JSON string keys. However, I could not still implement it since I have arrays inside my object, while that question deals purely with sorting objects inside objects alphabetically by property name.
Here is a piece of code that I wrote so far:
public string GenerateJson()
{
var model = (JObject)JsonConvert.DeserializeObject(data);
Sort(model);
}
private void Sort(JObject jObj)
{
var props = jObj["Result"][0]["locations"].ToList();
foreach (var prop in props)
{
prop.Remove();
}
foreach (var prop in props.OrderBy(p => p.Name))
{
jObj.Add(prop);
if (prop.Value is JObject)
Sort((JObject)prop.Value);
if (prop.Value is JArray)
{
Int32 iCount = prop.Value.Count();
for (Int32 iIterator = 0; iIterator < iCount; iIterator++)
if (prop.Value[iIterator] is JObject)
Sort((JObject)prop.Value[iIterator]);
}
}
}
You can sort each individual "Result[*].locations" array using LINQ as follows:
// Load the JSON without parsing or converting any dates.
var model = JsonConvert.DeserializeObject<JObject>(data, new JsonSerializerSettings{ DateParseHandling = DateParseHandling.None });
// Construct a serializer that converts all DateTime values to UTC
var serializer = JsonSerializer.CreateDefault(new JsonSerializerSettings{ DateTimeZoneHandling = DateTimeZoneHandling.Utc });
foreach (var locations in model.SelectTokens("Result[*].locations").OfType<JArray>())
{
// Then sort the locations by utcDate converting the value to UTC at this time.
var query = from location in locations
let utcDate = location.SelectToken("locTypeAttributes.utcDate").ToObject<DateTime>(serializer)
orderby utcDate
select location;
locations.ReplaceAll(query.ToList());
}
Notes:
The JSON is initially loaded using DateParseHandling.None to prevent the "localDate" and "utcDate" strings from being prematurely interpreted as DateTime objects with a uniform DateTime.Kind.
(For a discussion of how Json.NET interprets strings that look like dates, see Serializing Dates in JSON.)
We then iterate through all "locations" arrays using SelectTokens("Result[*].locations") where [*] is the JSONPath wildcard character, selecting all entries in the "Results" array.
We then order each "locations" array by deserializing the nested locTypeAttributes.utcDate to a UTC date, then ordering using LINQ.
Finally the array is updated using JArray.ReplaceAll().
If any locTypeAttributes.utcDate property is missing, an exception will be thrown. You could instead deserialize to DateTime? if that is a possibility.
Working sample .Net fiddle here.
I am converting XML to JSON.
Input:
<emp
id="17377"/>
<CustomerList>
<Customer
id="67149"/>
<Customer id="64260"/>
</CustomerList>
OutPut:
"emp": {
"id": "17377"
},
"CustomerList": {
"Customer": [
{
"id": "67149"
},
{
"id": "64260"
}
]
}
But I need the below output. But I can not remove <Customer from <CustomerList> in the input. Also Please note that I need accept dynamic name
of array input. But always i want to remove the inner property name to be removed. in this example its Customer.But I may get MarkList->Mark then I need to remove remove Mark, etc.,:
"emp": {
"id": "17377"
},
"CustomerList": [
{
"id": "67149"
},
{
"id": "64260"
}
]
Is this possible please.
I use below code to convert XML to Json:
var xml = new XmlDocument();
xml.XmlResolver = null;
xml.LoadXml(richTextBox1.Text);
var jsonText = JsonConvert.SerializeXmlNode(xml,Newtonsoft.Json.Formatting.Indented);
Note:
One solution would be find the char "[" and remove before "[" and after "{".
This is not possible, as it is simply trying to change to JSON scheme in which it was orignally built.
what you can do, is use JObject to change the value of customer to feet your needs:
JObject rss = JObject.Parse(json);
JObject customers = rss.SelectToken("CustomerList");
customers ["Customer"] = newValue;
This is the snippet, modify this in your code to feet your needs.
Found out that serialize XML object with single item and multiple item gives me different format, thus accessing it would be different.
For example, with single item:
{
"schemalist": {
"schema": {
"fieldid": "email",
"displayname": "email",
}
}
}
and
{
"schemalist": {
"schema": [
{
"fieldid": "name",
"displayname": "name",
},
{
"fieldid": "email",
"displayname": "email",
}
]
}
}
so when I parse them jQuery.ParseJSON, I have to access them differently
schemalist.schema.fieldid
and
schemalist.schema[0].fieldid
is there a way to convert to json even with single item, so I can use
schemalist.schema[0].fieldid even across single, multiple items?
thanks!
Edit:
Here's my xml input:
<schemalist>
<schema>
<fieldid><![CDATA[name]]></fieldid>
<displayname><![CDATA[name]]></displayname>
</schema>
<schema>
<fieldid><![CDATA[email]]></fieldid>
<displayname><![CDATA[email]]></displayname>
</schema>
but sometimes the return could have only one schema:
<schemalist>
<schema>
<fieldid><![CDATA[email]]></fieldid>
<displayname><![CDATA[email]]></displayname>
</schema>
</schemalist>
then I would do my conversion like this:
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return Newtonsoft.Json.JsonConvert.SerializeObject(doc);
well, what i end up doing was to remove the property and then add it again.
// run the code below with $(schemalist.schemalist.schema).length == 1
// create a schema array
var schema = [];
schema.push(schemalist.schemalist.schema);
// delete the property
delete schemalist.schemalist.schema;
schemalist['schema'] = schema;
so instead of accessing properties fieldid:
schemalist.schema.fieldid,
I can use
schemalist.schema[i].fieldid
even with one element, ha!