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

loresoft / FluentCommand / 26556850121

28 May 2026 05:33AM UTC coverage: 54.377%. First build
26556850121

push

github

pwelter34
Handle nulls in ParameterJson

1348 of 3241 branches covered (41.59%)

Branch coverage included in aggregate %.

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

4162 of 6892 relevant lines covered (60.39%)

314.02 hits per line

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

80.75
/src/FluentCommand.Json/JsonCommandExtensions.cs
1
using System.Buffers;
2
using System.Data;
3
using System.Data.Common;
4
using System.Globalization;
5
using System.Text;
6
using System.Text.Json;
7
using System.Text.Json.Serialization.Metadata;
8

9
using FluentCommand.Extensions;
10

11
using Microsoft.IO;
12

13
namespace FluentCommand;
14

15
/// <summary>
16
/// Extension methods for <see cref="IDataCommand"/>
17
/// </summary>
18
public static class JsonCommandExtensions
19
{
20
    private static readonly RecyclableMemoryStreamManager _memoryStreamManager = new();
1✔
21

22
    /// <summary>
23
    /// Adds a new parameter with the specified <paramref name="name" /> with the <paramref name="value" /> serialized as JSON using the specified <paramref name="options" />.
24
    /// </summary>
25
    /// <typeparam name="TParameter">The type of the parameter value.</typeparam>
26
    /// <param name="dataCommand">The <see cref="IDataCommand"/> for this extension method.</param>
27
    /// <param name="name">The name of the parameter.</param>
28
    /// <param name="value">The value to be serialized as JSON and added as a string parameter.</param>
29
    /// <param name="options">The <see cref="JsonSerializerOptions"/> to use when serializing.</param>
30
    /// <returns>
31
    /// A fluent <see langword="interface" /> to the data command.
32
    /// </returns>
33
    public static IDataCommand ParameterJson<TParameter>(
34
        this IDataCommand dataCommand,
35
        string name,
36
        TParameter value,
37
        JsonSerializerOptions? options = null)
38
    {
39
        if (value is null)
3✔
40
            return dataCommand.Parameter(name, (string?)null);
1✔
41

42
        var json = JsonSerializer.Serialize(value, options);
2✔
43
        return dataCommand.Parameter(name, json);
2✔
44
    }
45

46
    /// <summary>
47
    /// Adds a new parameter with the specified <paramref name="name" /> with the <paramref name="value" /> serialized as JSON using the specified <paramref name="jsonTypeInfo" />.
48
    /// </summary>
49
    /// <typeparam name="TParameter">The type of the parameter value.</typeparam>
50
    /// <param name="dataCommand">The <see cref="IDataCommand"/> for this extension method.</param>
51
    /// <param name="name">The name of the parameter.</param>
52
    /// <param name="value">The value to be serialized as JSON and added as a string parameter.</param>
53
    /// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo{T}"/> to use when serializing.</param>
54
    /// <returns>
55
    /// A fluent <see langword="interface" /> to the data command.
56
    /// </returns>
57
    public static IDataCommand ParameterJson<TParameter>(
58
        this IDataCommand dataCommand,
59
        string name,
60
        TParameter value,
61
        JsonTypeInfo<TParameter> jsonTypeInfo)
62
    {
63
        if (value is null)
1!
NEW
64
            return dataCommand.Parameter(name, (string?)null);
×
65

66
        var json = JsonSerializer.Serialize(value, jsonTypeInfo);
1✔
67
        return dataCommand.Parameter(name, json);
1✔
68
    }
69

70

71
    /// <summary>
72
    /// Executes the query and returns a JSON string from data set returned by the query.
73
    /// </summary>
74
    /// <param name="dataCommand">The data command.</param>
75
    /// <param name="options">The <see cref="JsonWriterOptions" /> options.</param>
76
    /// <returns>
77
    /// A JSON string representing the <see cref="IDataReader" /> result of the command.
78
    /// </returns>
79
    public static string QueryJson(
80
        this IDataCommand dataCommand,
81
        JsonWriterOptions options = default)
82
    {
83
        if (dataCommand is null)
1!
84
            throw new ArgumentNullException(nameof(dataCommand));
×
85

86
        using var stream = _memoryStreamManager.GetStream();
1✔
87

88
        QueryJson(dataCommand, stream, options);
1✔
89

90
        var bytes = stream.GetReadOnlySequence();
1✔
91

92
#if NET5_0_OR_GREATER
93
        return Encoding.UTF8.GetString(bytes);
1✔
94
#else
95
        return Encoding.UTF8.GetString(bytes.ToArray());
96
#endif
97
    }
1✔
98

99
    /// <summary>
100
    /// Executes the query and returns a JSON string from data set returned by the query.
101
    /// </summary>
102
    /// <param name="dataCommand">The data command.</param>
103
    /// <param name="stream">The destination for writing JSON text.</param>
104
    /// <param name="options">The <see cref="JsonWriterOptions" /> options.</param>
105
    /// <returns>
106
    /// A JSON string representing the <see cref="IDataReader" /> result of the command.
107
    /// </returns>
108
    public static void QueryJson(
109
        this IDataCommand dataCommand,
110
        Stream stream,
111
        JsonWriterOptions options = default)
112
    {
113
        if (dataCommand is null)
2!
114
            throw new ArgumentNullException(nameof(dataCommand));
×
115
        if (stream is null)
2!
116
            throw new ArgumentNullException(nameof(stream));
×
117

118
        var writer = new Utf8JsonWriter(stream, options);
2✔
119

120
        writer.WriteStartArray();
2✔
121

122
        dataCommand.Read(reader => WriteData(reader, writer), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult);
2✔
123

124
        writer.WriteEndArray();
2✔
125

126
        writer.Flush();
2✔
127
    }
2✔
128

129

130
    /// <summary>
131
    /// Executes the query and returns a JSON string from data set returned by the query asynchronously.
132
    /// </summary>
133
    /// <param name="dataCommand">The data command.</param>
134
    /// <param name="options">The <see cref="JsonWriterOptions" /> options.</param>
135
    /// <param name="cancellationToken">The cancellation token.</param>
136
    /// <returns>
137
    /// A JSON string representing the <see cref="IDataReader" /> result of the command.
138
    /// </returns>
139
    public static async Task<string> QueryJsonAsync(
140
        this IDataCommand dataCommand,
141
        JsonWriterOptions options = default,
142
        CancellationToken cancellationToken = default)
143
    {
144
        if (dataCommand is null)
3!
145
            throw new ArgumentNullException(nameof(dataCommand));
×
146

147
        using var stream = _memoryStreamManager.GetStream();
3✔
148

149
        await QueryJsonAsync(dataCommand, stream, options, cancellationToken);
3✔
150

151
        var bytes = stream.GetReadOnlySequence();
3✔
152

153
#if NET5_0_OR_GREATER
154
        return Encoding.UTF8.GetString(bytes);
3✔
155
#else
156
        return Encoding.UTF8.GetString(bytes.ToArray());
157
#endif
158

159
    }
3✔
160

161
    /// <summary>
162
    /// Executes the query and returns a JSON string from data set returned by the query asynchronously.
163
    /// </summary>
164
    /// <param name="dataCommand">The data command.</param>
165
    /// <param name="stream">The destination for writing JSON text.</param>
166
    /// <param name="options">The <see cref="JsonWriterOptions" /> options.</param>
167
    /// <param name="cancellationToken">The cancellation token.</param>
168
    /// <returns>
169
    /// A JSON string representing the <see cref="IDataReader" /> result of the command.
170
    /// </returns>
171
    public static async Task QueryJsonAsync(
172
        this IDataCommand dataCommand,
173
        Stream stream,
174
        JsonWriterOptions options = default,
175
        CancellationToken cancellationToken = default)
176
    {
177
        if (dataCommand is null)
4!
178
            throw new ArgumentNullException(nameof(dataCommand));
×
179
        if (stream is null)
4!
180
            throw new ArgumentNullException(nameof(stream));
×
181

182
        var writer = new Utf8JsonWriter(stream, options);
4✔
183

184
        writer.WriteStartArray();
4✔
185

186
        await dataCommand.ReadAsync(async (reader, token) =>
4✔
187
        {
4✔
188
            if (reader is DbDataReader dataReader)
4✔
189
                await WriteDataAsync(dataReader, writer, token);
4✔
190
            else
4✔
191
                WriteData(reader, writer);
4✔
192

4✔
193
        }, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancellationToken);
4✔
194

195
        writer.WriteEndArray();
4✔
196

197
        await writer.FlushAsync(cancellationToken);
4✔
198
    }
4✔
199

200

201
    private static void WriteData(
202
        IDataReader reader,
203
        Utf8JsonWriter writer)
204
    {
205
        while (reader.Read())
1,618✔
206
        {
207
            WriteObject(reader, writer);
1,616✔
208
        }
209
    }
2✔
210

211
    private static async Task WriteDataAsync(
212
        DbDataReader reader,
213
        Utf8JsonWriter writer,
214
        CancellationToken cancellationToken = default)
215
    {
216
        while (await reader.ReadAsync(cancellationToken))
1,629✔
217
        {
218
            WriteObject(reader, writer);
1,625✔
219
        }
220
    }
4✔
221

222
    private static void WriteObject(
223
        IDataReader reader,
224
        Utf8JsonWriter writer)
225
    {
226
        writer.WriteStartObject();
3,241✔
227

228
        for (int index = 0; index < reader.FieldCount; index++)
129,574✔
229
        {
230
            var name = reader.GetName(index);
61,546✔
231
            writer.WritePropertyName(name);
61,546✔
232

233
            WriteValue(reader, writer, index);
61,546✔
234
        }
235

236
        writer.WriteEndObject();
3,241✔
237
    }
3,241✔
238

239
    private static void WriteValue(
240
        IDataReader reader,
241
        Utf8JsonWriter writer,
242
        int index)
243
    {
244
        if (reader.IsDBNull(index))
61,546✔
245
        {
246
            writer.WriteNullValue();
22,704✔
247
            return;
22,704✔
248
        }
249

250
        var type = reader.GetFieldType(index);
38,842✔
251
        if (type == typeof(string))
38,842✔
252
        {
253
            var value = reader.GetString(index);
12,887✔
254
            writer.WriteStringValue(value);
12,887✔
255
            return;
12,887✔
256
        }
257

258
        if (type == typeof(bool))
25,955✔
259
        {
260
            var value = reader.GetBoolean(index);
9,707✔
261
            writer.WriteBooleanValue(value);
9,707✔
262
            return;
9,707✔
263
        }
264

265
        if (type == typeof(byte))
16,248!
266
        {
267
            var value = reader.GetByte(index);
×
268
            writer.WriteNumberValue(value);
×
269
            return;
×
270
        }
271

272
        if (type == typeof(short))
16,248✔
273
        {
274
            var value = reader.GetInt16(index);
5✔
275
            writer.WriteNumberValue(value);
5✔
276
            return;
5✔
277
        }
278

279
        if (type == typeof(int))
16,243✔
280
        {
281
            var value = reader.GetInt32(index);
3,244✔
282
            writer.WriteNumberValue(value);
3,244✔
283
            return;
3,244✔
284
        }
285

286
        if (type == typeof(long))
12,999✔
287
        {
288
            var value = reader.GetInt64(index);
8✔
289
            writer.WriteNumberValue(value);
8✔
290
            return;
8✔
291
        }
292

293
        if (type == typeof(float))
12,991✔
294
        {
295
            var value = reader.GetFloat(index);
5✔
296
            writer.WriteNumberValue(value);
5✔
297
            return;
5✔
298
        }
299

300
        if (type == typeof(double))
12,986✔
301
        {
302
            var value = reader.GetDouble(index);
5✔
303
            writer.WriteNumberValue(value);
5✔
304
            return;
5✔
305
        }
306

307
        if (type == typeof(decimal))
12,981✔
308
        {
309
            var value = reader.GetDecimal(index);
5✔
310
            writer.WriteNumberValue(value);
5✔
311
            return;
5✔
312
        }
313

314
#if NET6_0_OR_GREATER
315
        if (type == typeof(DateOnly))
12,976!
316
        {
317
            var value = reader.GetValue<DateOnly>(index);
×
318
            var formatted = value.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture);
×
319

320
            writer.WriteStringValue(formatted);
×
321
            return;
×
322
        }
323

324
        if (type == typeof(TimeOnly))
12,976!
325
        {
326
            var value = reader.GetValue<TimeOnly>(index);
×
327
            string formatted = value.Second == 0 && value.Millisecond == 0
×
328
                ? value.ToString("HH':'mm", CultureInfo.InvariantCulture)
×
329
                : value.ToString(CultureInfo.InvariantCulture);
×
330

331
            writer.WriteStringValue(formatted);
×
332
            return;
×
333
        }
334
#endif
335

336
        if (type == typeof(TimeSpan))
12,976✔
337
        {
338
            var value = reader.GetValue<TimeSpan>(index);
10✔
339
            string formatted = value.Seconds == 0 && value.Milliseconds == 0
10!
340
                ? value.ToString(@"hh\:mm", CultureInfo.InvariantCulture)
10✔
341
                : value.ToString();
10✔
342

343
            writer.WriteStringValue(formatted);
10✔
344
            return;
10✔
345
        }
346

347
        if (type == typeof(DateTime))
12,966✔
348
        {
349
            var value = reader.GetDateTime(index);
10✔
350
            var dataType = reader.GetDataTypeName(index).ToLowerInvariant();
10✔
351

352
            if (string.Equals(dataType, "date", StringComparison.OrdinalIgnoreCase))
10✔
353
            {
354
                var formattedDate = value.ToString("yyyy'-'MM'-'dd", CultureInfo.InvariantCulture);
5✔
355
                writer.WriteStringValue(formattedDate);
5✔
356
            }
357
            else
358
            {
359
                writer.WriteStringValue(value);
5✔
360
            }
361
            return;
5✔
362
        }
363

364
        if (type == typeof(DateTimeOffset))
12,956✔
365
        {
366
            var value = reader.GetValue(index);
6,481✔
367
            if (value is DateTimeOffset offset)
6,481!
368
            {
369
                writer.WriteStringValue(offset);
6,481✔
370
                return;
6,481✔
371
            }
372

373
            var date = reader.GetDateTime(index);
×
374
            date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
×
375

376
            offset = new DateTimeOffset(date, TimeSpan.Zero);
×
377

378
            writer.WriteStringValue(offset);
×
379
            return;
×
380
        }
381

382
        if (type == typeof(Guid))
6,475✔
383
        {
384
            var value = reader.GetGuid(index);
3,237✔
385
            writer.WriteStringValue(value);
3,237✔
386
            return;
3,237✔
387
        }
388

389
        // fallback
390
        var v = reader.GetValue(index);
3,238✔
391
        writer.WriteStringValue(v.ToString());
3,238✔
392
    }
3,238✔
393
}
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