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

LBreedlove / Queuebal.Expressions / 15762496729

19 Jun 2025 04:30PM UTC coverage: 88.035% (-1.6%) from 89.614%
15762496729

push

github

LBreedlove
Change Expression references to IExpression

260 of 285 branches covered (91.23%)

Branch coverage included in aggregate %.

16 of 48 new or added lines in 13 files covered. (33.33%)

1528 of 1746 relevant lines covered (87.51%)

196.65 hits per line

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

73.19
/Queuebal.Json/JSONValue.cs
1
using System.Runtime.CompilerServices;
2
using System.Text;
3
using System.Text.Json;
4

5
namespace Queuebal.Json;
6

7

8
/// <summary>
9
/// The types that can be stored in a JSONValue.
10
/// </summary>
11
public enum JSONFieldType
12
{
13
    Null,
14
    String,
15
    Boolean,
16
    Integer,
17
    Float,
18
    Dictionary,
19
    List,
20
    DateTime,
21
}
22

23
/// <summary>
24
/// Represents a JSONValue or object.
25
/// </summary>
26
public class JSONValue
27
{
28
    /// <summary>
29
    /// Stores the value of the JSONValue, when the value is a string.
30
    /// </summary>
31
    private readonly string? _stringValue;
32

33
    /// <summary>
34
    /// Stores the value of the JSONValue, when the value is a bool.
35
    /// </summary>
36
    private readonly bool? _boolValue;
37

38
    /// <summary>
39
    /// Stores the value of the JSONValue, when the value is an long.
40
    /// </summary>
41
    private readonly long? _intValue;
42

43
    /// <summary>
44
    /// Stores the value of the JSONValue, when the value is a float.
45
    /// </summary>
46
    private readonly double? _floatValue;
47

48
    /// <summary>
49
    /// Stores the value of the JSONValue, when the value is a DateTime.
50
    /// </summary>
51
    /// <remarks
52
    /// Although JSON doesn't support DateTime natively, we can store a
53
    /// DateTime value, then convert it to a string when serializing to JSON.
54
    /// When deserializing a JsonElement, strings will not be converted to DateTime automatically,
55
    /// </remarks>
56
    private readonly DateTime? _dateTimeValue;
57

58
    /// <summary>
59
    /// Stores the value of the JSONValue, when the value is a list of JSONValue objects.
60
    /// </summary>
61
    private readonly List<JSONValue>? _listValue;
62

63
    /// <summary>
64
    /// Stores the value of the JSONValue, when the value is a dictionary of string to JSONValue objects.
65
    /// </summary>
66
    private readonly Dictionary<string, JSONValue>? _dictValue;
67

68
    /// <summary>
69
    /// The type of JSONValue that is stored in this JSONValue instance.
70
    /// </summary>
71
    private readonly JSONFieldType _fieldType;
72

73
    /// <summary>
74
    /// Initializes a new instance of the JSONValue class, with a value of null.
75
    /// </summary>
76
    public JSONValue()
292✔
77
    {
292✔
78
        _fieldType = JSONFieldType.Null;
292✔
79
    }
292✔
80

81
    /// <summary>
82
    /// Creates a new JSONValue from a JsonElement.
83
    /// </summary>
84
    /// <param name="jsonElement">The JsonElement to create a JSONValue for.</param>
85
    public JSONValue(JsonElement jsonElement)
135✔
86
    {
135✔
87
        switch (jsonElement.ValueKind)
135✔
88
        {
89
            case JsonValueKind.Null:
90
                _fieldType = JSONFieldType.Null;
×
91
                break;
×
92
            case JsonValueKind.String:
93
                _fieldType = JSONFieldType.String;
49✔
94
                _stringValue = jsonElement.GetString();
49✔
95
                break;
49✔
96
            case JsonValueKind.False:
97
                _fieldType = JSONFieldType.Boolean;
×
98
                _boolValue = false;
×
99
                break;
×
100
            case JsonValueKind.True:
101
                _fieldType = JSONFieldType.Boolean;
×
102
                _boolValue = true;
×
103
                break;
×
104
            case JsonValueKind.Undefined:
105
                _fieldType = JSONFieldType.Null;
×
106
                break;
×
107
            case JsonValueKind.Number:
108
                if (jsonElement.TryGetInt64(out long intValue))
×
109
                {
×
110
                    _fieldType = JSONFieldType.Integer;
×
111
                    _intValue = intValue;
×
112
                }
×
113
                else if (jsonElement.TryGetDouble(out double floatValue))
×
114
                {
×
115
                    _fieldType = JSONFieldType.Float;
×
116
                    _floatValue = floatValue;
×
117
                }
×
118
                break;
×
119
            case JsonValueKind.Array:
120
                _fieldType = JSONFieldType.List;
19✔
121
                var listValue = new List<JSONValue>();
19✔
122
                foreach (var value in jsonElement.EnumerateArray())
155✔
123
                {
49✔
124
                    listValue.Add(new(value));
49✔
125
                }
49✔
126
                _listValue = listValue;
19✔
127
                break;
19✔
128
            case JsonValueKind.Object:
129
                _fieldType = JSONFieldType.Dictionary;
67✔
130
                var dict = new Dictionary<string, JSONValue>();
67✔
131
                foreach (var property in jsonElement.EnumerateObject())
335✔
132
                {
67✔
133
                    dict.Add(property.Name, new(property.Value));
67✔
134
                }
67✔
135
                _dictValue = dict;
67✔
136
                break;
67✔
137
        }
138
    }
135✔
139

140
    /// <summary>
141
    /// Initializes a new instance of the JSONValue class, with a value type of string.
142
    /// </summary>
143
    public JSONValue(string value)
266✔
144
    {
266✔
145
        _stringValue = value;
266✔
146
        _fieldType = JSONFieldType.String;
266✔
147
    }
266✔
148

149
    /// <summary>
150
    /// Initializes a new instance of the JSONValue class, with a value type of bool.
151
    /// </summary>
152
    public JSONValue(bool value)
30✔
153
    {
30✔
154
        _boolValue = value;
30✔
155
        _fieldType = JSONFieldType.Boolean;
30✔
156
    }
30✔
157

158
    /// <summary>
159
    /// Initializes a new instance of the JSONValue class, with a value type of integer.
160
    /// </summary>
161
    public JSONValue(long value)
4✔
162
    {
4✔
163
        _intValue = value;
4✔
164
        _fieldType = JSONFieldType.Integer;
4✔
165
    }
4✔
166

167
    /// <summary>
168
    /// Initializes a new instance of the JSONValue class, with a value type of integer.
169
    /// </summary>
170
    public JSONValue(int value)
26✔
171
    {
26✔
172
        _intValue = value;
26✔
173
        _fieldType = JSONFieldType.Integer;
26✔
174
    }
26✔
175

176
    /// <summary>
177
    /// Initializes a new instance of the JSONValue class, with a value type of double.
178
    /// </summary>
179
    public JSONValue(double value)
17✔
180
    {
17✔
181
        _floatValue = value;
17✔
182
        _fieldType = JSONFieldType.Float;
17✔
183
    }
17✔
184

185
    /// <summary>
186
    /// Initializes a new instance of the JSONValue class, with a value type of DateTime.
187
    /// </summary>
188
    public JSONValue(DateTime value)
12✔
189
    {
12✔
190
        _dateTimeValue = value;
12✔
191
        _fieldType = JSONFieldType.DateTime;
12✔
192
    }
12✔
193

194
    /// <summary>
195
    /// Initializes a new instance of the JSONValue class, with a value type of list.
196
    /// </summary>
197
    public JSONValue(List<JSONValue> value)
51✔
198
    {
51✔
199
        _listValue = value;
51✔
200
        _fieldType = JSONFieldType.List;
51✔
201
    }
51✔
202

203
    /// <summary>
204
    /// Initializes a new instance of the JSONValue class, with a value type of dictionary.
205
    /// </summary>
206
    public JSONValue(Dictionary<string, JSONValue> value)
71✔
207
    {
71✔
208
        _dictValue = value;
71✔
209
        _fieldType = JSONFieldType.Dictionary;
71✔
210
    }
71✔
211

212
    /// <summary>
213
    /// Creates a deep copy of the JSONValue and returns it.
214
    /// </summary>
215
    /// <returns>A new JSONValue containing a deep copy of the source value.</returns>
216
    /// <exception cref="InvalidOperationException">Thrown when the current JSONValue has an unsupported FieldType.</exception>
217
    public JSONValue Clone() => _fieldType switch
17✔
218
    {
17✔
219
        JSONFieldType.Null       => new JSONValue(),
1✔
220
        JSONFieldType.String     => new JSONValue(_stringValue!),
5✔
221
        JSONFieldType.Boolean    => new JSONValue((bool)_boolValue!),
3✔
222
        JSONFieldType.DateTime   => new JSONValue((DateTime)_dateTimeValue!),
1✔
223
        JSONFieldType.Integer    => new JSONValue((long)_intValue!),
4✔
224
        JSONFieldType.Float      => new JSONValue((double)_floatValue!),
1✔
225
        JSONFieldType.List       => CloneList(this),
1✔
226
        JSONFieldType.Dictionary => CloneObject(this),
1✔
227
        _ => throw new InvalidOperationException($"Invalid JSONValue field type: {_fieldType}"),
×
228
    };
17✔
229

230
    /// <summary>
231
    /// Clones a list JSONValue, recursively.
232
    /// </summary>
233
    /// <param name="source">The source object to clone.</param>
234
    /// <returns>A deep copy of th source object.</returns>
235
    private static JSONValue CloneList(JSONValue source)
236
    {
1✔
237
        var result = new List<JSONValue>();
1✔
238
        foreach (var item in source.ListValue)
9✔
239
        {
3✔
240
            result.Add(item.Clone());
3✔
241
        }
3✔
242

243
        return result;
1✔
244
    }
1✔
245

246
    /// <summary>
247
    /// Clones an object JSONValue, recursively.
248
    /// </summary>
249
    /// <param name="source">The source object to clone.</param>
250
    /// <returns>A deep copy of th source object.</returns>
251
    private static JSONValue CloneObject(JSONValue source)
252
    {
1✔
253
        var result = new Dictionary<string, JSONValue>();
1✔
254
        foreach (var kv in source.DictValue)
9✔
255
        {
3✔
256
            result[kv.Key] = kv.Value.Clone();
3✔
257
        }
3✔
258

259
        return result;
1✔
260
    }
1✔
261

262
    // we can disable these warnings, because we use the FieldType to determine which field
263
    // was set, and we don't allow users to create a JSONValue with a nullable reference/value-type
264
    // except when FieldType is JSONFieldType.Null.
265
#pragma warning disable CS8600, CS8603, CS8629
266

267
    /// <summary>
268
    /// Explicitly converts a JSONValue, holding a string value, to a string.
269
    /// </summary>
270
    /// <param name="value">The JSONValue to convert.</param>
271
    public static explicit operator string(JSONValue value)
272
    {
4✔
273
        if (value.FieldType != JSONFieldType.String)
4✔
274
        {
1✔
275
            throw new InvalidCastException("Cannot cast non-string JSONValue to string");
1✔
276
        }
277
        return value.StringValue;
3✔
278
    }
3✔
279

280
    /// <summary>
281
    /// Explicitly converts a JSONValue, holding a boolean value, to a bool.
282
    /// </summary>
283
    /// <param name="value">The JSONValue to convert.</param>
284
    public static explicit operator bool(JSONValue value)
285
    {
4✔
286
        if (value.FieldType != JSONFieldType.Boolean)
4✔
287
        {
1✔
288
            throw new InvalidCastException("Cannot cast non-boolean JSONValue to bool");
1✔
289
        }
290
        return value.BooleanValue;
3✔
291
    }
3✔
292

293
    /// <summary>
294
    /// Explicitly converts a JSONValue, holding a numeric value, to an int.
295
    /// </summary>
296
    /// <param name="value">The JSONValue to convert.</param>
297
    public static explicit operator int(JSONValue value)
298
    {
2✔
299
        if (value.FieldType != JSONFieldType.Integer && value.FieldType != JSONFieldType.Float)
2✔
300
        {
1✔
301
            throw new InvalidCastException("Cannot cast non-numeric JSONValue to long");
1✔
302
        }
303
        return (int)value.IntValue;
1✔
304
    }
1✔
305

306
    /// <summary>
307
    /// Explicitly converts a JSONValue, holding a numeric value, to an long.
308
    /// </summary>
309
    /// <param name="value">The JSONValue to convert.</param>
310
    public static explicit operator long(JSONValue value)
311
    {
5✔
312
        if (value.FieldType != JSONFieldType.Integer && value.FieldType != JSONFieldType.Float)
5✔
313
        {
1✔
314
            throw new InvalidCastException("Cannot cast non-numeric JSONValue to long");
1✔
315
        }
316
        return value.IntValue;
4✔
317
    }
4✔
318

319
    /// <summary>
320
    /// Explicitly converts a JSONValue, holding a numeric value, to a double.
321
    /// </summary>
322
    /// <param name="value">The JSONValue to convert.</param>
323
    public static explicit operator double(JSONValue value)
324
    {
4✔
325
        if (value.FieldType != JSONFieldType.Integer && value.FieldType != JSONFieldType.Float)
4✔
326
        {
1✔
327
            throw new InvalidCastException("Cannot cast non-numeric JSONValue to double");
1✔
328
        }
329
        return value.FloatValue;
3✔
330
    }
3✔
331

332
    /// <summary>
333
    /// Explicitly converts a JSONValue, holding a DateTime, string, or a numeric value, to a DateTime.
334
    /// </summary>
335
    /// <param name="value">The JSONValue to convert.</param>
336
    public static explicit operator DateTime(JSONValue value)
337
    {
6✔
338
        if (value.FieldType == JSONFieldType.DateTime)
6✔
339
        {
1✔
340
            return value.DateTimeValue;
1✔
341
        }
342
        else if (value.FieldType == JSONFieldType.String)
5✔
343
        {
4✔
344
            return ConvertStringToDateTime(value.StringValue);
4✔
345
        }
346
        else if (value.FieldType == JSONFieldType.Integer || value.FieldType == JSONFieldType.Float)
1✔
347
        {
1✔
348
            // If the value is a number, assume it's a Unix timestamp in seconds
349
            return DateTimeOffset.FromUnixTimeSeconds(value.IntValue).UtcDateTime;
1✔
350
        }
351

352
        throw new InvalidCastException("Unable to cast JSONValue to a DateTime");
×
353
    }
5✔
354

355
    /// <summary>
356
    /// Converts a string value to a DateTime, taking into account the format of the string.
357
    /// </summary>
358
    /// <param name="value">The value to convert.</param>
359
    /// <returns>A DateTime reflecting the value represented by the string.</returns>
360
    private static DateTime ConvertStringToDateTime(string value)
361
    {
4✔
362
        DateTime result;
363
        try
364
        {
4✔
365
            // Attempt to parse the string as a DateTime
366
            result = DateTime.Parse(value);
4✔
367
        }
3✔
368
        catch (FormatException)
1✔
369
        {
1✔
370
            // If parsing fails, throw an exception
371
            throw new InvalidCastException($"The string '{value}' is not in a valid DateTime format.");
1✔
372
        }
373

374
        if (value.EndsWith("Z", StringComparison.OrdinalIgnoreCase))
3✔
375
        {
1✔
376
            // If the string ends with 'Z', it is in UTC format
377
            return result.ToUniversalTime();
1✔
378
        }
379

380
        if (value.EndsWith("+00:00") || value.EndsWith("-00:00"))
2✔
381
        {
1✔
382
            // If the string ends with '+00:00' or '-00:00', it is in UTC offset format
383
            return result.ToUniversalTime();
1✔
384
        }
385

386
        // If ConvertToUtc is false, return the DateTime in the local time zone
387
        return DateTime.SpecifyKind(result, DateTimeKind.Local);
1✔
388
    }
3✔
389

390
    /// <summary>
391
    /// Explicitly converts a List JSONValue to a List of objects.
392
    /// </summary>
393
    /// <param name="value">The JSONValue to convert.</param>
394
    public static explicit operator List<object?>(JSONValue value)
395
    {
2✔
396
        if (value.FieldType != JSONFieldType.List)
2✔
397
        {
×
398
            throw new InvalidCastException("Cannot cast non-list JSONValue to List<object?>");
×
399
        }
400

401
        var result = new List<object?>();
2✔
402

403
        var data = value.ListValue;
2✔
404
        foreach (var listJSONValue in data)
22✔
405
        {
8✔
406
            if (listJSONValue.FieldType == JSONFieldType.Null)
8✔
407
            {
×
408
                result.Add(null);
×
409
            }
×
410
            else if (listJSONValue.FieldType == JSONFieldType.String)
8✔
411
            {
2✔
412
                result.Add((string)listJSONValue);
2✔
413
            }
2✔
414
            else if (listJSONValue.FieldType == JSONFieldType.Boolean)
6✔
415
            {
2✔
416
                result.Add((bool)listJSONValue);
2✔
417
            }
2✔
418
            else if (listJSONValue.FieldType == JSONFieldType.Float)
4✔
419
            {
2✔
420
                result.Add((double)listJSONValue);
2✔
421
            }
2✔
422
            else if (listJSONValue.FieldType == JSONFieldType.Integer)
2✔
423
            {
2✔
424
                result.Add((long)listJSONValue);
2✔
425
            }
2✔
426
            else if (listJSONValue.FieldType == JSONFieldType.List)
×
427
            {
×
428
                result.Add((List<object?>)listJSONValue);
×
429
            }
×
430
            else if (listJSONValue.FieldType == JSONFieldType.Dictionary)
×
431
            {
×
432
                result.Add((Dictionary<string, object?>)listJSONValue);
×
433
            }
×
434
            else
435
            {
×
436
                throw new InvalidCastException("Unexpected data type stored in JSONValue list");
×
437
            }
438
        }
8✔
439

440
        return result;
2✔
441
    }
2✔
442

443
    /// <summary>
444
    /// Explicitly converts a JSONValue holding a dictionary to a Dictionary<string, object?> value.
445
    /// </summary>
446
    /// <remarks>
447
    /// This operator recursively casts the elements of the dictionary in the JSONValue. Depending on
448
    /// the size of your dictionary data, this call may take longer than expected.
449
    /// </remarks>
450
    /// <param name="value">The JSONValue to convert.</param>
451
    public static explicit operator Dictionary<string, object?>(JSONValue value)
452
    {
1✔
453
        if (value.FieldType != JSONFieldType.Dictionary)
1✔
454
        {
×
455
            throw new InvalidCastException("Cannot cast non-dictionary JSONValue to Dictionary<string, object?>");
×
456
        }
457

458
        Dictionary<string, object?> result = new();
1✔
459
        foreach (var keyValuePair in value.DictValue)
13✔
460
        {
5✔
461
            if (keyValuePair.Value.FieldType == JSONFieldType.String)
5✔
462
            {
1✔
463
                result.Add(keyValuePair.Key, (string)keyValuePair.Value);
1✔
464
            }
1✔
465
            else if (keyValuePair.Value.FieldType == JSONFieldType.Boolean)
4✔
466
            {
1✔
467
                result.Add(keyValuePair.Key, (bool)keyValuePair.Value);
1✔
468
            }
1✔
469
            else if (keyValuePair.Value.FieldType == JSONFieldType.Float)
3✔
470
            {
1✔
471
                result.Add(keyValuePair.Key, (double)keyValuePair.Value);
1✔
472
            }
1✔
473
            else if (keyValuePair.Value.FieldType == JSONFieldType.Integer)
2✔
474
            {
1✔
475
                result.Add(keyValuePair.Key, (long)keyValuePair.Value);
1✔
476
            }
1✔
477
            else if (keyValuePair.Value.FieldType == JSONFieldType.List)
1✔
478
            {
1✔
479
                result.Add(keyValuePair.Key, (List<object?>)keyValuePair.Value);
1✔
480
            }
1✔
481
            else if (keyValuePair.Value.FieldType == JSONFieldType.Dictionary)
×
482
            {
×
483
                result.Add(keyValuePair.Key, (Dictionary<string, object?>)keyValuePair.Value);
×
484
            }
×
485
            else
486
            {
×
487
                throw new InvalidCastException("Unexpected data type stored in JSONValue dictionary");
×
488
            }
489
        }
5✔
490

491
        return result;
1✔
492
    }
1✔
493

494
    /// <summary>
495
    /// Converts a JSONValue to a Dictionary<string, JSONValue> object.
496
    /// </summary>
497
    /// <param name="value">The JSONValue to convert.</param>
498
    public static explicit operator Dictionary<string, JSONValue>(JSONValue value)
499
    {
1✔
500
        if (value.FieldType != JSONFieldType.Dictionary)
1✔
501
        {
1✔
502
            throw new InvalidCastException("Cannot cast non-dictionary JSONValue to Dictionary<string, JSONValue>");
1✔
503
        }
504

505
        Dictionary<string, JSONValue> output = new();
×
506
        if (value._dictValue == null)
×
507
        {
×
508
            return output;
×
509
        }
510

511
        foreach (var keyValue in value._dictValue)
×
512
        {
×
513
            output[keyValue.Key] = keyValue.Value;
×
514
        }
×
515

516
        return output;
×
517
    }
×
518

519
    /// <summary>
520
    /// Converts a JSONValue to a List<JSONValue> object.
521
    /// </summary>
522
    /// <param name="value">The JSONValue to convert.</param>
523
    public static explicit operator List<JSONValue>(JSONValue value)
524
    {
2✔
525
        if (value.FieldType != JSONFieldType.List)
2✔
526
        {
1✔
527
            throw new InvalidCastException("Cannot cast non-list JSONValue to List<JSONValue>");
1✔
528
        }
529

530
        return value.ListValue.Select(v => v).ToList();
4✔
531
    }
1✔
532

533
    /// <summary>
534
    /// Implicitly converts a string value to a JSONValue.
535
    /// </summary>
536
    /// <param name="value">The value to store in the JSONValue.</param>
537
    public static implicit operator JSONValue(string value) => new(value);
21✔
538

539
    /// <summary>
540
    /// Implicitly converts a boolean value to a JSONValue.
541
    /// </summary>
542
    /// <param name="value">The value to store in the JSONValue.</param>
543
    public static implicit operator JSONValue(bool value) => new(value);
14✔
544

545
    /// <summary>
546
    /// Implicitly converts an integer value to a JSONValue.
547
    /// </summary>
548
    /// <param name="value">The value to store in the JSONValue.</param>
549
    public static implicit operator JSONValue(int value) => new(value);
4✔
550

551
    /// <summary>
552
    /// Implicitly converts a long value to a JSONValue.
553
    /// </summary>
554
    /// <param name="value">The value to store in the JSONValue.</param>
555
    public static implicit operator JSONValue(long value) => new(value);
×
556

557
    /// <summary>
558
    /// Implicitly converts a double value to a JSONValue.
559
    /// </summary>
560
    /// <param name="value">The value to store in the JSONValue.</param>
561
    public static implicit operator JSONValue(double value) => new(value);
4✔
562

563
    /// <summary>
564
    /// Implicitly converts a DateTime value to a JSONValue.
565
    /// </summary>
566
    /// <param name="value">The value to store in the JSONValue.</param>
567
    public static implicit operator JSONValue(DateTime value) => new(value);
4✔
568

569
    /// <summary>
570
    /// Implicitly converts a double value to a JSONValue.
571
    /// </summary>
572
    /// <param name="value">The value to store in the JSONValue.</param>
573
    public static implicit operator JSONValue(float value) => new((double)value);
×
574

575
    /// <summary>
576
    /// Implicitly converts an array of object?'s into a JSONValue.
577
    /// </summary>
578
    /// <param name="value">The value to store in the JSONValue.</param>
579
    public static implicit operator JSONValue(object?[] value)
580
    {
×
581
        return value.ToList();
×
582
    }
×
583

584
    /// <summary>
585
    /// Implicitly converts a List of object?'s into a JSONValue.
586
    /// </summary>
587
    /// <param name="value">The value to store in the JSONValue.</param>
588
    public static implicit operator JSONValue(List<object?> value)
589
    {
2✔
590
        List<JSONValue> result = new();
2✔
591
        foreach (var listValue in value)
22✔
592
        {
8✔
593
            var valueType = listValue?.GetType();
8✔
594
            if (listValue == null)
8✔
595
            {
×
596
                result.Add(new());
×
597
            }
×
598
            else if (valueType == typeof(string))
8✔
599
            {
2✔
600
                result.Add((string)listValue);
2✔
601
            }
2✔
602
            else if (valueType == typeof(bool))
6✔
603
            {
2✔
604
                result.Add((bool)listValue);
2✔
605
            }
2✔
606
            else if (valueType == typeof(long))
4✔
607
            {
×
608
                result.Add((long)listValue);
×
609
            }
×
610
            else if (valueType == typeof(int))
4✔
611
            {
2✔
612
                result.Add((int)listValue);
2✔
613
            }
2✔
614
            else if (valueType == typeof(double))
2✔
615
            {
2✔
616
                result.Add((double)listValue);
2✔
617
            }
2✔
618
            else if (valueType == typeof(DateTime))
×
619
            {
×
620
                result.Add((DateTime)listValue);
×
621
            }
×
622
            else if (valueType == typeof(List<object?>))
×
623
            {
×
624
                result.Add((List<object?>)listValue);
×
625
            }
×
626
            else if (valueType == typeof(List<object>))
×
627
            {
×
628
                result.Add((List<object?>)listValue);
×
629
            }
×
630
            else if (valueType == typeof(Dictionary<string, object?>))
×
631
            {
×
632
                result.Add((Dictionary<string, object?>)listValue);
×
633
            }
×
634
            else if (valueType == typeof(Dictionary<string, object>))
×
635
            {
×
636
                result.Add((Dictionary<string, object?>)listValue);
×
637
            }
×
638
            else if (valueType == typeof(JsonElement))
×
639
            {
×
640
                result.Add(new((JsonElement)listValue));
×
641
            }
×
642
        }
8✔
643
        return new JSONValue(result);
2✔
644
    }
2✔
645

646
    /// <summary>
647
    /// Implicitly converts an Dictionary of object?'s into a JSONValue.
648
    /// </summary>
649
    /// <param name="value">The value to store in the JSONValue.</param>
650
    public static implicit operator JSONValue(Dictionary<string, object?> sourceValue)
651
    {
1✔
652
        Dictionary<string, JSONValue> result = new();
1✔
653
        foreach (var keyValue in sourceValue)
13✔
654
        {
5✔
655
            var key = keyValue.Key;
5✔
656
            var value = keyValue.Value;
5✔
657
            var valueType = value?.GetType();
5✔
658

659
            if (value == null)
5✔
660
            {
×
661
                result.Add(key, new());
×
662
            }
×
663
            else if (valueType == typeof(string))
5✔
664
            {
1✔
665
                result.Add(key, (string)value);
1✔
666
            }
1✔
667
            else if (valueType == typeof(bool))
4✔
668
            {
1✔
669
                result.Add(key, (bool)value);
1✔
670
            }
1✔
671
            else if (valueType == typeof(long))
3✔
672
            {
×
673
                result.Add(key, (long)value);
×
674
            }
×
675
            else if (valueType == typeof(int))
3✔
676
            {
1✔
677
                result.Add(key, (int)value);
1✔
678
            }
1✔
679
            else if (valueType == typeof(double))
2✔
680
            {
1✔
681
                result.Add(key, (double)value);
1✔
682
            }
1✔
683
            else if (valueType == typeof(DateTime))
1✔
684
            {
×
685
                result.Add(key, (DateTime)value);
×
686
            }
×
687
            else if (valueType == typeof(float))
1✔
688
            {
×
689
                result.Add(key, (double)(float)value);
×
690
            }
×
691
            else if (valueType == typeof(List<object?>))
1✔
692
            {
1✔
693
                result.Add(key, (List<object?>)value);
1✔
694
            }
1✔
695
            else if (valueType == typeof(Dictionary<string, object?>))
×
696
            {
×
697
                result.Add(key, (Dictionary<string, object?>)value);
×
698
            }
×
699
            else if (valueType == typeof(JsonElement))
×
700
            {
×
701
                result.Add(key, new((JsonElement)value));
×
702
            }
×
703
        }
5✔
704
        return new JSONValue(result);
1✔
705
    }
1✔
706

707
    /// <summary>
708
    /// Creates a JSONValue containing a list of JSONValue's.
709
    /// </summary>
710
    /// <param name="sourceValue">The source item to convert to a JSONValue.</param>
711
    public static implicit operator JSONValue(List<JSONValue> sourceValue) => new(sourceValue);
16✔
712

713
    /// <summary>
714
    /// Converts a Dictionary<string, JSONValue> to a JSONValue wrapping the dictionary.
715
    /// </summary>
716
    /// <param name="sourceValue">The source value to convert to a JSONValue.</param>
717
    public static implicit operator JSONValue(Dictionary<string, JSONValue> sourceValue) => new JSONValue(sourceValue);
27✔
718

719
    /// <summary>
720
    /// Gets the string value of the object, or raises an InvalidOperationException if a string value was not used
721
    /// to set the JSONValue.
722
    /// </summary>
723
    public string StringValue => FieldType == JSONFieldType.String ? _stringValue : throw new InvalidOperationException();
164✔
724

725
    /// <summary>
726
    /// Gets the boolean value of the object, or raises an InvalidOperationException if a bool value was not used
727
    /// to set the JSONValue.
728
    /// </summary>
729
    public bool BooleanValue => FieldType == JSONFieldType.Boolean ? (bool)_boolValue : throw new InvalidOperationException();
16✔
730

731
    /// <summary>
732
    /// Gets the DateTime value of the object, or raises an InvalidOperationException if a DateTime value was not used
733
    /// to set the JSONValue.
734
    /// </summary>
735
    public DateTime DateTimeValue => FieldType == JSONFieldType.DateTime ? (DateTime)_dateTimeValue : throw new InvalidOperationException();
11✔
736

737
    /// <summary>
738
    /// Gets the double value of the object, or raises an InvalidOperationException if a double value was not used
739
    /// to set the JSONValue.
740
    /// </summary>
741
    public double FloatValue
742
    {
743
        get
744
        {
6✔
745
            if (FieldType == JSONFieldType.Float)
6✔
746
            {
5✔
747
                return (double)_floatValue;
5✔
748
            }
749

750
            if (FieldType == JSONFieldType.Integer)
1✔
751
            {
1✔
752
                return (double)_intValue;
1✔
753
            }
754

755
            throw new InvalidOperationException();
×
756
        }
6✔
757
    }
758

759
    /// <summary>
760
    /// Gets the integer value of the object, or raises an InvalidOperationException if an long value was not used
761
    /// to set the JSONValue.
762
    /// </summary>
763
    public long IntValue
764
    {
765
        get
766
        {
11✔
767
            if (FieldType == JSONFieldType.Integer)
11✔
768
            {
8✔
769
                return (long)_intValue;
8✔
770
            }
771

772
            if (FieldType == JSONFieldType.Float)
3✔
773
            {
3✔
774
                return (long)_floatValue;
3✔
775
            }
776

777
            throw new InvalidOperationException();
×
778
        }
11✔
779
    }
780

781
    /// <summary>
782
    /// Gets the list value of the object, or raises an InvalidOperationException if a list value was not used
783
    /// to set the JSONValue.
784
    /// </summary>
785
    public List<JSONValue> ListValue => FieldType == JSONFieldType.List ? _listValue : throw new InvalidOperationException();
204✔
786

787
    /// <summary>
788
    /// Gets the Dictionary value of the object, or raises an InvalidOperationException if a dict value was not used
789
    /// to set the JSONValue.
790
    /// </summary>
791
    public Dictionary<string, JSONValue> DictValue => FieldType == JSONFieldType.Dictionary ? _dictValue : throw new InvalidOperationException();
190✔
792

793
    #pragma warning restore CS8600, CS8603, CS8629
794

795
    /// <summary>
796
    /// Indicates if the JSONValue stores a list value.
797
    /// </summary>
798
    public bool IsList => _fieldType == JSONFieldType.List;
48✔
799

800
    /// <summary>
801
    /// Indicates if the JSONValue stores an object/dictionary value.
802
    /// </summary>
803
    public bool IsObject => _fieldType == JSONFieldType.Dictionary;
103✔
804

805
    /// <summary>
806
    /// Indicates if the JSONValue stores a numeric value (float or integer).
807
    /// </summary>
808
    public bool IsNumber => _fieldType == JSONFieldType.Integer || _fieldType == JSONFieldType.Float;
6✔
809

810
    /// <summary>
811
    /// Indicates if the JSONValue stores a floating point value (float or double).
812
    /// </summary>
813
    public bool IsFloat => _fieldType == JSONFieldType.Float;
×
814

815
    /// <summary>
816
    /// Indicates if the JSONValue stores an integer value (long or int).
817
    /// </summary>
818
    public bool IsInteger => _fieldType == JSONFieldType.Integer;
1✔
819

820
    /// <summary>
821
    /// Indicates if the JSONValue stores a string value.
822
    /// </summary>
823
    public bool IsString => _fieldType == JSONFieldType.String;
186✔
824

825
    /// <summary>
826
    /// Indicates if the JSONValue stores a boolean value.
827
    /// </summary>
828
    public bool IsBoolean => _fieldType == JSONFieldType.Boolean;
1✔
829

830
    /// <summary>
831
    /// Indicates if the JSONValue has a value of null.
832
    /// </summary>
833
    public bool IsNull => _fieldType == JSONFieldType.Null;
54✔
834

835
    /// <summary>
836
    /// Indicates if the JSONValue stores a DateTime value.
837
    /// </summary>
838
    public bool IsDateTime => _fieldType == JSONFieldType.DateTime;
10✔
839

840
    /// <summary>
841
    /// Gets the value that of the JSON field, as a nullable object.
842
    /// </summary>
843
    public object? Value => _fieldType switch
31✔
844
    {
31✔
845
        JSONFieldType.Null => null,
×
846
        JSONFieldType.String => _stringValue,
22✔
847
        JSONFieldType.Boolean => _boolValue,
2✔
848
        JSONFieldType.Integer => _intValue,
3✔
849
        JSONFieldType.Float => _floatValue,
2✔
850
        JSONFieldType.List => _listValue,
1✔
851
        JSONFieldType.Dictionary => _dictValue,
1✔
852
        JSONFieldType.DateTime => _dateTimeValue,
×
853
        _ => null,
×
854
    };
31✔
855

856
    /// <summary>
857
    /// Gets the type of field that was set.
858
    /// </summary>
859
    public JSONFieldType FieldType => _fieldType;
716✔
860

861
    /// <summary>
862
    /// Compares the provided object to the current JSONValue.
863
    /// </summary>
864
    /// <param name="obj">The object to compare to the JSONValue.</param>
865
    /// <returns>true if the provided object is the equivalent of the JSONValue, otherwise false.</returns>
866
    public override bool Equals(object? obj)
867
    {
90✔
868
        if (obj == null)
90✔
869
        {
×
NEW
870
            return IsNull;
×
871
        }
872

873
        if (object.ReferenceEquals(this, obj))
90✔
874
        {
1✔
875
            return true;
1✔
876
        }
877

878
        if (obj.GetType() != typeof(JSONValue))
89✔
879
        {
×
880
            return false;
×
881
        }
882

883
        var json_obj = (JSONValue)obj;
89✔
884
        if (json_obj._fieldType != _fieldType)
89✔
885
        {
1✔
886
            return false;
1✔
887
        }
888

889
        return _fieldType switch
88✔
890
        {
88✔
891
            JSONFieldType.Null => true, // we already checked the field type and they're both Null
6✔
892
            JSONFieldType.String => _stringValue == json_obj._stringValue,
32✔
893
            JSONFieldType.Boolean => _boolValue == json_obj._boolValue,
4✔
894
            JSONFieldType.Integer => _intValue == json_obj._intValue,
4✔
895
            JSONFieldType.Float => _floatValue == json_obj._floatValue,
3✔
896
            JSONFieldType.DateTime => _dateTimeValue == json_obj._dateTimeValue,
×
897
            JSONFieldType.List => ListValueEquals(json_obj.ListValue),
16✔
898
            JSONFieldType.Dictionary => DictValueEquals(json_obj.DictValue),
23✔
899
            _ => throw new InvalidCastException("Unexpected JSONValue type"),
×
900
        };
88✔
901
    }
90✔
902

903
    /// <summary>Gets a hash code for the current object.</summary>
904
    public override int GetHashCode()
905
    {
×
906
        var typeHashCode = _fieldType.GetHashCode();
×
907
        int valueHashCode;
908
        switch (_fieldType)
×
909
        {
910
            case JSONFieldType.String:
911
                valueHashCode = (_stringValue ?? "").GetHashCode();
×
912
                break;
×
913
            case JSONFieldType.Boolean:
914
                valueHashCode = _boolValue.GetHashCode();
×
915
                break;
×
916
            case JSONFieldType.Integer:
917
                valueHashCode = _intValue.GetHashCode();
×
918
                break;
×
919
            case JSONFieldType.Float:
920
                valueHashCode = _floatValue.GetHashCode();
×
921
                break;
×
922
            case JSONFieldType.DateTime:
923
                valueHashCode = _dateTimeValue.GetHashCode();
×
924
                break;
×
925
            case JSONFieldType.List:
926
                valueHashCode = (_listValue ?? new()).GetHashCode();
×
927
                break;
×
928
            case JSONFieldType.Dictionary:
929
                valueHashCode = (_dictValue ?? new()).GetHashCode();
×
930
                break;
×
931
            case JSONFieldType.Null:
932
                valueHashCode = 0;
×
933
                break;
×
934
            default:
935
                throw new InvalidCastException("Unexpected JSONValue type");
×
936
        };
×
937
        return typeHashCode ^ valueHashCode;
×
938
    }
×
939

940
    /// <summary>
941
    /// Determines if the provided list equals the List Value stored in this JSONValue.
942
    /// </summary>
943
    /// <param name="value">The value to compare to our ListValue.</param>
944
    /// <returns>true if the two lists are equal/equivalent, otherwise false.</returns>
945
    private bool ListValueEquals(List<JSONValue> value)
946
    {
16✔
947
        if (object.ReferenceEquals(this._listValue, value))
16✔
948
        {
×
949
            return true;
×
950
        }
951

952
        if (_fieldType != JSONFieldType.List)
16✔
953
        {
×
954
            return false;
×
955
        }
956

957
        var compare = ListValue;
16✔
958
        if (compare.Count != value.Count)
16✔
959
        {
×
960
            return false;
×
961
        }
962

963
        for (int idx = 0; idx < compare.Count; ++idx)
98✔
964
        {
33✔
965
            if (!compare[idx].Equals(value[idx]))
33✔
966
            {
×
967
                return false;
×
968
            }
969
        }
33✔
970

971
        return true;
16✔
972
    }
16✔
973

974
    /// <summary>
975
    /// Determines if the provided dictionary is equal/equivalent to the dictionary
976
    /// stored in this JSONValue.
977
    /// </summary>
978
    /// <param name="value">The dictionary to compare to this JSONValue's dictionary.</param>
979
    /// <returns>true if the two dictionaries are equal/equivalent, otherwise false.</returns>
980
    private bool DictValueEquals(Dictionary<string, JSONValue> value)
981
    {
23✔
982
        if (_dictValue == null)
23✔
983
        {
×
984
            return false;
×
985
        }
986

987
        if (object.ReferenceEquals(_dictValue, value))
23✔
988
        {
1✔
989
            return true;
1✔
990
        }
991

992
        if (_fieldType != JSONFieldType.Dictionary)
22✔
993
        {
×
994
            return false;
×
995
        }
996

997
        var compare = DictValue;
22✔
998
        foreach (var keyValue in value)
122✔
999
        {
28✔
1000
            if (!compare.ContainsKey(keyValue.Key))
28✔
1001
            {
×
1002
                return false;
×
1003
            }
1004

1005
            if (!compare[keyValue.Key].Equals(keyValue.Value))
28✔
1006
            {
×
1007
                return false;
×
1008
            }
1009
        }
28✔
1010

1011
        foreach (var keyValue in _dictValue)
122✔
1012
        {
28✔
1013
            if (!value.ContainsKey(keyValue.Key))
28✔
1014
            {
×
1015
                return false;
×
1016
            }
1017
        }
28✔
1018
        return true;
22✔
1019
    }
23✔
1020

1021
    /// <summary>
1022
    /// Converts the JSONValue to a string.
1023
    /// </summary>
1024
    /// <returns>A string representing the JSONValue.</returns>
1025
    /// <exception cref="InvalidCastException">Raised when an invalid JSONFieldType is assigned to the JSONValue.</exception>
1026
    public override string ToString()
1027
    {
13✔
1028
        var value = Value;
13✔
1029
        if (value == null)
13✔
1030
        {
×
1031
            return "null";
×
1032
        }
1033

1034
        return FieldType switch
13✔
1035
        {
13✔
1036
            JSONFieldType.Null => "null",
×
1037
            JSONFieldType.String => (string)value,
4✔
1038
            JSONFieldType.Boolean => (bool)value ? "true" : "false",
2✔
1039
            JSONFieldType.Float => ((double)value).ToString(),
2✔
1040
            JSONFieldType.Integer => ((long)value).ToString(),
3✔
1041
            JSONFieldType.DateTime => ((DateTime)value).ToString("o"), // ISO 8601 format
×
1042
            JSONFieldType.List => GetListValueString((List<JSONValue>)value),
1✔
1043
            JSONFieldType.Dictionary => GetDictValueString((Dictionary<string, JSONValue>)value),
1✔
1044
            _ => throw new InvalidCastException("Unexpected JSONValue type"),
×
1045
        };
13✔
1046
    }
13✔
1047

1048
    /// <summary>
1049
    /// Gets the value of the list as a string, in JSON format.
1050
    /// </summary>
1051
    /// <param name="value">The list value to convert to a string.</param>
1052
    /// <returns>A string representing the list value.</returns>
1053
    string GetListValueString(List<JSONValue> value)
1054
    {
1✔
1055
        StringBuilder builder = new();
1✔
1056
        builder.Append('[');
1✔
1057
        var isFirstItem = true;
1✔
1058
        foreach (var item in value)
11✔
1059
        {
4✔
1060
            if (!isFirstItem)
4✔
1061
            {
3✔
1062
                builder.Append(", ");
3✔
1063
            }
3✔
1064

1065
            isFirstItem = false;
4✔
1066
            if (item.FieldType == JSONFieldType.String)
4✔
1067
            {
1✔
1068
                builder.Append($"\"{item.ToString()}\"");
1✔
1069
            }
1✔
1070
            else
1071
            {
3✔
1072
                builder.Append(item.ToString());
3✔
1073
            }
3✔
1074
        }
4✔
1075

1076
        builder.Append(']');
1✔
1077
        return builder.ToString();
1✔
1078
    }
1✔
1079

1080
    /// <summary>
1081
    /// Gets the value of the dict as a string, in JSON format.
1082
    /// </summary>
1083
    /// <param name="value">The dict value to convert to a string.</param>
1084
    /// <returns>A string representing the dict value.</returns>
1085
    string GetDictValueString(Dictionary<string, JSONValue> value)
1086
    {
1✔
1087
        bool isFirstItem = true;
1✔
1088

1089
        StringBuilder builder = new();
1✔
1090
        builder.Append('{');
1✔
1091
        foreach (var item in value)
13✔
1092
        {
5✔
1093
            if (!isFirstItem)
5✔
1094
            {
4✔
1095
                builder.Append(", ");
4✔
1096
            }
4✔
1097

1098
            isFirstItem = false;
5✔
1099
            if (item.Value.FieldType == JSONFieldType.String)
5✔
1100
            {
1✔
1101
                // wrap the value in quotes.
1102
                builder.Append($"\"{item.Key}\": \"{item.Value.ToString()}\"");
1✔
1103
            }
1✔
1104
            else
1105
            {
4✔
1106
                builder.Append($"\"{item.Key}\": {item.Value.ToString()}");
4✔
1107
            }
4✔
1108
        }
5✔
1109
        builder.Append('}');
1✔
1110
        return builder.ToString();
1✔
1111
    }
1✔
1112
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc