aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
blob: d13935fbab9d42406fed57de8a5ab2663dc42304 (plain)
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
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Emby.Server.Implementations.Services
{
    /// <summary>
    /// Serializer cache of delegates required to create a type from a string map (e.g. for REST urls)
    /// </summary>
    public class StringMapTypeDeserializer
    {
        internal class PropertySerializerEntry
        {
            public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn)
            {
                PropertySetFn = propertySetFn;
                PropertyParseStringFn = propertyParseStringFn;
            }

            public Action<object, object> PropertySetFn;
            public Func<string, object> PropertyParseStringFn;
            public Type PropertyType;
        }

        private readonly Type type;
        private readonly Dictionary<string, PropertySerializerEntry> propertySetterMap
            = new Dictionary<string, PropertySerializerEntry>(StringComparer.OrdinalIgnoreCase);

        public Func<string, object> GetParseFn(Type propertyType)
        {
            if (propertyType == typeof(string))
                return s => s;

            return _GetParseFn(propertyType);
        }

        private readonly Func<Type, object> _CreateInstanceFn;
        private readonly Func<Type, Func<string, object>> _GetParseFn;

        public StringMapTypeDeserializer(Func<Type, object> createInstanceFn, Func<Type, Func<string, object>> getParseFn, Type type)
        {
            _CreateInstanceFn = createInstanceFn;
            _GetParseFn = getParseFn;
            this.type = type;

            foreach (var propertyInfo in RestPath.GetSerializableProperties(type))
            {
                var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
                var propertyType = propertyInfo.PropertyType;
                var propertyParseStringFn = GetParseFn(propertyType);
                var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };

                propertySetterMap[propertyInfo.Name] = propertySerializer;
            }
        }

        public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
        {
            string propertyName = null;
            string propertyTextValue = null;
            PropertySerializerEntry propertySerializerEntry = null;

            if (instance == null)
                instance = _CreateInstanceFn(type);

            foreach (var pair in keyValuePairs)
            {
                propertyName = pair.Key;
                propertyTextValue = pair.Value;

                if (string.IsNullOrEmpty(propertyTextValue))
                {
                    continue;
                }

                if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
                {
                    if (propertyName == "v")
                    {
                        continue;
                    }

                    continue;
                }

                if (propertySerializerEntry.PropertySetFn == null)
                {
                    continue;
                }

                if (propertySerializerEntry.PropertyType == typeof(bool))
                {
                    //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
                    propertyTextValue = LeftPart(propertyTextValue, ',');
                }

                var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
                if (value == null)
                {
                    continue;
                }
                propertySerializerEntry.PropertySetFn(instance, value);
            }

            return instance;
        }

        public static string LeftPart(string strVal, char needle)
        {
            if (strVal == null) return null;
            var pos = strVal.IndexOf(needle);
            return pos == -1
                ? strVal
                : strVal.Substring(0, pos);
        }
    }

    internal class TypeAccessor
    {
        public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
        {
            if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null;

            var setMethodInfo = propertyInfo.SetMethod;
            return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
        }
    }
}