The bounty expires in 14 hours. Answers to this question are eligible for a +100 reputation bounty.
DeLorean wants to draw more attention to this question.
I have two json string namely left(original json) and right(edited json). I want to find the difference between left and right and create a new json with only the changes and specific key "ID" AND "TYPE" . I tried using "JsonDiffPatchDotNet" and "JsonDiffer" packages in c# but no luck could someone help me out with this. Here is the code i have tried !
PACKAGES
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using JsonDiffPatchDotNet;
using JsonDiffer;
CODE MINIFY
public static string Minify(string json)
{
if (json == null)
{
throw new ArgumentNullException(nameof(json));
}
return Regex.Replace(json, "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
}
CODE MAIN LOGIC
public static void Main(string[] args)
{
string Tleft = #"{
""TEST JSON"" : ""JSON"",
""JSON"":{
""ANIMALS"":[
{
""ID"":0,
""TYPE"":""DOG"",
""DOG"":{
""TYPE"":""RETRIEVER"",
""RETRIEVER"":{
""NAME"":""LEO"",
""AGE"":3,
""YEARS"":[2019 , 2020, 2021],
""WEIGHTS"": [2,10,13]
}
},
""REMARKS"":{
""ID"":1,
""STATUS"":""GOOD"",
""REFERENCE"": {
""SOURCE"": ""XYZ"",
""FIT"": 1,
""BMI"" : 1
}
}
},
{
""ID"":1,
""TYPE"":""DOG2"",
""DOG2"":{
""TYPE"":""PUG"",
""RETRIEVER"":{
""NAME"":""HUTCH"",
""AGE"":4,
""YEARS"":[2019 , 2020, 2021, 2022],
""WEIGHTS"": [2,3,4,4]
}
},
""REMARKS"":{
""ID"":1,
""TYPE"" : ""REFERENCE"",
""STATUS"":""GOOD"",
""REFERENCE"": {
""SOURCE"": ""XYZ"",
""FIT"": 1,
""BMI"" : 1
}
}
},
{
""ID"": 2,
""TYPE"": ""DIAGNOSTICS"",
""STATUS"": ""ENABLED""
},
{
""ID"": 3,
""TYPE"": ""ORGANISATION"",
""ORGANISATION"":{
""NAME"":""RED CROSS"",
""YEAR"": 2023
}
}
]
}
}";
string Tright = #"{
""TEST JSON"" : ""JSON"",
""JSON"":{
""ANIMALS"":[
{
""ID"":0,
""TYPE"":""DOG"",
""DOG"":{
""TYPE"":""RETRIEVER"",
""RETRIEVER"":{
""NAME"":""LEO"",
""AGE"":3,
""YEARS"":[2019 , 2020, 2021],
""WEIGHTS"": [2,10,13]
}
},
""REMARKS"":{
""ID"":1,
""STATUS"":""GOOD"",
""REFERENCE"": {
""SOURCE"": ""XYZ"",
""FIT"": 1,
""BMI"" : 1
}
}
},
{
""ID"":1,
""TYPE"":""DOG2"",
""DOG2"":{
""TYPE"":""PUG"",
""RETRIEVER"":{
""NAME"":""HUTCH"",
""AGE"":4,
""YEARS"":[2019 , 2020, 2021, 2022],
""WEIGHTS"": [2,3,4,4]
}
},
""REMARKS"":{
""ID"":1,
""TYPE"" : ""REFERENCE"",
""STATUS"":""GOOD"",
""REFERENCE"": {
""SOURCE"": ""XYZ"",
""FIT"": 1,
""BMI"" : 1
}
}
},
{
""ID"": 2,
""TYPE"": ""DIAGNOSTICS"",
""STATUS"": ""ENABLED ZZZ""
},
{
""ID"": 3,
""TYPE"": ""ORGANISATION"",
""ORGANISATION"":{
""NAME"":""RED CROSS ZZZ"",
""YEAR"": 2023
}
}
]
}
}";
var left = JToken.Parse(Minify(Tleft));
var right = JToken.Parse(Minify(Tright));
var jdp = new JsonDiffPatch();
// difference using JsonDiffPatchDotNet package
var diff2 = JsonDifferentiator.Differentiate(left, right, OutputMode.Symbol, showOriginalValues: true);
Console.WriteLine("--------------------------------------");
Console.WriteLine("----diff using JsonDiffPatchDotNet----");
Console.WriteLine("--------------------------------------");
Console.WriteLine(diff2);
Console.WriteLine("***************************************");
Console.WriteLine("***************************************\n\n");
if (diff2 != null)
{
// removing * charecter from the difference
var formatedDiff = JToken.Parse(diff2.ToString().Replace("*", ""));
// patching with diff2
// Error occurs here if ther are more than 3 changes .
var Diff2PatchOutput = jdp.Patch(left, formatedDiff).ToString();
Console.WriteLine("--------------------------------------");
Console.WriteLine("---------- CURRENT OUTPUT --------");
Console.WriteLine("--------------------------------------");
Console.WriteLine(Diff2PatchOutput);
Console.WriteLine("***************************************");
Console.WriteLine("***************************************\n\n");
}
else
{
Console.WriteLine("Diff failed or Invalid json inputs left and rigth !");
}
}
CURRETN OUPUT
--------------------------------------
----diff using JsonDiffPatchDotNet----
--------------------------------------
{
"*JSON": {
"*ANIMALS": [
{
"*STATUS": "ENABLED ZZZ"
},
{
"*ORGANISATION": {
"*NAME": "RED CROSS ZZZ"
}
}
]
}
}
***************************************
***************************************
--------------------------------------
---------- CURRENT OUTPUT --------
--------------------------------------
{
"TEST JSON": "JSON",
"JSON": {
"ANIMALS": {
"ORGANISATION": {
"NAME": "RED CROSS ZZZ"
}
}
}
}
***************************************
***************************************
I'm trying to get the output like shown below. Any help would be really appreciated !
EXPECTED OUTPUT
--------------------------------------
---------- EXPECTED OUTPUT --------
--------------------------------------
{
"TEST JSON": "JSON",
"JSON": {
"ANIMALS": [
{
"ID": 2,
"TYPE": "DIAGNOSTICS",
"STATUS": "ENABLED ZZZ"
}
{
"ID": 3,
"TYPE": "ORGANISATION",
"ORGANISATION": {
"NAME": "RED CROSS ZZZ"
}
}
]
}
}
***************************************
***************************************
The problem is that if the changes are made in the same index of the JSON then they are being recognized but if they are made at 2 different indexes the first is being ignored. Also if there are changes at more than 2 different indexes it raises and error.
Error :
System.IO.InvalidDataException
HResult = 0x80131501
Message = Invalid patch object
Source = JsonDiffPatchDotNet
StackTrace:
at JsonDiffPatchDotNet.JsonDiffPatch.Patch(JToken left, JToken patch)
at JsonDiffPatchDotNet.JsonDiffPatch.ObjectPatch(JObject obj, JObject patch)
at JsonDiffPatchDotNet.JsonDiffPatch.Patch(JToken left, JToken patch)
at JsonDiffPatchDotNet.JsonDiffPatch.ObjectPatch(JObject obj, JObject patch)
at JsonDiffPatchDotNet.JsonDiffPatch.Patch(JToken left, JToken patch)
Please feel free to suggest any other approaches to achieve this 🙂.
Updated Answer
You're almost there - there's one thing you probably missed in my previous answer:
Actually, using two different libraries (where only one of them supports patching) gives no value here.
Meaning, you can really just use your JsonDiffPatch object, which has built-in patching support. So just re-introduce your diff1 of your previous question version, remove diff2, and that's it.
You'll end up with just those 3 lines (see full demo here):
var jdp = new JsonDiffPatch();
var diff1 = jdp.Diff(left, right);
// ...
var diff1PatchOutput = jdp.Patch(left, diff1).ToString();
And if you want to see just the changed items:
var diffAnimalsIds = ((JObject)JObject.Parse(diff1.ToString())["JSON"]["ANIMALS"]).Properties().Select(p => p.Name);
var allAnimals = JObject.Parse(diff1PatchOutput)["JSON"]["ANIMALS"].Cast<JObject>();
var changedAnimals = allAnimals.Where(animal => diffAnimalsIds.Contains(((JValue)animal["ID"]).ToString()));
Previous Answer
I'll start by saying that running your code (see full demo here) doesn't output what you mentioned in your question.
For example, the expected output can't actually be constructed by the provided jsons, beacuse STATUS and NAME are not the ones which have changes, but TYPE and YEAR. Visually, these are the changes:
That said, since I think that what got you those errors is mainly code disorganization, here are several points to help you resolve those issues.
[Don't be intimidated by all those bullets, I'm here to help!]
The first code block is wrong for several reasons:
// difference using JsonDiffer package
var diff1 = jdp.Diff(left, right);
// difference using JsonDiffPatchDotNet package
var diff2 = JsonDifferentiator.Differentiate(right, left...)
You've got:
The comments are the other way around (i.e. jdp.Diff is the one using the JsonDiffPatchDotNet package)
You're passing the json copies in two opposites ways (i.e. left, right and then right, left)
Actually, using two different libraries (where only one of them supports patching) gives no value here.
Then, the next code block actually digs the hole deeper:
var diff1PatchOutput = jdp.Patch(right, diff1).ToString();
// removing * charecter from the difference
var formatedDiff = JToken.Parse(diff2.ToString().Replace("*", ""));
// patching with diff2
// Error occurs here if ther are more than 3 changes .
var diff2PatchOutput = jdp.Patch(right, formatedDiff).ToString();
Because:
right and diff shouldn't really be patched together, because right is already "patched". You probably want left to be patched.
You shouldn't edit diff2, e.g. by removing *. If you want to know what's changed, construct the differ with something like JsonDifferentiator.Differentiate(left, right, OutputMode.Detailed, true), which will get you detailed object.
In the last line, as mentioned above, you probably don't want to patch right, as it's already "patched".
It seems like you are trying to use the JsonDiffPatchDotNet library to create a patch between two JSON objects and then use this patch to create a new JSON object with only the changes and specific keys. The approach you are using is correct, but you may need to modify it a bit to get the expected output.
One of the main issues with your code is that you are trying to create a new JSON object by applying the patch directly to the original JSON object. Instead, you need to create a new JSON object by selecting only the changed fields from the original JSON object and the patch.
Here is a modified version of your code that should work as expected:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static class JsonDiff {
public static JObject GetDiff(string originalJson, string editedJson) {
// Parse the original and edited JSON strings
var original = JObject.Parse(originalJson);
var edited = JObject.Parse(editedJson);
// Find the differences between the two
var diff = GetDifferences(original, edited);
// Extract the changes to an array of JObject representing the differences
var changes = new List<JObject>();
foreach (var d in diff) {
var change = new JObject();
change["ID"] = d.Key;
change["TYPE"] = d.Value.Type.ToString().ToUpper();
if (d.Value.Type == JTokenType.Object) {
change[d.Value.Path] = d.Value.ToString(Formatting.None);
} else {
change[d.Value.Path] = d.Value;
}
changes.Add(change);
}
// Build the result JSON object
var result = new JObject();
result["TEST JSON"] = "JSON";
result["JSON"] = new JObject();
result["JSON"]["ANIMALS"] = new JArray(changes);
return result;
}
private static IDictionary<string, JToken> GetDifferences(JToken original, JToken edited) {
var differences = new Dictionary<string, JToken>();
var originalProps = original.Children<JProperty>();
var editedProps = edited.Children<JProperty>();
// Find properties in original that are not in edited
foreach (var prop in originalProps) {
var editedProp = editedProps.FirstOrDefault(p => p.Name == prop.Name);
if (editedProp == null) {
differences.Add(prop.Path, prop.Value);
}
}
// Find properties in edited that are not in original
foreach (var prop in editedProps) {
var originalProp = originalProps.FirstOrDefault(p => p.Name == prop.Name);
if (originalProp == null) {
differences.Add(prop.Path, prop.Value);
}
}
// Find properties in original and edited that are not equal
foreach (var prop in originalProps) {
var editedProp = editedProps.FirstOrDefault(p => p.Name == prop.Name);
if (editedProp != null) {
if (!JToken.DeepEquals(prop.Value, editedProp.Value)) {
differences.Add(prop.Path, editedProp.Value);
}
}
}
return differences;
}
}
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 have a json object as follows:
"dnsNames": {
"type": "array",
"defaultValue": [
"something.else.com",
"something.com",
"else.com"
]
}
I'd like to read that into a List<string> the same way I can read it into a string (i.e. without creating a class for it):
JObject jsonParameters = JObject.Parse(File.ReadAllText(filePath));
string test = jsonParameters["parameters"]["dnsNames"]["defaultValue"].ToString();
Just unsure if that's possible or what the syntax for it might be.
Navigate the object structure as you see it dnsNames.defaultValue then convert that object to a given type (List<string> in our case):
var json =
#"{""dnsNames"": {
""type"": ""array"",
""defaultValue"": [
""something.else.com"",
""something.com"",
""else.com""
]
}}";
var jObject = JObject.Parse(json);
var list = jObject["dnsNames"]["defaultValue"].ToObject<List<string>>();
// ?list
// Count = 3
// [0]: "something.else.com"
// [1]: "something.com"
// [2]: "else.com"
I am trying to parse JSON data from Instagram API and I am having problem with parsing the child elements. For example, one Instagram response looks like this:
{
"pagination": {
"next_url": "https://api.instagram.com/v1/users/273112457/followed-by?access_token=1941825738.97584da.3242609045494207883c900cbbab04b8&cursor=1439090845443",
"next_cursor": "1439090845443"
},
"meta": {
"code": 200
},
"data": [
{
"username": "ohdyxl",
"profile_picture": "https://igcdn-photos-e-a.akamaihd.net/hphotos-ak-xfp1/t51.2885-19/11093019_661322517306044_2019954676_a.jpg",
"id": "1393044864",
"full_name": "只有你和我知道"
},
{
"username": "dpetalco_florist",
"profile_picture": "https://igcdn-photos-a-a.akamaihd.net/hphotos-ak-xtf1/t51.2885-19/11192809_930052080349888_1420093998_a.jpg",
"id": "1098934333",
"full_name": "D'petalco florist"
}
]
}
My code is the following:
dynamic d = JObject.Parse(response);
foreach (var result in d["data"])
{
string userName = (string)result["username"];
list.Add(userName);
}
This part works perfectly, however when I try to extract pagination, I get a child error access error.
My code is the following:
foreach (var res in d["pagination"])
{
string nexturl = (string)res["next_url"];
string nextcursor = (string)res["next_cursor"];
}
How can I extract the next_url and next_curosr from "pagination" in C#? Thanks.
Unlike data property value, pagination property value is not an array so you don't need foreach loop here :
var res = d["pagination"];
string nexturl = (string)res["next_url"];
string nextcursor = (string)res["next_cursor"];
or without using intermediate variable res :
string nexturl = (string)d["pagination"]["next_url"];
string nextcursor = (string)d["pagination"]["next_cursor"];
I have a JSON object that looks like this
{
"totalCount": 2,
"students": [{
"name": "abc",
"data": {
"Maths": 20,
"Science": 25
},
"score": 10.0
},
{
"name": "xyz",
"data": {
"Maths": 44,
"Science": 12
},
"score": 11.0
}]
}
I want to deserialize this JSON object to an IEnumerable<String> that contains all the names.
I want -
private IEnumerable<String> GetAllNames(string json) to return ["abc","xyz"]
This is just sample data (and not homework!!). Any advice on how to achieve this would be appreciated. I'm using Newtonsoft library but haven't been able to do this effectively yet. I do not want to iterate through the objects and construct the list myself, any direct way of doing this?
EDIT -
This is what I'm doing currently
var studentList = new List<string>();
var json = JsonConvert.DeserializeObject<dynamic>(jsonString);
foreach (var data in json.students)
{
catalogsList.Add(data.name.toString());
}
return catalogsList;
Try this:
private IEnumerable<string> GetAllNames(string json)
{
JObject jo = JObject.Parse(json);
return jo["students"].Select(s => s["name"].ToString());
}