• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Tomlet / 7392240306

03 Jan 2024 01:00AM UTC coverage: 91.909% (-0.5%) from 92.423%
7392240306

push

github

SamboyCoding
[publish] Release 5.3.0 - see CHANGELOG.md for details

937 of 1086 branches covered (0.0%)

1863 of 2027 relevant lines covered (91.91%)

401.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.86
/Tomlet/TomlSerializationMethods.cs
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Reflection;
6
using Tomlet.Attributes;
7
using Tomlet.Exceptions;
8
using Tomlet.Models;
9

10
namespace Tomlet
11
{
12
    public static class TomlSerializationMethods
13
    {
14
        private static MethodInfo _stringKeyedDictionaryMethod = typeof(TomlSerializationMethods).GetMethod(nameof(StringKeyedDictionaryDeserializerFor), BindingFlags.Static | BindingFlags.NonPublic)!;
15
        private static MethodInfo _genericDictionarySerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericDictionarySerializer), BindingFlags.Static | BindingFlags.NonPublic)!;
16
        private static MethodInfo _genericNullableSerializerMethod = typeof(TomlSerializationMethods).GetMethod(nameof(GenericNullableSerializer), BindingFlags.Static | BindingFlags.NonPublic)!;
17

18
        public delegate T Deserialize<out T>(TomlValue value);
19
        public delegate T ComplexDeserialize<out T>(TomlValue value, TomlSerializerOptions options);
20
        public delegate TomlValue? Serialize<in T>(T? t);
21
        public delegate TomlValue? ComplexSerialize<in T>(T? t, TomlSerializerOptions options);
22

23
        private static readonly Dictionary<Type, Delegate> Deserializers = new();
24
        private static readonly Dictionary<Type, Delegate> Serializers = new();
25

26

27
        [ExcludeFromCodeCoverage]
28
        static TomlSerializationMethods()
29
        {
30
            //Register built-in serializers
31

32
            //String
33
            Register(s => new TomlString(s!), value => (value as TomlString)?.Value ?? value.StringValue);
34

35
            //Bool
36
            Register(TomlBoolean.ValueOf, value => (value as TomlBoolean)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlBoolean), value.GetType(), typeof(bool)));
37

38
            //Byte
39
            Register(i => new TomlLong(i), value => (byte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(byte))));
40

41
            //SByte
42
            Register(i => new TomlLong(i), value => (sbyte)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(sbyte))));
43

44
            //UShort
45
            Register(i => new TomlLong(i), value => (ushort)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ushort))));
46

47
            //Short
48
            Register(i => new TomlLong(i), value => (short)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(short))));
49

50
            //UInt
51
            Register(i => new TomlLong(i), value => (uint)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(uint))));
52

53
            //Int
54
            Register(i => new TomlLong(i), value => (int)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(int))));
55

56
            //ULong
57
            Register(l => new TomlLong((long)l), value => (ulong)((value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(ulong))));
58

59
            //Long
60
            Register(l => new TomlLong(l), value => (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLong), value.GetType(), typeof(long)));
61

62
            //Double
63
            Register(d => new TomlDouble(d), value => (value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(double)));
64

65
            //Float
66
            Register(f => new TomlDouble(f), value => (float)((value as TomlDouble)?.Value ?? (value as TomlLong)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlDouble), value.GetType(), typeof(float))));
67

68
            //LocalDate(Time)
69
            Register(dt => dt.TimeOfDay == TimeSpan.Zero ? new TomlLocalDate(dt) : new TomlLocalDateTime(dt), value => (value as ITomlValueWithDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(ITomlValueWithDateTime), value.GetType(), typeof(DateTime)));
70

71
            //OffsetDateTime
72
            Register(odt => new TomlOffsetDateTime(odt), value => (value as TomlOffsetDateTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlOffsetDateTime), value.GetType(), typeof(DateTimeOffset)));
73

74
            //LocalTime
75
            Register(lt => new TomlLocalTime(lt), value => (value as TomlLocalTime)?.Value ?? throw new TomlTypeMismatchException(typeof(TomlLocalTime), value.GetType(), typeof(TimeSpan)));
76
        }
77

78
        /// <summary>
79
        /// Returns the default (reflection-based) serializer for the given type. Can be useful if you're implementing your own custom serializer but want to use the default behavior (e.g. to extend it or to use it as a fallback). 
80
        /// </summary>
81
        /// <param name="type">The type to get the default serializer for</param>
82
        /// <param name="options">The options to use for the serializer</param>
83
        /// <returns>The default reflection-based serializer for the given type.</returns>
84
        /// <exception cref="ArgumentException">Thrown if <paramref name="type"/> is a primitive type.</exception>
85
        public static Serialize<object> GetDefaultSerializerForType(Type type, TomlSerializerOptions? options = null)
86
        {
×
87
            options ??= TomlSerializerOptions.Default;
×
88
            if(type.IsPrimitive)
×
89
                throw new ArgumentException("Can't use reflection-based serializer for primitive types.", nameof(type));
×
90
            
91
            return TomlCompositeSerializer.For(type, options);
×
92
        }
×
93
        
94
        /// <summary>
95
        /// Returns the default (reflection-based) deserializer for the given type. Can be useful if you're implementing your own custom deserializer but want to use the default behavior (e.g. to extend it or to use it as a fallback).
96
        /// </summary>
97
        /// <param name="type">The type to get the default deserializer for</param>
98
        /// <param name="options">The options to use for the deserializer</param>
99
        /// <returns>The default reflection-based deserializer for the given type.</returns>
100
        /// <exception cref="ArgumentException">Thrown if <paramref name="type"/> is a primitive type.</exception>
101
        public static Deserialize<object> GetDefaultDeserializerForType(Type type, TomlSerializerOptions? options = null)
102
        {
×
103
            options ??= TomlSerializerOptions.Default;
×
104
            if(type.IsPrimitive)
×
105
                throw new ArgumentException("Can't use reflection-based deserializer for primitive types.", nameof(type));
×
106
            
107
            return TomlCompositeDeserializer.For(type, options);
×
108
        }
×
109

110
        internal static Serialize<object> GetSerializer(Type t, TomlSerializerOptions? options)
111
        {
434✔
112
            options ??= TomlSerializerOptions.Default;
434✔
113
            
114
            if (Serializers.TryGetValue(t, out var value))
434✔
115
                return (Serialize<object>)value;
292✔
116

117
            //First check, lists and arrays get serialized as enumerables.
118
            if (t.IsArray || t is { Namespace: "System.Collections.Generic", Name: "List`1" })
142✔
119
            {
12✔
120
                var arrSerializer = GenericEnumerableSerializer();
12✔
121
                Serializers[t] = arrSerializer;
12✔
122
                return arrSerializer;
12✔
123
            }
124

125
            //Check for dicts and nullables
126
            if (t.IsGenericType &&  t.GetGenericArguments() is { } genericArgs)
130✔
127
            {
26✔
128
                if (t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
26✔
129
                {
2✔
130
                    var serializer = _genericDictionarySerializerMethod.MakeGenericMethod(genericArgs);
2✔
131

132
                    var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(t), serializer);
2✔
133
                    var ret = (Serialize<object>)(dict => (TomlValue?)del.DynamicInvoke(dict, options));
4✔
134
                    Serializers[t] = ret;
2✔
135

136
                    return ret;
2✔
137
                }
138

139
                if (t.GetGenericTypeDefinition() == typeof(Nullable<>))
24✔
140
                {
2✔
141
                    var serializer = _genericNullableSerializerMethod.MakeGenericMethod(genericArgs);
2✔
142
                    
143
                    var del = Delegate.CreateDelegate(typeof(ComplexSerialize<>).MakeGenericType(t), serializer);
2✔
144
                    var ret = (Serialize<object>)(dict => (TomlValue?)del.DynamicInvoke(dict, options));
4✔
145
                    Serializers[t] = ret;
2✔
146
                    
147
                    return ret;
2✔
148
                }
149
            }
22✔
150
            
151
            //Now we've got dicts out of the way we can check if we're dealing with something that's IEnumerable and if so serialize as an array. We do this only *after* checking for dictionaries, because we don't want to serialize dictionaries as enumerables (i.e. table-arrays)
152
            if (typeof(IEnumerable).IsAssignableFrom(t))
126✔
153
            {
2✔
154
                var enumerableSerializer = GenericEnumerableSerializer();
2✔
155
                Serializers[t] = enumerableSerializer;
2✔
156
                return enumerableSerializer;
2✔
157
            }
158

159
            return TomlCompositeSerializer.For(t, options);
124✔
160
        }
434✔
161

162
        internal static Deserialize<object> GetDeserializer(Type t, TomlSerializerOptions? options)
163
        {
322✔
164
            options ??= TomlSerializerOptions.Default;
322✔
165
            
166
            if (Deserializers.TryGetValue(t, out var value))
322✔
167
                return (Deserialize<object>)value;
216✔
168

169
            //We allow deserializing to IEnumerable fields/props, by setting them an array. We DO NOT do anything for classes that implement IEnumerable, though, because that would mess with deserializing lists, dictionaries, etc.
170
            if (t.IsArray || t.IsInterface && typeof(IEnumerable).IsAssignableFrom(t)) 
106✔
171
            {
6✔
172
                var elementType = t.IsInterface ? t.GetGenericArguments()[0] : t.GetElementType()!;
6✔
173
                var arrayDeserializer = ArrayDeserializerFor(elementType, options);
6✔
174
                Deserializers[t] = arrayDeserializer;
6✔
175
                return arrayDeserializer;
6✔
176
            }
177

178
            if (t.Namespace == "System.Collections.Generic" && t.Name == "List`1")
100✔
179
            {
4✔
180
                var listDeserializer = ListDeserializerFor(t.GetGenericArguments()[0], options);
4✔
181
                Deserializers[t] = listDeserializer;
4✔
182
                return listDeserializer;
4✔
183
            }
184
            
185
            if(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>) && t.GetGenericArguments() is {Length: 1})
96!
186
            {
2✔
187
                var nullableDeserializer = NullableDeserializerFor(t, options);
2✔
188
                Deserializers[t] = nullableDeserializer;
2✔
189
                return nullableDeserializer;
2✔
190
            }
191

192
            if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>) && t.GetGenericArguments() is { Length: 2 } genericArgs)
94!
193
            {
2✔
194
                if (genericArgs[0] == typeof(string))
2!
195
                {
2✔
196
                    return (Deserialize<object>)_stringKeyedDictionaryMethod.MakeGenericMethod(genericArgs[1]).Invoke(null, new object[]{options})!;
2✔
197
                }
198
            }
×
199

200
            return TomlCompositeDeserializer.For(t, options);
92✔
201
        }
322✔
202

203
        private static Serialize<object?> GenericEnumerableSerializer() =>
204
            o =>
14✔
205
            {
30✔
206
                if (o is not IEnumerable arr)
30!
207
                    throw new Exception("How did ArraySerializer end up getting a non-array?");
×
208

14✔
209
                var ret = new TomlArray();
30✔
210
                foreach (var entry in arr)
274✔
211
                {
92✔
212
                    ret.Add(entry);
92✔
213
                }
92✔
214

14✔
215
                return ret;
30✔
216
            };
44✔
217

218
        private static Deserialize<object> ArrayDeserializerFor(Type elementType, TomlSerializerOptions options) =>
219
            value =>
6✔
220
            {
26✔
221
                if (value is not TomlArray tomlArray)
26!
222
                    throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), elementType.MakeArrayType());
×
223

6✔
224
                var ret = Array.CreateInstance(elementType, tomlArray.Count);
26✔
225
                var deserializer = GetDeserializer(elementType, options);
26✔
226
                for (var index = 0; index < tomlArray.ArrayValues.Count; index++)
176✔
227
                {
62✔
228
                    var arrayValue = tomlArray.ArrayValues[index];
62✔
229
                    ret.SetValue(deserializer(arrayValue), index);
62✔
230
                }
62✔
231

6✔
232
                return ret;
26✔
233
            };
32✔
234

235
        private static Deserialize<object> ListDeserializerFor(Type elementType, TomlSerializerOptions options)
236
        {
4✔
237
            var listType = typeof(List<>).MakeGenericType(elementType);
4✔
238
            var relevantAddMethod = listType.GetMethod("Add")!;
4✔
239

240
            return value =>
4✔
241
            {
6✔
242
                if (value is not TomlArray tomlArray)
6!
243
                    throw new TomlTypeMismatchException(typeof(TomlArray), value.GetType(), listType);
×
244

4✔
245
                var ret = Activator.CreateInstance(listType)!;
6✔
246
                var deserializer = GetDeserializer(elementType, options);
6✔
247

4✔
248
                foreach (var arrayValue in tomlArray.ArrayValues)
78✔
249
                {
30✔
250
                    relevantAddMethod.Invoke(ret, new[] { deserializer(arrayValue) });
30✔
251
                }
30✔
252

4✔
253
                return ret;
6✔
254
            };
10✔
255
        }
4✔
256
        
257
        private static Deserialize<object> NullableDeserializerFor(Type nullableType, TomlSerializerOptions options)
258
        {
2✔
259
            var elementType = nullableType.GetGenericArguments()[0];
2✔
260
            var elementDeserializer = GetDeserializer(elementType, options);
2✔
261
            
262
            return value =>
2✔
263
            {
2✔
264
                //If we're deserializing, we know the value is not null
2✔
265
                var element = elementDeserializer(value);
2✔
266
                return Activator.CreateInstance(nullableType, element)!;
2✔
267
            };
4✔
268
        }
2✔
269

270
        private static Deserialize<Dictionary<string, T>> StringKeyedDictionaryDeserializerFor<T>(TomlSerializerOptions options)
271
        {
2✔
272
            var deserializer = GetDeserializer(typeof(T), options);
2✔
273

274
            return value =>
2✔
275
            {
2✔
276
                if (value is not TomlTable table)
2!
277
                    throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary<string, T>));
×
278

2✔
279
                return table.Entries.ToDictionary(entry => entry.Key, entry => (T)deserializer(entry.Value));
10✔
280
            };
4✔
281
        }
2✔
282
        
283
        private static TomlValue? GenericNullableSerializer<T>(T? nullable, TomlSerializerOptions options) where T : struct
284
        {
2✔
285
            var elementSerializer = GetSerializer(typeof(T), options);
2✔
286
            
287
            if (nullable.HasValue)
2!
288
                return elementSerializer(nullable.Value);
2✔
289

290
            return null;
×
291
        }
2✔
292

293
        private static TomlValue GenericDictionarySerializer<TKey, TValue>(Dictionary<TKey, TValue> dict, TomlSerializerOptions options) where TKey : notnull
294
        {
2✔
295
            var valueSerializer = GetSerializer(typeof(TValue), options);
2✔
296

297
            var ret = new TomlTable();
2✔
298
            foreach (var entry in dict)
14✔
299
            {
4✔
300
                var keyAsString = entry.Key.ToString();
4✔
301
                
302
                if(keyAsString == null)
4✔
303
                    continue;
×
304

305
                var value = valueSerializer(entry.Value);
4✔
306
                
307
                if(value == null)
4✔
308
                    continue;
×
309
                
310
                ret.PutValue(keyAsString, value, true);
4✔
311
            }
4✔
312

313
            return ret;
2✔
314
        }
2✔
315

316
        internal static void Register<T>(Serialize<T>? serializer, Deserialize<T>? deserializer)
317
        {
30✔
318
            if (serializer != null)
30✔
319
            {
30✔
320
                RegisterSerializer(serializer);
30✔
321

322
                RegisterDictionarySerializer(serializer);
30✔
323
            }
30✔
324

325
            if (deserializer != null)
30✔
326
            {
30✔
327
                RegisterDeserializer(deserializer);
30✔
328
                RegisterDictionaryDeserializer(deserializer);
30✔
329
            }
30✔
330
        }
30✔
331

332
        internal static void Register(Type t, Serialize<object>? serializer, Deserialize<object>? deserializer)
333
        {
210✔
334
            if (serializer != null)
210✔
335
                RegisterSerializer(serializer);
122✔
336

337
            if (deserializer != null)
210✔
338
                RegisterDeserializer(deserializer);
88✔
339
        }
210✔
340

341
        private static void RegisterDeserializer<T>(Deserialize<T> deserializer)
342
        {
148✔
343
            object BoxedDeserializer(TomlValue value) => deserializer.Invoke(value) ?? throw new Exception($"TOML Deserializer returned null for type {nameof(T)}");
234!
344
            Deserializers[typeof(T)] = (Deserialize<object>)BoxedDeserializer;
148✔
345
        }
148✔
346

347
        private static void RegisterSerializer<T>(Serialize<T> serializer)
348
        {
182✔
349
            TomlValue? ObjectAcceptingSerializer(object value) => serializer.Invoke((T)value);
274✔
350
            Serializers[typeof(T)] = (Serialize<object>)ObjectAcceptingSerializer!;
182✔
351
        }
182✔
352

353
        private static void RegisterDictionarySerializer<T>(Serialize<T> serializer)
354
        {
30✔
355
            RegisterSerializer<Dictionary<string, T>>(dict =>
30✔
356
            {
12✔
357
                var table = new TomlTable();
12✔
358

30✔
359
                if (dict == null)
12!
360
                    return table;
×
361

30✔
362
                var keys = dict.Keys.ToList();
12✔
363
                var values = dict.Values.Select(serializer.Invoke).ToList();
12✔
364

30✔
365
                for (var i = 0; i < keys.Count; i++)
72✔
366
                {
24✔
367
                    table.PutValue(keys[i], values[i], true);
24✔
368
                }
24✔
369

30✔
370
                return table;
12✔
371
            });
42✔
372
        }
30✔
373

374
        private static void RegisterDictionaryDeserializer<T>(Deserialize<T> deserializer)
375
        {
30✔
376
            RegisterDeserializer(value =>
30✔
377
            {
6✔
378
                if (value is not TomlTable table)
6!
379
                    throw new TomlTypeMismatchException(typeof(TomlTable), value.GetType(), typeof(Dictionary<string, T>));
×
380

30✔
381
                return table.Entries
6✔
382
                    .Select(kvp => new KeyValuePair<string, T>(kvp.Key, deserializer.Invoke(kvp.Value)))
12✔
383
                    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
30✔
384
            });
36✔
385
        }
30✔
386
    }
387
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc