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

LBreedlove / Queuebal.Expressions / 15957417891

29 Jun 2025 04:58PM UTC coverage: 95.895% (+2.7%) from 93.169%
15957417891

push

github

LBreedlove
Add test coverage for JSONValue

384 of 389 branches covered (98.71%)

Branch coverage included in aggregate %.

1 of 3 new or added lines in 1 file covered. (33.33%)

5 existing lines in 1 file now uncovered.

2396 of 2510 relevant lines covered (95.46%)

471.95 hits per line

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

87.55
/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()
831✔
77
    {
831✔
78
        _fieldType = JSONFieldType.Null;
831✔
79
    }
831✔
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)
286✔
86
    {
286✔
87
        switch (jsonElement.ValueKind)
286✔
88
        {
89
            case JsonValueKind.Null:
90
                _fieldType = JSONFieldType.Null;
1✔
91
                break;
1✔
92
            case JsonValueKind.String:
93
                _fieldType = JSONFieldType.String;
118✔
94
                _stringValue = jsonElement.GetString();
118✔
95
                break;
118✔
96
            case JsonValueKind.False:
97
                _fieldType = JSONFieldType.Boolean;
5✔
98
                _boolValue = false;
5✔
99
                break;
5✔
100
            case JsonValueKind.True:
101
                _fieldType = JSONFieldType.Boolean;
7✔
102
                _boolValue = true;
7✔
103
                break;
7✔
104
            case JsonValueKind.Undefined:
105
                _fieldType = JSONFieldType.Null;
×
106
                break;
×
107
            case JsonValueKind.Number:
108
                if (jsonElement.TryGetInt64(out long intValue))
29✔
109
                {
14✔
110
                    _fieldType = JSONFieldType.Integer;
14✔
111
                    _intValue = intValue;
14✔
112
                }
14✔
113
                else if (jsonElement.TryGetDouble(out double floatValue))
15✔
114
                {
15✔
115
                    _fieldType = JSONFieldType.Float;
15✔
116
                    _floatValue = floatValue;
15✔
117
                }
15✔
118
                break;
29✔
119
            case JsonValueKind.Array:
120
                _fieldType = JSONFieldType.List;
27✔
121
                var listValue = new List<JSONValue>();
27✔
122
                foreach (var value in jsonElement.EnumerateArray())
233✔
123
                {
76✔
124
                    listValue.Add(new(value));
76✔
125
                }
76✔
126
                _listValue = listValue;
27✔
127
                break;
27✔
128
            case JsonValueKind.Object:
129
                _fieldType = JSONFieldType.Dictionary;
99✔
130
                var dict = new Dictionary<string, JSONValue>();
99✔
131
                foreach (var property in jsonElement.EnumerateObject())
637✔
132
                {
170✔
133
                    dict.Add(property.Name, new(property.Value));
170✔
134
                }
170✔
135
                _dictValue = dict;
99✔
136
                break;
99✔
137
        }
138
    }
286✔
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)
709✔
144
    {
709✔
145
        _stringValue = value;
709✔
146
        _fieldType = JSONFieldType.String;
709✔
147
    }
709✔
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)
87✔
153
    {
87✔
154
        _boolValue = value;
87✔
155
        _fieldType = JSONFieldType.Boolean;
87✔
156
    }
87✔
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)
17✔
162
    {
17✔
163
        _intValue = value;
17✔
164
        _fieldType = JSONFieldType.Integer;
17✔
165
    }
17✔
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)
179✔
171
    {
179✔
172
        _intValue = value;
179✔
173
        _fieldType = JSONFieldType.Integer;
179✔
174
    }
179✔
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)
34✔
180
    {
34✔
181
        _floatValue = value;
34✔
182
        _fieldType = JSONFieldType.Float;
34✔
183
    }
34✔
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)
16✔
189
    {
16✔
190
        _dateTimeValue = value;
16✔
191
        _fieldType = JSONFieldType.DateTime;
16✔
192
    }
16✔
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)
132✔
198
    {
132✔
199
        _listValue = value;
132✔
200
        _fieldType = JSONFieldType.List;
132✔
201
    }
132✔
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)
151✔
207
    {
151✔
208
        _dictValue = value;
151✔
209
        _fieldType = JSONFieldType.Dictionary;
151✔
210
    }
151✔
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
    {
7✔
273
        if (value.FieldType != JSONFieldType.String)
7✔
274
        {
1✔
275
            throw new InvalidCastException("Cannot cast non-string JSONValue to string");
1✔
276
        }
277
        return value.StringValue;
6✔
278
    }
6✔
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
    {
5✔
286
        if (value.FieldType != JSONFieldType.Boolean)
5✔
287
        {
1✔
288
            throw new InvalidCastException("Cannot cast non-boolean JSONValue to bool");
1✔
289
        }
290
        return value.BooleanValue;
4✔
291
    }
4✔
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
    {
6✔
312
        if (value.FieldType != JSONFieldType.Integer && value.FieldType != JSONFieldType.Float)
6✔
313
        {
1✔
314
            throw new InvalidCastException("Cannot cast non-numeric JSONValue to long");
1✔
315
        }
316
        return value.IntValue;
5✔
317
    }
5✔
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
        {
3✔
344
            return ConvertStringToDateTime(value.StringValue);
3✔
345
        }
346
        else if (value.FieldType == JSONFieldType.Integer || value.FieldType == JSONFieldType.Float)
2✔
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");
1✔
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
    {
3✔
362
        DateTime result;
363
        try
364
        {
3✔
365
            // Attempt to parse the string as a DateTime
366
            result = DateTime.Parse(value);
3✔
367
        }
3✔
UNCOV
368
        catch (FormatException)
×
UNCOV
369
        {
×
370
            // If parsing fails, throw an exception
UNCOV
371
            throw new InvalidCastException($"The string '{value}' is not in a valid DateTime format.");
×
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
    {
5✔
396
        if (value.FieldType != JSONFieldType.List)
5✔
397
        {
1✔
398
            throw new InvalidCastException("Cannot cast non-list JSONValue to List<object?>");
1✔
399
        }
400

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

403
        var data = value.ListValue;
4✔
404
        foreach (var listJSONValue in data)
40✔
405
        {
14✔
406
            if (listJSONValue.FieldType == JSONFieldType.Null)
14✔
407
            {
1✔
408
                result.Add(null);
1✔
409
            }
1✔
410
            else if (listJSONValue.FieldType == JSONFieldType.String)
13✔
411
            {
4✔
412
                result.Add((string)listJSONValue);
4✔
413
            }
4✔
414
            else if (listJSONValue.FieldType == JSONFieldType.Boolean)
9✔
415
            {
3✔
416
                result.Add((bool)listJSONValue);
3✔
417
            }
3✔
418
            else if (listJSONValue.FieldType == JSONFieldType.Float)
6✔
419
            {
2✔
420
                result.Add((double)listJSONValue);
2✔
421
            }
2✔
422
            else if (listJSONValue.FieldType == JSONFieldType.Integer)
4✔
423
            {
3✔
424
                result.Add((long)listJSONValue);
3✔
425
            }
3✔
426
            else if (listJSONValue.FieldType == JSONFieldType.List)
1✔
427
            {
1✔
428
                result.Add((List<object?>)listJSONValue);
1✔
429
            }
1✔
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
        }
14✔
439

440
        return result;
4✔
441
    }
4✔
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
    {
3✔
453
        if (value.FieldType != JSONFieldType.Dictionary)
3✔
454
        {
1✔
455
            throw new InvalidCastException("Cannot cast non-dictionary JSONValue to Dictionary<string, object?>");
1✔
456
        }
457

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

491
        return result;
2✔
492
    }
2✔
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
    {
2✔
500
        if (value.FieldType != JSONFieldType.Dictionary)
2✔
501
        {
1✔
502
            throw new InvalidCastException("Cannot cast non-dictionary JSONValue to Dictionary<string, JSONValue>");
1✔
503
        }
504

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

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

516
        return output;
1✔
517
    }
1✔
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
    {
1✔
525
        if (value.FieldType != JSONFieldType.List)
1✔
526
        {
1✔
527
            throw new InvalidCastException("Cannot cast non-list JSONValue to List<JSONValue>");
1✔
528
        }
529

UNCOV
530
        return value.ListValue.Select(v => v).ToList();
×
UNCOV
531
    }
×
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);
235✔
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);
64✔
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);
146✔
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);
13✔
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);
14✔
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);
6✔
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);
3✔
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
    {
4✔
590
        List<JSONValue> result = new();
4✔
591
        foreach (var listValue in value)
42✔
592
        {
15✔
593
            var valueType = listValue?.GetType();
15✔
594
            if (listValue == null)
15✔
595
            {
1✔
596
                result.Add(new());
1✔
597
            }
1✔
598
            else if (valueType == typeof(string))
14✔
599
            {
4✔
600
                result.Add((string)listValue);
4✔
601
            }
4✔
602
            else if (valueType == typeof(bool))
10✔
603
            {
3✔
604
                result.Add((bool)listValue);
3✔
605
            }
3✔
606
            else if (valueType == typeof(long))
7✔
607
            {
1✔
608
                result.Add((long)listValue);
1✔
609
            }
1✔
610
            else if (valueType == typeof(int))
6✔
611
            {
2✔
612
                result.Add((int)listValue);
2✔
613
            }
2✔
614
            else if (valueType == typeof(double))
4✔
615
            {
2✔
616
                result.Add((double)listValue);
2✔
617
            }
2✔
618
            else if (valueType == typeof(DateTime))
2✔
619
            {
×
620
                result.Add((DateTime)listValue);
×
621
            }
×
622
            else if (valueType == typeof(List<object?>))
2✔
623
            {
1✔
624
                result.Add((List<object?>)listValue);
1✔
625
            }
1✔
626
            else if (valueType == typeof(Dictionary<string, object?>))
1✔
627
            {
1✔
628
                result.Add((Dictionary<string, object?>)listValue);
1✔
629
            }
1✔
630
            else if (valueType == typeof(JsonElement))
×
631
            {
×
632
                result.Add(new((JsonElement)listValue));
×
633
            }
×
634
        }
15✔
635
        return new JSONValue(result);
4✔
636
    }
4✔
637

638
    /// <summary>
639
    /// Implicitly converts an Dictionary of object?'s into a JSONValue.
640
    /// </summary>
641
    /// <param name="value">The value to store in the JSONValue.</param>
642
    public static implicit operator JSONValue(Dictionary<string, object?> sourceValue)
643
    {
3✔
644
        Dictionary<string, JSONValue> result = new();
3✔
645
        foreach (var keyValue in sourceValue)
25✔
646
        {
8✔
647
            var key = keyValue.Key;
8✔
648
            var value = keyValue.Value;
8✔
649
            var valueType = value?.GetType();
8✔
650

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

699
    /// <summary>
700
    /// Creates a JSONValue containing a list of JSONValue's.
701
    /// </summary>
702
    /// <param name="sourceValue">The source item to convert to a JSONValue.</param>
703
    public static implicit operator JSONValue(List<JSONValue> sourceValue) => new(sourceValue);
87✔
704

705
    /// <summary>
706
    /// Converts a Dictionary<string, JSONValue> to a JSONValue wrapping the dictionary.
707
    /// </summary>
708
    /// <param name="sourceValue">The source value to convert to a JSONValue.</param>
709
    public static implicit operator JSONValue(Dictionary<string, JSONValue> sourceValue) => new JSONValue(sourceValue);
85✔
710

711
    /// <summary>
712
    /// Gets the string value of the object, or raises an InvalidOperationException if a string value was not used
713
    /// to set the JSONValue.
714
    /// </summary>
715
    public string StringValue => FieldType == JSONFieldType.String ? _stringValue : throw new InvalidOperationException();
454✔
716

717
    /// <summary>
718
    /// Gets the boolean value of the object, or raises an InvalidOperationException if a bool value was not used
719
    /// to set the JSONValue.
720
    /// </summary>
721
    public bool BooleanValue => FieldType == JSONFieldType.Boolean ? (bool)_boolValue : throw new InvalidOperationException();
47✔
722

723
    /// <summary>
724
    /// Gets the DateTime value of the object, or raises an InvalidOperationException if a DateTime value was not used
725
    /// to set the JSONValue.
726
    /// </summary>
727
    public DateTime DateTimeValue => FieldType == JSONFieldType.DateTime ? (DateTime)_dateTimeValue : throw new InvalidOperationException();
11✔
728

729
    /// <summary>
730
    /// Gets the double value of the object, or raises an InvalidOperationException if a double value was not used
731
    /// to set the JSONValue.
732
    /// </summary>
733
    public double FloatValue
734
    {
735
        get
736
        {
58✔
737
            if (FieldType == JSONFieldType.Float)
58✔
738
            {
13✔
739
                return (double)_floatValue;
13✔
740
            }
741

742
            if (FieldType == JSONFieldType.Integer)
45✔
743
            {
45✔
744
                return (double)_intValue;
45✔
745
            }
746

747
            throw new InvalidOperationException();
×
748
        }
58✔
749
    }
750

751
    /// <summary>
752
    /// Gets the integer value of the object, or raises an InvalidOperationException if an long value was not used
753
    /// to set the JSONValue.
754
    /// </summary>
755
    public long IntValue
756
    {
757
        get
758
        {
75✔
759
            if (FieldType == JSONFieldType.Integer)
75✔
760
            {
72✔
761
                return (long)_intValue;
72✔
762
            }
763

764
            if (FieldType == JSONFieldType.Float)
3✔
765
            {
3✔
766
                return (long)_floatValue;
3✔
767
            }
768

769
            throw new InvalidOperationException();
×
770
        }
75✔
771
    }
772

773
    /// <summary>
774
    /// Gets the list value of the object, or raises an InvalidOperationException if a list value was not used
775
    /// to set the JSONValue.
776
    /// </summary>
777
    public List<JSONValue> ListValue => FieldType == JSONFieldType.List ? _listValue : throw new InvalidOperationException();
308✔
778

779
    /// <summary>
780
    /// Gets the Dictionary value of the object, or raises an InvalidOperationException if a dict value was not used
781
    /// to set the JSONValue.
782
    /// </summary>
783
    public Dictionary<string, JSONValue> DictValue => FieldType == JSONFieldType.Dictionary ? _dictValue : throw new InvalidOperationException();
358✔
784

785
    #pragma warning restore CS8600, CS8603, CS8629
786

787
    /// <summary>
788
    /// Indicates if the JSONValue stores a list value.
789
    /// </summary>
790
    public bool IsList => _fieldType == JSONFieldType.List;
89✔
791

792
    /// <summary>
793
    /// Indicates if the JSONValue stores an object/dictionary value.
794
    /// </summary>
795
    public bool IsDict => _fieldType == JSONFieldType.Dictionary;
215✔
796

797
    /// <summary>
798
    /// Indicates if the JSONValue stores a numeric value (float or integer).
799
    /// </summary>
800
    public bool IsNumber => _fieldType == JSONFieldType.Integer || _fieldType == JSONFieldType.Float;
174✔
801

802
    /// <summary>
803
    /// Indicates if the JSONValue stores a floating point value (float or double).
804
    /// </summary>
805
    public bool IsFloat => _fieldType == JSONFieldType.Float;
×
806

807
    /// <summary>
808
    /// Indicates if the JSONValue stores an integer value (long or int).
809
    /// </summary>
810
    public bool IsInteger => _fieldType == JSONFieldType.Integer;
15✔
811

812
    /// <summary>
813
    /// Indicates if the JSONValue stores a string value.
814
    /// </summary>
815
    public bool IsString => _fieldType == JSONFieldType.String;
882✔
816

817
    /// <summary>
818
    /// Indicates if the JSONValue stores a boolean value.
819
    /// </summary>
820
    public bool IsBoolean => _fieldType == JSONFieldType.Boolean;
1✔
821

822
    /// <summary>
823
    /// Indicates if the JSONValue has a value of null.
824
    /// </summary>
825
    public bool IsNull => _fieldType == JSONFieldType.Null;
219✔
826

827
    /// <summary>
828
    /// Indicates if the JSONValue stores a DateTime value.
829
    /// </summary>
830
    public bool IsDateTime => _fieldType == JSONFieldType.DateTime;
10✔
831

832
    /// <summary>
833
    /// Gets the value that of the JSON field, as a nullable object.
834
    /// </summary>
835
    public object? Value => _fieldType switch
76✔
836
    {
76✔
837
        JSONFieldType.Null => null,
×
838
        JSONFieldType.String => _stringValue,
50✔
839
        JSONFieldType.Boolean => _boolValue,
2✔
840
        JSONFieldType.Integer => _intValue,
6✔
841
        JSONFieldType.Float => _floatValue,
5✔
842
        JSONFieldType.List => _listValue,
2✔
843
        JSONFieldType.Dictionary => _dictValue,
11✔
844
        JSONFieldType.DateTime => _dateTimeValue,
×
845
        _ => null,
×
846
    };
76✔
847

848
    /// <summary>
849
    /// Gets the type of field that was set.
850
    /// </summary>
851
    public JSONFieldType FieldType => _fieldType;
1,698✔
852

853
    /// <summary>
854
    /// Compares the provided object to the current JSONValue.
855
    /// </summary>
856
    /// <param name="obj">The object to compare to the JSONValue.</param>
857
    /// <returns>true if the provided object is the equivalent of the JSONValue, otherwise false.</returns>
858
    public override bool Equals(object? obj)
859
    {
213✔
860
        if (obj is null)
213✔
861
        {
×
862
            return IsNull;
×
863
        }
864

865
        if (object.ReferenceEquals(this, obj))
213✔
866
        {
5✔
867
            return true;
5✔
868
        }
869

870
        if (obj.GetType() != typeof(JSONValue))
208✔
871
        {
×
872
            return false;
×
873
        }
874

875
        var json_obj = (JSONValue)obj;
208✔
876
        if (json_obj._fieldType != _fieldType)
208✔
877
        {
6✔
878
            return false;
6✔
879
        }
880

881
        return _fieldType switch
202✔
882
        {
202✔
883
            JSONFieldType.Null => true, // we already checked the field type and they're both Null
9✔
884
            JSONFieldType.String => _stringValue == json_obj._stringValue,
84✔
885
            JSONFieldType.Boolean => _boolValue == json_obj._boolValue,
15✔
886
            JSONFieldType.Integer => _intValue == json_obj._intValue,
21✔
887
            JSONFieldType.Float => _floatValue == json_obj._floatValue,
10✔
888
            JSONFieldType.DateTime => _dateTimeValue == json_obj._dateTimeValue,
×
889
            JSONFieldType.List => ListValueEquals(json_obj.ListValue),
22✔
890
            JSONFieldType.Dictionary => DictValueEquals(json_obj.DictValue),
41✔
891
            _ => throw new InvalidCastException("Unexpected JSONValue type"),
×
892
        };
202✔
893
    }
213✔
894

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

932
    /// <summary>
933
    /// Determines if the provided list equals the List Value stored in this JSONValue.
934
    /// </summary>
935
    /// <param name="value">The value to compare to our ListValue.</param>
936
    /// <returns>true if the two lists are equal/equivalent, otherwise false.</returns>
937
    private bool ListValueEquals(List<JSONValue> value)
938
    {
22✔
939
        if (object.ReferenceEquals(this._listValue, value))
22✔
940
        {
×
941
            return true;
×
942
        }
943

944
        if (_fieldType != JSONFieldType.List)
22✔
945
        {
×
946
            return false;
×
947
        }
948

949
        var compare = ListValue;
22✔
950
        if (compare.Count != value.Count)
22✔
951
        {
×
952
            return false;
×
953
        }
954

955
        for (int idx = 0; idx < compare.Count; ++idx)
156✔
956
        {
56✔
957
            if (!compare[idx].Equals(value[idx]))
56✔
958
            {
×
959
                return false;
×
960
            }
961
        }
56✔
962

963
        return true;
22✔
964
    }
22✔
965

966
    /// <summary>
967
    /// Determines if the provided dictionary is equal/equivalent to the dictionary
968
    /// stored in this JSONValue.
969
    /// </summary>
970
    /// <param name="value">The dictionary to compare to this JSONValue's dictionary.</param>
971
    /// <returns>true if the two dictionaries are equal/equivalent, otherwise false.</returns>
972
    private bool DictValueEquals(Dictionary<string, JSONValue> value)
973
    {
41✔
974
        if (_dictValue == null)
41✔
975
        {
×
976
            return false;
×
977
        }
978

979
        if (object.ReferenceEquals(_dictValue, value))
41✔
980
        {
1✔
981
            return true;
1✔
982
        }
983

984
        if (_fieldType != JSONFieldType.Dictionary)
40✔
985
        {
×
986
            return false;
×
987
        }
988

989
        var compare = DictValue;
40✔
990
        if (compare.Count != value.Count)
40✔
NEW
991
        {
×
NEW
992
            return false;
×
993
        }
994

995
        foreach (var keyValue in value)
296✔
996
        {
88✔
997
            if (!compare.ContainsKey(keyValue.Key))
88✔
998
            {
×
999
                return false;
×
1000
            }
1001

1002
            if (!compare[keyValue.Key].Equals(keyValue.Value))
88✔
1003
            {
×
1004
                return false;
×
1005
            }
1006
        }
88✔
1007

1008
        return true;
40✔
1009
    }
41✔
1010

1011
    /// <summary>
1012
    /// Converts the JSONValue to a string.
1013
    /// </summary>
1014
    /// <returns>A string representing the JSONValue.</returns>
1015
    /// <exception cref="InvalidCastException">Raised when an invalid JSONFieldType is assigned to the JSONValue.</exception>
1016
    public override string ToString()
1017
    {
57✔
1018
        var value = Value;
57✔
1019
        if (value is null)
57✔
1020
        {
×
1021
            return "null";
×
1022
        }
1023

1024
        return FieldType switch
57✔
1025
        {
57✔
1026
            JSONFieldType.Null => "null",
×
1027
            JSONFieldType.String => (string)value,
31✔
1028
            JSONFieldType.Boolean => (bool)value ? "true" : "false",
2✔
1029
            JSONFieldType.Float => ((double)value).ToString(),
5✔
1030
            JSONFieldType.Integer => ((long)value).ToString(),
6✔
1031
            JSONFieldType.DateTime => ((DateTime)value).ToString("o"), // ISO 8601 format
×
1032
            JSONFieldType.List => GetListValueString((List<JSONValue>)value),
2✔
1033
            JSONFieldType.Dictionary => GetDictValueString((Dictionary<string, JSONValue>)value),
11✔
1034
            _ => throw new InvalidCastException("Unexpected JSONValue type"),
×
1035
        };
57✔
1036
    }
57✔
1037

1038
    /// <summary>
1039
    /// Gets the value of the list as a string, in JSON format.
1040
    /// </summary>
1041
    /// <param name="value">The list value to convert to a string.</param>
1042
    /// <returns>A string representing the list value.</returns>
1043
    string GetListValueString(List<JSONValue> value)
1044
    {
2✔
1045
        StringBuilder builder = new();
2✔
1046
        builder.Append('[');
2✔
1047
        var isFirstItem = true;
2✔
1048
        foreach (var item in value)
18✔
1049
        {
6✔
1050
            if (!isFirstItem)
6✔
1051
            {
4✔
1052
                builder.Append(", ");
4✔
1053
            }
4✔
1054

1055
            isFirstItem = false;
6✔
1056
            if (item.FieldType == JSONFieldType.String)
6✔
1057
            {
1✔
1058
                builder.Append($"\"{item.ToString()}\"");
1✔
1059
            }
1✔
1060
            else
1061
            {
5✔
1062
                builder.Append(item.ToString());
5✔
1063
            }
5✔
1064
        }
6✔
1065

1066
        builder.Append(']');
2✔
1067
        return builder.ToString();
2✔
1068
    }
2✔
1069

1070
    /// <summary>
1071
    /// Gets the value of the dict as a string, in JSON format.
1072
    /// </summary>
1073
    /// <param name="value">The dict value to convert to a string.</param>
1074
    /// <returns>A string representing the dict value.</returns>
1075
    string GetDictValueString(Dictionary<string, JSONValue> value)
1076
    {
11✔
1077
        bool isFirstItem = true;
11✔
1078

1079
        StringBuilder builder = new();
11✔
1080
        builder.Append('{');
11✔
1081
        foreach (var item in value)
115✔
1082
        {
41✔
1083
            if (!isFirstItem)
41✔
1084
            {
30✔
1085
                builder.Append(", ");
30✔
1086
            }
30✔
1087

1088
            isFirstItem = false;
41✔
1089
            if (item.Value.FieldType == JSONFieldType.String)
41✔
1090
            {
23✔
1091
                // wrap the value in quotes.
1092
                builder.Append($"\"{item.Key}\": \"{item.Value.ToString()}\"");
23✔
1093
            }
23✔
1094
            else
1095
            {
18✔
1096
                builder.Append($"\"{item.Key}\": {item.Value.ToString()}");
18✔
1097
            }
18✔
1098
        }
41✔
1099
        builder.Append('}');
11✔
1100
        return builder.ToString();
11✔
1101
    }
11✔
1102
}
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