Json To Dictionary generic model binder

Hey all,

Few days ago I was challenged by a good friend and colleague to write a generic model binder for a dynamic json.
Why does he need such a thing ? I'll explain:
Normally, when you write a classic website using any platform, the contract between the client and the server is obvious and well known.
However, if you write a complex web system, and you have the need to build your client model dynamically without creating a massive model on the server that handles all kinds of properties and values, you might be interested in getting a generic dictionary that contains all your client values.
To implement it I used a state machine to parse the json:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public class DictionaryModelBinder : DefaultModelBinder
{
    private const string _dateTimeFormat = "dd/MM/yyyy HH:mm:ss";
 
    private enum StateMachine
    {
        NewSection,
        Key,
        Delimiter,
        Value,
        ValueArray
    }
 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var stream = controllerContext.HttpContext.Request.InputStream;
        string text;
 
        stream.Position = 0;
        using (var reader = new StreamReader(stream))
        {
            text = reader.ReadToEnd();
        }
 
        int index = 0;
        return Build(text, ref index);
    }
 
    private static Dictionary<string, object> Build(string text, ref int index)
    {
        var state = StateMachine.NewSection;
        var dictionary = new Dictionary<string, object>();
        var key = string.Empty;
        object value = string.Empty;
 
        for (; index < text.Length; ++index)
        {
            if (state == StateMachine.NewSection && text[index] == '{')
            {
                dictionary = new Dictionary<string, object>();
                state = StateMachine.NewSection;
            }
            else if (state == StateMachine.NewSection && text[index] == '"')
            {
                key = string.Empty;
                state = StateMachine.Key;
            }
            else if (state == StateMachine.Key && text[index] != '"')
            {
                key += text[index];
            }
            else if (state == StateMachine.Key && text[index] == '"')
            {
                state = StateMachine.Delimiter;
            }
            else if (state == StateMachine.Delimiter && text[index] == ':')
            {
                state = StateMachine.Value;
                value = string.Empty;
            }
            else if (state == StateMachine.Value && text[index] == '[')
            {
                state = StateMachine.ValueArray;
                value = value.ToString() + text[index];
            }
            else if (state == StateMachine.ValueArray && text[index] == ']')
            {
                state = StateMachine.Value;
                value = value.ToString() + text[index];
            }
            else if (state == StateMachine.Value && text[index] == '{')
            {
                value = Build(text, ref index);
            }
            else if (state == StateMachine.Value && text[index] == ',')
            {
                dictionary.Add(key, ConvertValue(value));
                state = StateMachine.NewSection;
            }
            else if (state == StateMachine.Value && text[index] == '}')
            {
                dictionary.Add(key, ConvertValue(value));
                return dictionary;
            }
            else if (state == StateMachine.Value || state == StateMachine.ValueArray)
            {
                value = value.ToString() + text[index];
            }
        }
 
        return dictionary;
    }
 
    private static object ConvertValue(object value)
    {
        string valueStr;
        if (value is Dictionary<string, object> || value == null || (valueStr = value.ToString()).Length == 0)
        {
            return value;
        }
 
        bool boolValue;
        if (bool.TryParse(valueStr, out boolValue))
        {
            return boolValue;
        }
 
        int intValue;
        if (int.TryParse(valueStr, out intValue))
        {
            return intValue;
        }
 
        double doubleValue;
        if (double.TryParse(valueStr, out doubleValue))
        {
            return doubleValue;
        }
 
        valueStr = valueStr.Trim('"');
 
        DateTime datetimeValue;
        if (DateTime.TryParseExact(valueStr, _dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out datetimeValue))
        {
            return datetimeValue;
        }
 
        if (valueStr.First() == '[' && valueStr.Last() == ']')
        {
            valueStr = valueStr.Trim('[', ']');
            if (valueStr.Length > 0)
            {
                if (valueStr[0] == '"')
                {
                    return valueStr
                        .Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries)
                        .Where(x => x != ",")
                        .ToArray();
                }
                else
                {
                    return valueStr
                        .Split(',')
                        .Select(x => ConvertValue(x.Trim()))
                        .ToArray();
                }
            }
        }
 
        return valueStr;
    }
}
We need to register this model binder in the Global.asax file:
1
ModelBinders.Binders.Add(typeof(Dictionary<string, object>), new DictionaryModelBinder());
And the usage in the client side is pretty much straight forward as you probably expected:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var url = '/';
var data = {
    name: 'Amir Yonatan',
    age: 26,
    height: 1.7,
    hasDrivingLicence: true,
    nicknames: ['amiry', 'speedy'],
    education: {
        graduationDate: '30/10/2010 15:12:30',
        grades: [100, 95, 87],
        major: 'Computer Science'
    }
};
 
$.ajax(url, {
    cache: false,
    contentType: 'application/json',
    data: JSON.stringify(data),
    dataType: 'json',
    type: 'POST'
});
That's it!
Hope you'll find it useful.

Comments

Popular posts from this blog

Impersonating CurrentUser from SYSTEM

Add Styles & Scripts Dynamically to head tag in ASP .NET MVC

Extending cshtml with functions and properties