Flatten nested Dictionary<string, object>
Asked Answered
A

3

2

I am deserializing some nested JSON with the following:

string json = @"{
    ""name"": ""charlie"",
    ""someID"": 123,
    ""level1"" : {
        ""name"": ""charlie 1"",
        ""someID"": 456
    }
}";

JavaScriptSerializer serializer = new JavaScriptSerializer();
Dictionary<string, object> data = serializer.Deserialize<Dictionary<string, object>>(json);

Once this is done, the values of each dictionary key may be another Dictionary, and so on, multiple levels deep.

What I would like to do is to flatten the multi-level data so it's just a flat Array/List, with just all the JSON attribute names and their values. So that I end up with something like this:

name, "charlie"
someID, 123
name, charlie 1
someID, 456

I was heading down the path of using SelectMany() and so forth, but could not wrangle it to do what I am after.

I've been sort of waddling around with things like this:

var obj = data.Values.SelectMany<object, Dictionary<string, object>>(x => x);

But I am not able to satisfy the compiler. Yes, I am lost.

I am using .NET 3.5.

Arhat answered 13/9, 2012 at 8:51 Comment(1)
Create your own object with each propertyGuildhall
U
8
Func<Dictionary<string, object>, IEnumerable<KeyValuePair<string, object>>> flatten = null;

flatten = dict => dict.SelectMany(kv => 
                        kv.Value is Dictionary<string,object> 
                            ? flatten((Dictionary<string,object>)kv.Value)
                            : new List<KeyValuePair<string,object>>(){ kv}
                       );

var flatList = flatten(data).ToList();
Unhouse answered 13/9, 2012 at 9:13 Comment(0)
L
4

You need recursion here:

IEnumerable<Tuple<string, string>> Flatten(this IDictionary dict)
{
    foreach(DictionaryEntry kvp in dict)
    {
        var childDictionary = kvp.Value as IDictionary;
        if(childDictionary != null)
        {
            foreach(var tuple in childDictionary.Flatten())
                yield return tuple;
        }
        else
            yield return Tuple.Create(kvp.Key.ToString(), kvp.Value.ToString());
    }
}

// Usage:

var flatList = data.Flatten().ToList();

On .NET 3.5, you can use KeyValuePair<string, string> instead of Tuple<string, string>.

Please note that there is no KeyValuePair.Create, you need to use new KeyValuePair<string, string>(kvp.Key.ToString(), kvp.Value.ToString()) instead.

Luttrell answered 13/9, 2012 at 8:57 Comment(3)
Daniel, looks good. Unfortunately I am using .NET 3.5, which means Tupkes are out. Sorry.Arhat
@dbarros: You can use a KeyValuePair instead.Luttrell
+1 I hacked together nearly exact the same code in LinqPad (just using KeyValuePairs), but I was too slow.... :-)Hutch
S
0

This was not what was requested, but if anyone wants to keep the JSON properties path in the flattened dictionary by appending it with a separator, feel free to use this which was based on the accepted answer.

private static IEnumerable<KeyValuePair<string, object>> Flatten(Dictionary<string, object> dictionary, string key)
{
    return dictionary.SelectMany(kv =>
        {
            var currentKey = string.IsNullOrEmpty(key) ? kv.Key : $"{key}.{kv.Key}";
            return kv.Value is Dictionary<string, object> nestedDictionary ? Flatten(nestedDictionary, currentKey) : new List<KeyValuePair<string, object>>() { new KeyValuePair<string, object>(currentKey, kv.Value) };
        }
    );
}

public static IEnumerable<KeyValuePair<string, object>> Flatten(Dictionary<string, object> dictionary)
{
    return Flatten(dictionary, "");
}

If you control who does the calls, consider having only the private method as public.

Storyteller answered 29/6, 2023 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.