Search code examples
c#json.netjson.net

How to create JSON from a slash separated String?


I have a slash separated string format.

For Example

  myDecision/data/buyerDecision/data/buy = food

  myDecision/data/sellerDecision/data/sell = food

I want to convert it into following json format

"myDecision":{
    "data":{
        "buyerDesision":{
            "data":{"buy":"food"}
        },
        "sellerDesision":{
            "data":{"sell":"food"}
        }
    }
}

I tried a different solution, but it didn't work.


Solution

  • One way to solve this problem is by handcrafting JObjects and then merging them. (This is not the most performant solution because it will recreate certain levels of the hierarchy several times.)

    This approach differs from Benzara Tahar's proposed solution:

    • Tahar: Uses string manipulation to construct a valid json and then uses Json.Parse
    • Mine: Uses path traversal to construct JObject hierarchy.

    Core logic

    The core logic can be implemented as this:

    static JObject CreateHierarchy(Queue<string> pathLevels, JObject currentNode)
    {
        if (pathLevels.Count == 0) return currentNode;
    
        var newNode = new JObject(new JProperty(pathLevels.Dequeue(), currentNode));
        return CreateHierarchy(pathLevels, newNode);
    }
    
    • This is a recursive function, which builds the hierarchy from the inner most item to the outer most.
    • So, if we call this method with the following pathLevels: data, buyerDecision, data, myDescision
      then it will generate something like this:
    new JObject(
        new JProperty("myDecision",
            new JObject(
                new JProperty("data",
                    new JObject(
                        new JProperty("buyerDecision",
                            new JObject(
                                new JProperty("data", currentNode)))))));
    

    Calling core logic

    First, convert the input strings into a dictionary where

    • the key is the path
    • the value is the desired innermost value:
    var dataSource = new List<string>
    {
        "myDecision/data/buyerDecision/data/buy = food",
        "myDecision/data/sellerDecision/data/sell = food"
    };
    
    var mappings = dataSource.Select(data => data.Split('=', StringSplitOptions.RemoveEmptyEntries))
        .ToDictionary(parts => parts[0].Trim(), parts => parts[1].Trim());
    
    • NOTE: This is fragile because it can throw ArgumentException: 'An item with the same key has already been added if multiple datasource entries want to set the same property.
    • We can loop through the dictionary to construct JObjects from the entries
      • The key should be split by / and then reverse the collection
      • The innermost value and the innermost path can be used to construct the currentNode
    var objectsWithHierarchy = new List<JObject>();
    foreach (var (path, innerMostValue) in mappings.Select(kv => (kv.Key, kv.Value)))
    {
        var entryLevels = path.Split('/').Reverse().ToArray();
        objectsWithHierarchy.Add(CreateHierarchy(new Queue<string>(entryLevels.Skip(1)),
            new JObject(new JProperty(entryLevels.First(), innerMostValue))));
    }
    

    Merging the objects

    Here we choose the first object from the objectsWithHierarchy to collection to be the base JObject on which we apply/merge the rest of the JObjects.

    var baseObject = objectsWithHierarchy.First();
    var mergeSettings = new JsonMergeSettings {MergeArrayHandling = MergeArrayHandling.Union};
    foreach (var currentObj in objectsWithHierarchy.Skip(1))
    {
        baseObject.Merge(currentObj, mergeSettings);
    }
    
    Console.WriteLine(baseObject);
    

    The output will be the following:

    {
      "myDecision": {
        "data": {
          "buyerDecision": {
            "data": {
              "buy": "food"
            }
          },
          "sellerDecision": {
            "data": {
              "sell": "food"
            }
          }
        }
      }
    }
    

    Closing Thoughts

    • This code is really fragile because it was designed based on two sample data without actual requirements
    • The data format is quite unique so we want use things like JSONPath
    • The actual use case is also unknown so the solution itself might provide very poor performance
      • in case of very deep hierarchies
      • in case of very large data source