Coverage Report

Created: 2024-04-30 09:35

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
        writeKey(fieldInfo.schemaName);
273
274
6
    beginObject();
275
6
}
276
277
template <typename ALLOC>
278
void BasicJsonWriter<ALLOC>::endCompound(
279
        const IBasicReflectableConstPtr<ALLOC>&, const BasicFieldInfo<ALLOC>&, size_t)
280
6
{
281
6
    endObject();
282
283
6
    endItem();
284
6
}
285
286
template <typename ALLOC>
287
void BasicJsonWriter<ALLOC>::visitValue(const IBasicReflectableConstPtr<ALLOC>& value,
288
        const BasicFieldInfo<ALLOC>& fieldInfo, size_t elementIndex)
289
85
{
290
85
    beginItem();
291
292
85
    if (elementIndex == WALKER_NOT_ELEMENT)
293
81
        writeKey(fieldInfo.schemaName);
294
295
85
    writeValue(value);
296
297
85
    endItem();
298
85
}
299
300
template <typename ALLOC>
301
void BasicJsonWriter<ALLOC>::beginItem()
302
108
{
303
108
    if (!m_isFirst)
304
29
        m_out.write(m_itemSeparator.data(), static_cast<std::streamsize>(m_itemSeparator.size()));
305
306
108
    if (m_indent.hasValue())
307
43
        m_out.put('\n');
308
309
108
    writeIndent();
310
108
}
311
312
template <typename ALLOC>
313
void BasicJsonWriter<ALLOC>::endItem()
314
107
{
315
107
    m_isFirst = false;
316
107
}
317
318
template <typename ALLOC>
319
void BasicJsonWriter<ALLOC>::beginObject()
320
55
{
321
55
    m_out.put('{');
322
323
55
    m_isFirst = true;
324
55
    m_level += 1;
325
55
}
326
327
template <typename ALLOC>
328
void BasicJsonWriter<ALLOC>::endObject()
329
55
{
330
55
    if (m_indent.hasValue())
331
43
        m_out.put('\n');
332
333
55
    m_level -= 1;
334
335
55
    writeIndent();
336
337
55
    m_out.put('}');
338
55
}
339
340
template <typename ALLOC>
341
void BasicJsonWriter<ALLOC>::beginArray()
342
7
{
343
7
    m_out.put('[');
344
345
7
    m_isFirst = true;
346
7
    m_level += 1;
347
7
}
348
349
template <typename ALLOC>
350
void BasicJsonWriter<ALLOC>::endArray()
351
7
{
352
7
    if (m_indent.hasValue())
353
1
        m_out.put('\n');
354
355
7
    m_level -= 1;
356
357
7
    writeIndent();
358
359
7
    m_out.put(']');
360
7
}
361
362
template <typename ALLOC>
363
void BasicJsonWriter<ALLOC>::writeIndent()
364
170
{
365
170
    if (m_indent.hasValue())
366
87
    {
367
87
        const auto& indent = m_indent.value();
368
87
        if (!indent.empty())
369
82
        {
370
129
            for (size_t i = 0; i < m_level; 
++i47
)
371
47
                m_out.write(indent.data(), static_cast<std::streamsize>(indent.size()));
372
82
        }
373
87
    }
374
170
}
375
376
template <typename ALLOC>
377
void BasicJsonWriter<ALLOC>::writeKey(StringView key)
378
95
{
379
95
    JsonEncoder::encodeString(m_out, key);
380
95
    m_out.write(m_keySeparator.data(), static_cast<std::streamsize>(m_keySeparator.size()));
381
95
    m_out.flush();
382
95
}
383
384
template <typename ALLOC>
385
void BasicJsonWriter<ALLOC>::writeValue(const IBasicReflectableConstPtr<ALLOC>& reflectable)
386
85
{
387
85
    if (!reflectable)
388
1
    {
389
1
        JsonEncoder::encodeNull(m_out);
390
1
        return;
391
1
    }
392
393
84
    const IBasicTypeInfo<ALLOC>& typeInfo = reflectable->getTypeInfo();
394
84
    switch (typeInfo.getCppType())
395
84
    {
396
1
    case CppType::BOOL:
397
1
        JsonEncoder::encodeBool(m_out, reflectable->getBool());
398
1
        break;
399
1
    case CppType::INT8:
400
2
    case CppType::INT16:
401
3
    case CppType::INT32:
402
4
    case CppType::INT64:
403
4
        JsonEncoder::encodeIntegral(m_out, reflectable->toInt());
404
4
        break;
405
1
    case CppType::UINT8:
406
2
    case CppType::UINT16:
407
13
    case CppType::UINT32:
408
14
    case CppType::UINT64:
409
14
        JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
410
14
        break;
411
1
    case CppType::FLOAT:
412
1
        JsonEncoder::encodeFloatingPoint(m_out, static_cast<double>(reflectable->getFloat()));
413
1
        break;
414
1
    case CppType::DOUBLE:
415
1
        JsonEncoder::encodeFloatingPoint(m_out, reflectable->getDouble());
416
1
        break;
417
2
    case CppType::BYTES:
418
2
        writeBytes(reflectable->getBytes());
419
2
        break;
420
39
    case CppType::STRING:
421
39
        JsonEncoder::encodeString(m_out, reflectable->getStringView());
422
39
        break;
423
2
    case CppType::BIT_BUFFER:
424
2
        writeBitBuffer(reflectable->getBitBuffer());
425
2
        break;
426
12
    case CppType::ENUM:
427
12
        if (m_enumerableFormat == EnumerableFormat::STRING)
428
7
            writeStringifiedEnum(reflectable);
429
5
        else if (TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType()))
430
3
            JsonEncoder::encodeIntegral(m_out, reflectable->toInt());
431
2
        else
432
2
            JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
433
12
        break;
434
7
    case CppType::BITMASK:
435
7
        if (m_enumerableFormat == EnumerableFormat::STRING)
436
5
            writeStringifiedBitmask(reflectable);
437
2
        else
438
2
            JsonEncoder::encodeIntegral(m_out, reflectable->toUInt());
439
7
        break;
440
1
    default:
441
1
        throw CppRuntimeException("JsonWriter: Unexpected not-null value of type '")
442
1
                << typeInfo.getSchemaName() << "'!";
443
84
    }
444
445
83
    m_out.flush();
446
83
}
447
448
template <typename ALLOC>
449
void BasicJsonWriter<ALLOC>::writeBitBuffer(const BasicBitBuffer<ALLOC>& bitBuffer)
450
2
{
451
2
    beginObject();
452
2
    beginItem();
453
2
    writeKey("buffer"_sv);
454
2
    beginArray();
455
2
    Span<const uint8_t> buffer = bitBuffer.getData();
456
2
    for (uint8_t element : buffer)
457
4
    {
458
4
        beginItem();
459
4
        JsonEncoder::encodeIntegral(m_out, element);
460
4
        endItem();
461
4
    }
462
2
    endArray();
463
2
    endItem();
464
2
    beginItem();
465
2
    writeKey("bitSize"_sv);
466
2
    JsonEncoder::encodeIntegral(m_out, bitBuffer.getBitSize());
467
2
    endItem();
468
2
    endObject();
469
2
}
470
471
template <typename ALLOC>
472
void BasicJsonWriter<ALLOC>::writeBytes(Span<const uint8_t> value)
473
2
{
474
2
    beginObject();
475
2
    beginItem();
476
2
    writeKey("buffer"_sv);
477
2
    beginArray();
478
2
    for (uint8_t byte : value)
479
4
    {
480
4
        beginItem();
481
4
        JsonEncoder::encodeIntegral(m_out, byte);
482
4
        endItem();
483
4
    }
484
2
    endArray();
485
2
    endItem();
486
2
    endObject();
487
2
}
488
489
template <typename ALLOC>
490
void BasicJsonWriter<ALLOC>::writeStringifiedEnum(const IBasicReflectableConstPtr<ALLOC>& reflectable)
491
7
{
492
7
    const auto& typeInfo = reflectable->getTypeInfo();
493
7
    const uint64_t enumValue = TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType())
494
7
            ? 
static_cast<uint64_t>(reflectable->toInt())4
495
7
            : 
reflectable->toUInt()3
;
496
7
    for (const auto& itemInfo : typeInfo.getEnumItems())
497
14
    {
498
14
        if (itemInfo.value == enumValue)
499
5
        {
500
            // exact match
501
5
            JsonEncoder::encodeString(m_out, itemInfo.schemaName);
502
5
            return;
503
5
        }
504
14
    }
505
506
    // no match
507
2
    string<ALLOC> stringValue = TypeInfoUtil::isSigned(typeInfo.getUnderlyingType().getCppType())
508
2
            ? 
toString(reflectable->toInt(), get_allocator())1
509
2
            : 
toString(reflectable->toUInt(), get_allocator())1
;
510
2
    stringValue.append(" /* no match */");
511
2
    JsonEncoder::encodeString(m_out, stringValue);
512
2
}
513
514
template <typename ALLOC>
515
void BasicJsonWriter<ALLOC>::writeStringifiedBitmask(const IBasicReflectableConstPtr<ALLOC>& reflectable)
516
5
{
517
5
    string<ALLOC> stringValue(get_allocator());
518
5
    const auto& typeInfo = reflectable->getTypeInfo();
519
5
    const uint64_t bitmaskValue = reflectable->toUInt();
520
5
    uint64_t valueCheck = 0;
521
5
    for (const auto& itemInfo : typeInfo.getBitmaskValues())
522
15
    {
523
15
        if ((itemInfo.value != 0 && 
(bitmaskValue & itemInfo.value) == itemInfo.value10
) ||
524
15
                
(10
itemInfo.value == 010
&&
bitmaskValue == 05
))
525
6
        {
526
6
            valueCheck |= itemInfo.value;
527
6
            if (!stringValue.empty())
528
2
                stringValue += " | ";
529
6
            stringValue += toString(itemInfo.schemaName, get_allocator());
530
6
        }
531
15
    }
532
533
5
    if (stringValue.empty())
534
1
    {
535
        // no match
536
1
        stringValue.append(toString(bitmaskValue, get_allocator()));
537
1
        stringValue.append(" /* no match */");
538
1
    }
539
4
    else if (bitmaskValue != valueCheck)
540
1
    {
541
        // partial match
542
1
        stringValue =
543
1
                toString(bitmaskValue, get_allocator())
544
1
                        .append(" /* partial match: ")
545
1
                        .append(stringValue)
546
1
                        .append(" */");
547
1
    }
548
    // else exact match
549
550
5
    JsonEncoder::encodeString(m_out, stringValue);
551
5
}
552
553
} // namespace zserio
554
555
#endif // ZSERIO_JSON_WRITER_H_INC