I wanted to share with you what I came up with, based off of Ahmad Mageed work. (It was a great start by the way.) I needed the same functionality, but I needed it to be able to accept much more depth in the index chain.
This allows you to be able to read as many sub indexes as you need, without hard coding it to stop at 2 or 3 levels deep. So with this update you could read such a query string as so...
columns[0][data]=0&columns[0][name]=&columns[0][searchable]=true
&columns[0][orderable]=true&columns[0][search][value]=
&columns[0][search][regex]=false&columns[1][data]=1
&columns[1][name]=&columns[1][searchable]=true
&columns[1][orderable]=true&columns[1][search][value]=
&columns[1][search][regex]=false&columns[2][data]=2
...
&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&search[regex]=false
Here is my updated version of that part of the code.
public IDictionary ConvertQueryString(string sQry)
{
string pattern = @"(?<Prop>[^[]+)(?:\[(?<Key>[^]]+)\])*=(?<Value>.*)";
// The brackets seem to be encoded as %5B and %5D
var qry = HttpUtility.UrlDecode(sQry);
var re = new Regex(pattern);
var dict = qry.Split(new[] { "?", "&" }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => re.Match(s)).Where(g => g.Success)
.GroupBy(m => m.Groups["Prop"].Value)
.ToDictionary<IGrouping<string, Match>, string, object>(
g => g.Key,
g => GetKey(g, 0));
return dict;
}
private object GetKey(IGrouping<string, Match> grouping, int level)
{
var count = grouping.FirstOrDefault().Groups["Key"].Captures.Count;
// If the level is equal to the captures, then we are at the end of the indexes
if (count == level)
{
var gValue = grouping.Where(gr => gr.Success).FirstOrDefault();
var value = gValue.Groups["Value"].Value;
return value;
}
else
{
return grouping.Where(gr => gr.Success)
.GroupBy(m => m.Groups["Key"].Captures[level].Value)
.ToDictionary<IGrouping<string, Match>, string, object>(
a => a.Key,
a => GetKey(a, level + 1));
}
}
I wanted to go a little further than just building a nested dictionary object, I wanted to actually build objects based on the passed string. I needed this for the new release of jQuery DataTables, it's actually what produces that nasty looking query string above. It was a quick write so it is a little crude and probably a little error prone as I have not had time yet to harden it.
// Call it like so.
MyClass object = new MyClass();
WalkDictionary(dict, object);
// At this point the object will be filled out for you.
public class MyClass
{
public List<cColumns> columns { get; set; }
public cSearch search { get; set; }
}
public class cColumns
{
public int? data { get; set; }
public string name { get; set; }
public bool? searchable { get; set; }
public bool? orderable { get; set; }
public cSearch search { get; set; }
}
public class cSearch
{
public string value { get; set; }
public bool? regex { get; set; }
}
I only put the basic Types that I needed. So, if you need/want more, then you will have to add them.
private void WalkDictionary(IDictionary dict, object obj)
{
foreach (string key in dict.Keys)
{
Type t = obj.GetType();
if (t.IsGenericType
&& typeof(List<>) == t.GetGenericTypeDefinition())
{
Type[] typeParameters = t.GetGenericArguments();
foreach (DictionaryEntry item in dict)
{
var lstPropObj = Activator.CreateInstance(typeParameters[0]);
WalkDictionary((IDictionary)item.Value, lstPropObj);
((IList)obj).Add(lstPropObj);
}
return;
}
PropertyInfo prop = obj.GetType().GetProperty(key);
if (prop == null)
continue;
if (dict[key] is IDictionary)
{
//Walk
var objProp = prop.GetValue(obj);
if (objProp == null)
objProp = Activator.CreateInstance(prop.PropertyType);
WalkDictionary(dict[key] as IDictionary, objProp);
prop.SetValue(obj, objProp);
}
else if (prop.PropertyType.IsAssignableFrom(typeof(int)))
{
int val = Convert.ToInt32(dict[key]);
prop.SetValue(obj, val);
}
else if (prop.PropertyType.IsAssignableFrom(typeof(bool)))
{
bool val = Convert.ToBoolean(dict[key]);
prop.SetValue(obj, val);
}
else if (prop.PropertyType.IsAssignableFrom(typeof(string)))
{
string val = Convert.ToString(dict[key]);
prop.SetValue(obj, val);
}
}
}