Coverage Report

Created: 2024-07-18 11:41

src/zserio/JsonWriter.h
Line
Count
Source (jump to first uncovered line)
1
#ifndef ZSERIO_JSON_WRITER_H_INC
2
#define ZSERIO_JSON_WRITER_H_INC
3
4
#include <ostream>
5
6
#include "zserio/AllocatorHolder.h"
7
#include "zserio/IWalkObserver.h"
8
#include "zserio/JsonEncoder.h"
9
#include "zserio/OptionalHolder.h"
10
#include "zserio/TypeInfoUtil.h"
11
12
namespace zserio
13
{
14
15
/**
16
 * Walker observer which dumps zserio objects to JSON format.
17
 */
18
template <typename ALLOC = std::allocator<uint8_t>>
19
class BasicJsonWriter : public IBasicWalkObserver<ALLOC>, public AllocatorHolder<ALLOC>
20
{
21
public:
22
    using AllocatorHolder<ALLOC>::get_allocator;
23
24
    /**
25
     * Default item separator used when indent is not set.
26
     */
27
    static constexpr const char* DEFAULT_ITEM_SEPARATOR = ", ";
28
29
    /**
30
     * Default item separator used when indent is set.
31
     */
32
    static constexpr const char* DEFAULT_ITEM_SEPARATOR_WITH_INDENT = ",";
33
34
    /**
35
     * Default key separator.
36
     */
37
    static constexpr const char* DEFAULT_KEY_SEPARATOR = ": ";
38
39
    /**
40
     * Configuration for writing of enumerable types.
41
     */
42
    enum class EnumerableFormat
43
    {
44
        /** Print as JSON integral value. */
45
        NUMBER,
46
        /**
47
         * Print as JSON string according to the following rules:
48
         *
49
         * 1. Enums
50
         *   * when an exact match with an enumerable item is found, the item name is used - e.g. "FIRST",
51
         *   * when no exact match is found, it's an invalid value, the integral value is converted to string
52
         *     and an appropriate comment is included - e.g. \"10 /<span>*</span> no match <span>*</span>/\".
53
         *
54
         * 2. Bitmasks
55
         *   * when an exact mach with or-ed bitmask values is found, it's used - e.g. "READ | WRITE",
56
         *   * when no exact match is found, but some or-ed values match, the integral value is converted
57
         *     to string and the or-ed values are included in a comment - e.g.
58
         *     \"127 /<span>*</span> READ | CREATE <span>*</span>/\",
59
         *   * when no match is found at all, the integral value is converted to string and an appropriate
60
         *     comment is included - e.g. \"13 /<span>*</span> no match <span>*</span>/\".
61
         */
62
        STRING
63
    };
64
    /**
65
     * Default configuration for enumerable types.
66
     */
67
    static constexpr EnumerableFormat DEFAULT_ENUMERABLE_FORMAT = EnumerableFormat::STRING;
68
69
    /**
70
     * Constructor.
71
     *
72
     * \param out Stream to use for writing.
73
     * \param allocator Allocator to use.
74
     */
75
    explicit BasicJsonWriter(std::ostream& out, const ALLOC& allocator = ALLOC());
76
77
    /**
78
     * Constructor.
79
     *
80
     * \param out Stream to use for writing.
81
     * \param indent Indent as a number of ' ' to be used for indentation.
82
     * \param allocator Allocator to use.
83
     */
84
    BasicJsonWriter(std::ostream& out, uint8_t indent, const ALLOC& allocator = ALLOC());
85
86
    /**
87
     * Constructor.
88
     *
89
     * \param out Stream to use for writing.
90
     * \param indent Indent as a string to be used for indentation.
91
     * \param allocator Allocator to use.
92
     */
93
    BasicJsonWriter(std::ostream& out, const string<ALLOC>& indent, const ALLOC& allocator = ALLOC());
94
95
    /**
96
     * Method generated by default.
97
     */
98
68
    ~BasicJsonWriter() override = default;
99
100
    /**
101
     * Copying and moving is disallowed!
102
     * \{
103
     */
104
    BasicJsonWriter(const BasicJsonWriter& other) = delete;
105
    BasicJsonWriter& operator=(const BasicJsonWriter& other) = delete;
106
107
    BasicJsonWriter(BasicJsonWriter&& other) = delete;
108
    BasicJsonWriter& operator=(BasicJsonWriter&& other) = delete;
109
    /**
110
     * \}
111
     */
112
113
    /**
114
     * Sets custom item separator.
115
     *
116
     * Use with caution since setting of a wrong separator can lead to invalid JSON output.
117
     *
118
     * \param itemSeparator Item separator to set.
119
     */
120
    void setItemSeparator(const string<ALLOC>& itemSeparator);
121
122
    /**
123
     * Sets custom key separator.
124
     *
125
     * Use with caution since setting of a wrong separator can lead to invalid JSON output.
126
     *
127
     * \param keySeparator Key separator to set.
128
     */
129
    void setKeySeparator(const string<ALLOC>& keySeparator);
130
131
    /**
132
     * Sets preferred formatting for enumerable types.
133
     *
134
     * @param enumerableFormat Enumerable format to use.
135
     */
136
    void setEnumerableFormat(EnumerableFormat enumerableFormat);
137
138
    void beginRoot(const IBasicReflectableConstPtr<ALLOC>& compound) override;
139
    void endRoot(const IBasicReflectableConstPtr<ALLOC>& compound) override;
140
141
    void beginArray(
142
            const IBasicReflectableConstPtr<ALLOC>& array, const BasicFieldInfo<ALLOC>& fieldInfo) override;
143
    void endArray(
144
            const IBasicReflectableConstPtr<ALLOC>& array, const BasicFieldInfo<ALLOC>& fieldInfo) override;
145
146
    void beginCompound(const IBasicReflectableConstPtr<ALLOC>& compound, const BasicFieldInfo<ALLOC>& fieldInfo,
147
            size_t elementIndex) override;
148
    void endCompound(const IBasicReflectableConstPtr<ALLOC>& compound, const BasicFieldInfo<ALLOC>& fieldInfo,
149
            size_t elementIndex) override;
150
151
    void visitValue(const IBasicReflectableConstPtr<ALLOC>& value, const BasicFieldInfo<ALLOC>& fieldInfo,
152
            size_t elementIndex) override;
153
154
private:
155
    BasicJsonWriter(std::ostream& out, InplaceOptionalHolder<string<ALLOC>>&& optionalIndent,
156
            const ALLOC& allocator = ALLOC());
157
158
    void beginItem();
159
    void endItem();
160
    void beginObject();
161
    void endObject();
162
    void beginArray();
163
    void endArray();
164
165
    void writeIndent();
166
    void writeKey(StringView key);
167
    void writeValue(const IBasicReflectableConstPtr<ALLOC>& reflectable);
168
    void writeBitBuffer(const BasicBitBuffer<ALLOC>& bitBuffer);
169
    void writeBytes(Span<const uint8_t> value);
170
    void writeStringifiedEnum(const IBasicReflectableConstPtr<ALLOC>& reflectable);
171
    void writeStringifiedBitmask(const IBasicReflectableConstPtr<ALLOC>& reflectable);
172
173
    std::ostream& m_out;
174
    InplaceOptionalHolder<string<ALLOC>> m_indent;
175
    string<ALLOC> m_itemSeparator;
176
    string<ALLOC> m_keySeparator;
177
    EnumerableFormat m_enumerableFormat = DEFAULT_ENUMERABLE_FORMAT;
178
179
    bool m_isFirst = true;
180
    size_t m_level = 0;
181
};
182
183
/** Typedef to JsonWriter provided for convenience - using default std::allocator<uint8_t>. */
184
/** \{ */
185
using JsonWriter = BasicJsonWriter<>;
186
/** \} */
187
188
template <typename ALLOC>
189
BasicJsonWriter<ALLOC>::BasicJsonWriter(std::ostream& out, const ALLOC& allocator) :
190
        BasicJsonWriter(out, NullOpt, allocator)
191
28
{}
192
193
template <typename ALLOC>
194
BasicJsonWriter<ALLOC>::BasicJsonWriter(std::ostream& out, uint8_t indent, const ALLOC& allocator) :
195
        BasicJsonWriter(out, string<ALLOC>(indent, ' ', allocator), allocator)
196
38
{}
197
198
template <typename ALLOC>
199
BasicJsonWriter<ALLOC>::BasicJsonWriter(
200
        std::ostream& out, const string<ALLOC>& indent, const ALLOC& allocator) :
201
        BasicJsonWriter(out, InplaceOptionalHolder<string<ALLOC>>(indent), allocator)
202
40
{}
203
204
template <typename ALLOC>
205
BasicJsonWriter<ALLOC>::BasicJsonWriter(
206
        std::ostream& out, InplaceOptionalHolder<string<ALLOC>>&& optionalIndent, const ALLOC& allocator) :
207
        AllocatorHolder<ALLOC>(allocator),
208
        m_out(out),
209
        m_indent(optionalIndent),
210
        m_itemSeparator(
211
                m_indent.hasValue() ? DEFAULT_ITEM_SEPARATOR_WITH_INDENT : DEFAULT_ITEM_SEPARATOR, allocator),
212
        m_keySeparator(DEFAULT_KEY_SEPARATOR, allocator)
213
68
{}
214
215
template <typename ALLOC>
216
void BasicJsonWriter<ALLOC>::setItemSeparator(const string<ALLOC>& itemSeparator)
217
1
{
218
1
    m_itemSeparator = itemSeparator;
219
1
}
220
221
template <typename ALLOC>
222
void BasicJsonWriter<ALLOC>::setKeySeparator(const string<ALLOC>& keySeparator)
223
1
{
224
1
    m_keySeparator = keySeparator;
225
1
}
226
227
template <typename ALLOC>
228
void BasicJsonWriter<ALLOC>::setEnumerableFormat(EnumerableFormat enumerableFormat)
229
3
{
230
3
    m_enumerableFormat = enumerableFormat;
231
3
}
232
233
template <typename ALLOC>
234
void BasicJsonWriter<ALLOC>::beginRoot(const IBasicReflectableConstPtr<ALLOC>&)
235
45
{
236
45
    beginObject();
237
45
}
238
239
template <typename ALLOC>
240
void BasicJsonWriter<ALLOC>::endRoot(const IBasicReflectableConstPtr<ALLOC>&)
241
45
{
242
45
    endObject();
243
45
    m_out.flush();
244
45
}
245
246
template <typename ALLOC>
247
void BasicJsonWriter<ALLOC>::beginArray(
248
        const IBasicReflectableConstPtr<ALLOC>&, const BasicFieldInfo<ALLOC>& fieldInfo)
249
3
{
250
3
    beginItem();
251
252
3
    writeKey(fieldInfo.schemaName);
253
254
3
    beginArray();
255
3
}
256
257
template <typename ALLOC>
258
void BasicJsonWriter<ALLOC>::endArray(const IBasicReflectableConstPtr<ALLOC>&, const BasicFieldInfo<ALLOC>&)
259
3
{
260
3
    endArray();
261
262
3
    endItem();
263
3
}
264
265
template <typename ALLOC>
266
void BasicJsonWriter<ALLOC>::beginCompound(
267
        const IBasicReflectableConstPtr<ALLOC>&, const BasicFieldInfo<ALLOC>& fieldInfo, size_t elementIndex)
268
6
{
269
6
    beginItem();
270
271
6
    if (elementIndex == WALKER_NOT_ELEMENT)
272
5
    {
273
5
        writeKey(fieldInfo.schemaName);
274
5
    }
275
276
6
    beginObject();
277
6
}
278
279
template <typename ALLOC>
280
void BasicJsonWriter<ALLOC>::endCompound(
281
        const IBasicReflectableConstPtr<ALLOC>&, const BasicFieldInfo<ALLOC>&, size_t)
282
6
{
283
6
    endObject();
284
285
6
    endItem();
286
6
}
287
288
template <typename ALLOC>
289
void BasicJsonWriter<ALLOC>::visitValue(const IBasicReflectableConstPtr<ALLOC>& value,
290
        const BasicFieldInfo<ALLOC>& fieldInfo, size_t elementIndex)
291
85
{
292
85
    beginItem();
293
294
85
    if (elementIndex == WALKER_NOT_ELEMENT)
295
81
    {
296
81
        writeKey(fieldInfo.schemaName);
297
81
    }
298
299
85
    writeValue(value);
300
301
85
    endItem();
302
85
}
303
304
template <typename ALLOC>
305
void BasicJsonWriter<ALLOC>::beginItem()
306
108
{
307
108
    if (!m_isFirst)
308
29
    {
309
29
        m_out.write(m_itemSeparator.data(), static_cast<std::streamsize>(m_itemSeparator.size()));
310
29
    }
311
312
108
    if (m_indent.hasValue())
313
43
    {
314
43
        m_out.put('\n');
315
43
    }
316
317
108
    writeIndent();
318
108
}
319
320
template <typename ALLOC>
321
void BasicJsonWriter<ALLOC>::endItem()
322
107
{
323
107
    m_isFirst = false;
324
107
}
325
326
template <typename ALLOC>
327
void BasicJsonWriter<ALLOC>::beginObject()
328
55
{
329
55
    m_out.put('{');
330
331
55
    m_isFirst = true;
332
55
    m_level += 1;
333
55
}
334
335
template <typename ALLOC>
336
void BasicJsonWriter<ALLOC>::endObject()
337
55
{
338
55
    if (m_indent.hasValue())
339
43
    {
340
43
        m_out.put('\n');
341
43
    }
342
343
55
    m_level -= 1;
344
345
55
    writeIndent();
346
347
55
    m_out.put('}');
348
55
}
349
350
template <typename ALLOC>
351
void BasicJsonWriter<ALLOC>::beginArray()
352
7
{
353
7
    m_out.put('[');
354
355
7
    m_isFirst = true;
356
7
    m_level += 1;
357
7
}
358
359
template <typename ALLOC>
360
void BasicJsonWriter<ALLOC>::endArray()
361
7
{
362
7
    if (m_indent.hasValue())
363
1
    {
364
1
        m_out.put('\n');
365
1
    }
366
367
7
    m_level -= 1;
368
369
7
    writeIndent();
370
371
7
    m_out.put(']');
372
7
}
373
374
template <typename ALLOC>
375
void BasicJsonWriter<ALLOC>::writeIndent()
376
170
{
377
170
    if (m_indent.hasValue())
378
87
    {
379
87
        const auto& indent = m_indent.value();
380
87
        if (!indent.empty())
381
82
        {
382
129
            for (size_t i = 0; i < m_level; 
++i47
)
383
47
            {
384
47
                m_out.write(indent.data(), static_cast<std::streamsize>(indent.size()));
385
47
            }
386
82
        }
387
87
    }
388
170
}
389
390
template <typename ALLOC>
391
void BasicJsonWriter<ALLOC>::writeKey(StringView key)
392
95
{
393
95
    JsonEncoder::encodeString(m_out, key);
394
95
    m_out.write(m_keySeparator.data(), static_cast<std::streamsize>(m_keySeparator.size()));
395
95
    m_out.flush();
396
95
}
397
398
template <typename ALLOC>
399
void BasicJsonWriter<ALLOC>::writeValue(const IBasicReflectableConstPtr<ALLOC>& reflectable)
400
85
{
401
85
    if (!reflectable)
402
1
    {
403
1
        JsonEncoder::encodeNull(m_out);
404
1
        return;
405
1
    }
406
407
84
    const IBasicTypeInfo<ALLOC>& typeInfo = reflectable->getTypeInfo();
408
84
    switch (typeInfo.getCppType())
409
84
    {
410
1
    case CppType::BOOL:
411
1
        JsonEncoder::encodeBool(m_out, reflectable->getBool());
412
1
        break;
413
1
    case CppType::INT8:
414
2
    case CppType::INT16:
415
3
    case CppType::INT32:
416
4
    case CppType::INT64:
417
4
        JsonEncoder::encodeIntegral(m_out, reflectable->toInt());
418
4
        break;
419
1
    case CppType::UINT8:
420
2
    case CppType::UINT16:
421
13
    case CppType::UINT32:
422
14
    case CppType::UINT64:
423
14
        JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
424
14
        break;
425
1
    case CppType::FLOAT:
426
1
        JsonEncoder::encodeFloatingPoint(m_out, static_cast<double>(reflectable->getFloat()));
427
1
        break;
428
1
    case CppType::DOUBLE:
429
1
        JsonEncoder::encodeFloatingPoint(m_out, reflectable->getDouble());
430
1
        break;
431
2
    case CppType::BYTES:
432
2
        writeBytes(reflectable->getBytes());
433
2
        break;
434
39
    case CppType::STRING:
435
39
        JsonEncoder::encodeString(m_out, reflectable->getStringView());
436
39
        break;
437
2
    case CppType::BIT_BUFFER:
438
2
        writeBitBuffer(reflectable->getBitBuffer());
439
2
        break;
440
12
    case CppType::ENUM:
441
12
        if (m_enumerableFormat == EnumerableFormat::STRING)
442
7
        {
443
7
            writeStringifiedEnum(reflectable);
444
7
        }
445
5
        else if (TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType()))
446
3
        {
447
3
            JsonEncoder::encodeIntegral(m_out, reflectable->toInt());
448
3
        }
449
2
        else
450
2
        {
451
2
            JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
452
2
        }
453
12
        break;
454
7
    case CppType::BITMASK:
455
7
        if (m_enumerableFormat == EnumerableFormat::STRING)
456
5
        {
457
5
            writeStringifiedBitmask(reflectable);
458
5
        }
459
2
        else
460
2
        {
461
2
            JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
462
2
        }
463
7
        break;
464
1
    default:
465
1
        throw CppRuntimeException("JsonWriter: Unexpected not-null value of type '")
466
1
                << typeInfo.getSchemaName() << "'!";
467
84
    }
468
469
83
    m_out.flush();
470
83
}
471
472
template <typename ALLOC>
473
void BasicJsonWriter<ALLOC>::writeBitBuffer(const BasicBitBuffer<ALLOC>& bitBuffer)
474
2
{
475
2
    beginObject();
476
2
    beginItem();
477
2
    writeKey("buffer"_sv);
478
2
    beginArray();
479
2
    Span<const uint8_t> buffer = bitBuffer.getData();
480
2
    for (uint8_t element : buffer)
481
4
    {
482
4
        beginItem();
483
4
        JsonEncoder::encodeIntegral(m_out, element);
484
4
        endItem();
485
4
    }
486
2
    endArray();
487
2
    endItem();
488
2
    beginItem();
489
2
    writeKey("bitSize"_sv);
490
2
    JsonEncoder::encodeIntegral(m_out, bitBuffer.getBitSize());
491
2
    endItem();
492
2
    endObject();
493
2
}
494
495
template <typename ALLOC>
496
void BasicJsonWriter<ALLOC>::writeBytes(Span<const uint8_t> value)
497
2
{
498
2
    beginObject();
499
2
    beginItem();
500
2
    writeKey("buffer"_sv);
501
2
    beginArray();
502
2
    for (uint8_t byte : value)
503
4
    {
504
4
        beginItem();
505
4
        JsonEncoder::encodeIntegral(m_out, byte);
506
4
        endItem();
507
4
    }
508
2
    endArray();
509
2
    endItem();
510
2
    endObject();
511
2
}
512
513
template <typename ALLOC>
514
void BasicJsonWriter<ALLOC>::writeStringifiedEnum(const IBasicReflectableConstPtr<ALLOC>& reflectable)
515
7
{
516
7
    const auto& typeInfo = reflectable->getTypeInfo();
517
7
    const uint64_t enumValue = TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType())
518
7
            ? 
static_cast<uint64_t>(reflectable->toInt())4
519
7
            : 
reflectable->toUInt()3
;
520
7
    for (const auto& itemInfo : typeInfo.getEnumItems())
521
14
    {
522
14
        if (itemInfo.value == enumValue)
523
5
        {
524
            // exact match
525
5
            JsonEncoder::encodeString(m_out, itemInfo.schemaName);
526
5
            return;
527
5
        }
528
14
    }
529
530
    // no match
531
2
    string<ALLOC> stringValue = TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType())
532
2
            ? 
toString(reflectable->toInt(), get_allocator())1
533
2
            : 
toString(reflectable->toUInt(), get_allocator())1
;
534
2
    stringValue.append(" /* no match */");
535
2
    JsonEncoder::encodeString(m_out, stringValue);
536
2
}
537
538
template <typename ALLOC>
539
void BasicJsonWriter<ALLOC>::writeStringifiedBitmask(const IBasicReflectableConstPtr<ALLOC>& reflectable)
540
5
{
541
5
    string<ALLOC> stringValue(get_allocator());
542
5
    const auto& typeInfo = reflectable->getTypeInfo();
543
5
    const uint64_t bitmaskValue = reflectable->toUInt();
544
5
    uint64_t valueCheck = 0;
545
5
    for (const auto& itemInfo : typeInfo.getBitmaskValues())
546
15
    {
547
15
        if ((itemInfo.value != 0 && 
(bitmaskValue & itemInfo.value) == itemInfo.value10
) ||
548
15
                
(10
itemInfo.value == 010
&&
bitmaskValue == 05
))
549
6
        {
550
6
            valueCheck |= itemInfo.value;
551
6
            if (!stringValue.empty())
552
2
            {
553
2
                stringValue += " | ";
554
2
            }
555
6
            stringValue += toString(itemInfo.schemaName, get_allocator());
556
6
        }
557
15
    }
558
559
5
    if (stringValue.empty())
560
1
    {
561
        // no match
562
1
        stringValue.append(toString(bitmaskValue, get_allocator()));
563
1
        stringValue.append(" /* no match */");
564
1
    }
565
4
    else if (bitmaskValue != valueCheck)
566
1
    {
567
        // partial match
568
1
        stringValue =
569
1
                toString(bitmaskValue, get_allocator())
570
1
                        .append(" /* partial match: ")
571
1
                        .append(stringValue)
572
1
                        .append(" */");
573
1
    }
574
    // else exact match
575
576
5
    JsonEncoder::encodeString(m_out, stringValue);
577
5
}
578
579
} // namespace zserio
580
581
#endif // ZSERIO_JSON_WRITER_H_INC