3

I am also struck in a sort of problem. I am able to convert the nested JSON into key-Value , but Now I want to convert it into back its orignal json format. As of my problem I can't use the C# Object Model to do this, because the JSON file that I have is dynamic and its structure changes over the period of time. So I am looking for the solution by which we can serialize and deserialize the JSON via updated Key-Value pair. Any help would be great relief. TIA.

Sample JSON Code:

 {
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021-3100"
 },
"phoneNumbers": [
{
  "type": "home",
  "number": "212 555-1234"
 },
{
  "type": "office",
  "number": "646 555-4567"
},
{
  "type": "mobile",
  "number": "123 456-7890"
}
],
"children": [],
"spouse": null

}

var obj = JObject.Parse(json);

var result = obj.Descendants()
.OfType<JProperty>()
.Select(p => new KeyValuePair<string, object>(p.Path,
    p.Value.Type == JTokenType.Array || p.Value.Type == JTokenType.Object
        ? null : p.Value));

foreach (var kvp in result)
  Console.WriteLine(kvp);

The output of this code is coming like this :

[firstName, John]
[lastName, Smith]
[isAlive, True]
[age, 25]
[address, ]
[address.streetAddress, 21 2nd Street]
[address.city, New York]
[address.state, NY]
[address.postalCode, 10021-3100]
[phoneNumbers, ]
[phoneNumbers[0].type, home]
[phoneNumbers[0].number, 212 555-1234]
[phoneNumbers[1].type, office]
[phoneNumbers[1].number, 646 555-4567]
[phoneNumbers[2].type, mobile]
[phoneNumbers[2].number, 123 456-7890]
[children, ]
[spouse, ]

I want to convert it into back its original JSON structure.

er-sho
  • 9,581
  • 2
  • 13
  • 26
Pankaj Sharma
  • 95
  • 1
  • 10
  • Not currently implemented in Json.NET, see [Issue #1949: A way to create a JToken(JObject/JArray/JValue) from jpath->value mapping](https://github.com/JamesNK/Newtonsoft.Json/issues/1949) and [Build JObject from JSONPath](https://stackoverflow.com/q/43988405) and [How to add JObject property by path if not exists?](https://stackoverflow.com/q/51310888), for which the consensus is that you will have to roll your own method. – dbc Jan 14 '19 at 08:38

1 Answers1

7

The below ExtensionMethods can help you to update any key value in your json on any level.

public static class JsonExtensions
{
    public static void SetByPath(this JToken obj, string path, JToken value)
    {
        JToken token = obj.SelectToken(path);
        token.Replace(value);
    }

    public static List<JToken> FindTokens(this JToken containerToken, string name)
    {
        List<JToken> matches = new List<JToken>();
        FindTokens(containerToken, name, matches);
        return matches;
    }

    private static void FindTokens(JToken containerToken, string name, List<JToken> matches)
    {
        if (containerToken.Type == JTokenType.Object)
        {
            foreach (JProperty child in containerToken.Children<JProperty>())
            {
                if (child.Name == name)
                {
                    matches.Add(child.Value);
                }
                FindTokens(child.Value, name, matches);
            }
        }
        else if (containerToken.Type == JTokenType.Array)
        {
            foreach (JToken child in containerToken.Children())
            {
                FindTokens(child, name, matches);
            }
        }
    }
}

And here I write one custom function that find the key and replace it's value,

public static JToken FindAndReplace(JToken jToken, string key, JToken value, int? occurence)
{
    var searchedTokens = jToken.FindTokens(key);
    int count = searchedTokens.Count;

    if (count == 0)
        return $"The key you have to serach is not present in json, Key: {key}";

    foreach (JToken token in searchedTokens)
    {
        if (!occurence.HasValue)
            jToken.SetByPath(token.Path, value);
        else
        if (occurence.Value == searchedTokens.IndexOf(token))
            jToken.SetByPath(token.Path, value);
    }

    return jToken;
}

Important: What's the fourth parameter occurence mean here?

  • If you put null in this parameter then the value will be updated for all the occurrences of the specified key in json at any level.
  • If you put any index like 0, 1 then the value will be updated for the specified index of the specified key in json at any level.

And you can use it like

string json = File.ReadAllText(@"Path to your json file");

JToken jToken = JToken.Parse(json);

jToken = FindAndReplace(jToken, "firstName", "Matthew", null);
jToken = FindAndReplace(jToken, "lastName", "Gilbert", null);
jToken = FindAndReplace(jToken, "streetAddress", "Falcon Ave, 91 Street, New Jersey", null);
jToken = FindAndReplace(jToken, "postalCode", "R12H34", null);

jToken = FindAndReplace(jToken, "type", "work", 0);
jToken = FindAndReplace(jToken, "number", "787-878-7878", 0);

jToken = FindAndReplace(jToken, "type", "factory", 1);
jToken = FindAndReplace(jToken, "number", "989-898-9898", 1);

string outputJson = jToken.ToString();

Output:

enter image description here

Reference: Brian Rogers, senshin

er-sho
  • 9,581
  • 2
  • 13
  • 26
  • The JSON i have is not consistent , it can vary so the keys and the arrays are not static , they can change so how can we achieve that as i cant write any thing hard code in my cs file – Pankaj Sharma Jan 14 '19 at 12:19
  • how we are going to traverse through the arrays like myClass.DynamicData["phoneNumbers"][0]["type"] as the json file is created dynamically in my case. – Pankaj Sharma Jan 14 '19 at 13:03
  • for your second comment => means you have to use above code for every index item in array, right? means here `phoneNumbers` is array and you have to iterate over this array. – er-sho Jan 14 '19 at 13:12
  • for your first comment => your server respond to your request with schema less json. So how you know what to do with specific key bcoz you don't know the schema and then you could stuck here. – er-sho Jan 14 '19 at 13:14
  • I'm syncing few values in json file that matches with other config file which is not in json, so my purpose is to sync the matched keys and update their values. – Pankaj Sharma Jan 14 '19 at 13:18
  • so you said you sync few values in JSON with other file right? then you know those key name from JSON regardless of JSON schema. Means you know which values to be sync and that the key name form json. – er-sho Jan 14 '19 at 13:22
  • Yes i know the key names as from the other file which is in xml format I'm picking up the key name , deserializing the json file , looking for the keys name in json file if the key exists updating its value with the that in the xml file and then serilizing the json back. So I'm not sure of the path under which array that key would be so i can't give the hardcode path of the key. I have to traverse through the arrays to look for the key. – Pankaj Sharma Jan 14 '19 at 13:32
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186656/discussion-between-er-mfahhgk-and-pankaj-sharma). – er-sho Jan 14 '19 at 13:36
  • Answer updated, just replace "postalCode" in "FindTokens" method with your desired key name and "new value" with your value in "SetByPath" method. Upto you try this. Tomorrow I'll try to update this code in custom function :) – er-sho Jan 14 '19 at 16:33
  • in case if i have two or more keys by same name. – Pankaj Sharma Jan 15 '19 at 07:36
  • in that case you need to provide `occurance` paramter that replaces the value of key, like if you provide `occurance =2` for key `Abc` then from top to bottom in json our program searches `3rd number` key and replaces its value. like I did in above for key `type` and `number` . – er-sho Jan 15 '19 at 07:44