Coverage Report

Created: 2023-12-13 14:58

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