1

The following javascript question is the same problem i'm attempting to solve but in c#

How can I merge 2 dot notation strings to a GraphQL query string

Expected structure is

{
    "Case": {
        "Owner": {
            "Name": null,
            "ProfilePic": null
        },
        "CaseNo": null,
        "FieldOfLaw":{
            "Name": null
        },
        "CaseType": {
            "Name": null
        },
        "CaseSubType": {
            "Name": null
        },
    },
    "Client":{
        "Policy":{
            "PolicyNo": null
        }
    }
}

and my current output is

{
    "Case": {
        "Owner": {
            "Name": null,
            "ProfilePic": null
        },
        "CaseNo": null,
        "FieldOfLaw": null,
        "CaseType": null,
        "CaseSubType": null
    }
}

Below is my attempt using ExpandoObjects to try dynamically generate the objects needed. Any advice or pointers in the right direction would be appreciated.

public static void Main(string[] args)
{
    var FieldList = new List<string>
    {
    "Case.Owner.Name",
    "Case.Owner.ProfilePic",
    "Case.CaseNo",
    "Case.FieldOfLaw.Name",
    "Case.CaseType.Name",
    "Case.CaseSubType.Name",
    "Client.Policy.PolicyNo",
    };

    Parser graphQL = new Parser();
    var result = graphQL.Parse(FieldList);
    Console.Write(result);
}

Below is the actual parse method, so i'm running an aggregation function over each element to create and return the expando objects into the initial holder. I traverse each split string recursively and exit the recursion once there are no more items left in the split list.

public class Parser
    {
        public string Parse(List<string> fieldList)
        {
            // List<ExpandoObject> queryHolder = new List<ExpandoObject>();
            ExpandoObject intialSeed = new ExpandoObject();

            fieldList.Aggregate(intialSeed, (holder, field) =>
            {
                holder = ParseToObject(holder, field.Split('.').ToList());
                return holder;
            });

            return JsonConvert.SerializeObject(intialSeed);
        }

        public ExpandoObject ParseToObject(ExpandoObject holder, List<string> fieldSplit, string previousKey = null)
        {
            if (fieldSplit.Any())
            {
                var item = fieldSplit.Shift();

                if (item == null)
                    return holder;

                // If the current item doesn't exists in the dictionary
                if (!((IDictionary<string, object>)holder).ContainsKey(item))
                {
                    if (((IDictionary<string, object>)holder).Keys.Count() == 0)
                        holder.TryAdd(item, null);
                    else
                        _ = ((IDictionary<string, object>)holder).GetItemByKeyRecursively(previousKey, item);
                }

                previousKey = item;

                ParseToObject(holder, fieldSplit, previousKey);
            }

            return holder;
        }

    }

Here are my two extensions methods, i'm having an issue with the GetItemByKeyRecursively when it goes into the 3rd level in it's recursion so example.

I'm adding FieldOfLaw it adds the property to the Case expandoObject but doesn't know how to get back to the leaf containing Owner, CaseNo etc.

 public static class CollectionExtensions
    {
        public static T Shift<T>(this IList<T> list)
        {
            var shiftedElement = list.FirstOrDefault();
            list.RemoveAt(0);
            return shiftedElement;
        }

        public static IDictionary<string, object> GetItemByKeyRecursively(this IDictionary<string, object> dictionary, string parentKey, string keyToCreate)
        {
            foreach (string key in dictionary.Keys)
            {
                var leaf = dictionary[key];

                if (key == parentKey)
                {
                    var @value = dictionary[key];
                    if (@value is ExpandoObject)
                    {
                        (@value as ExpandoObject).TryAdd(keyToCreate, null);
                    }
                    else if (@value == null)
                    {
                        var item = new ExpandoObject();
                        item.TryAdd(keyToCreate, null);
                        dictionary[key] = item;
                    }
                    return dictionary;
                }

                if (leaf == null)
                    continue;

                return GetItemByKeyRecursively((IDictionary<string, object>)leaf, parentKey, keyToCreate);
            }

            return null;
        }
    }

1 Answer 1

1

Nothing you can't accomplish mostly declaratively.

public string Parse(List<string> fieldList)
{
    var fieldPaths = fieldList.Select(x => x.Split('.').ToList());
    var groups = fieldPaths.GroupBy(x => x.First(), x => x.Skip(1));
    return ParseGroups(groups, 1);
}

private string ParseGroups(IEnumerable<IGrouping<string, IEnumerable<string>>> groups, int level)
{
    string indent = new string('\t', level - 1);

    var groupResults = groups.Select(g =>
        !g.First().Any() ? 
            $"\t{indent}{g.Key}: null" :
            $"\t{indent}{g.Key}: " + string.Join(", \n",
                 ParseGroups(g.GroupBy(x => x.First(), x => x.Skip(1)), level + 1))
    );

    return indent + "{\n" + string.Join(", \n", groupResults) + "\n" + indent + "}";
}

See the complete sample code here: https://dotnetfiddle.net/RLygjt

Sign up to request clarification or add additional context in comments.

1 Comment

This is a pretty cool way to solve this problem, i did end up solving it today with some of my similar code above. Will try post if anyone is interested a bit later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.