aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/protobuf/json/inline.h
blob: e2d7bb6ef04bd0b27c4bae62d41f7561450c4999 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#pragma once

// A printer from protobuf to json string, with ability to inline some string fields of given protobuf message
// into output as ready json without additional escaping. These fields should be marked using special field option.
// An example of usage:
// 1) Define a field option in your .proto to identify fields which should be inlined, e.g.
//
//     import "google/protobuf/descriptor.proto";
//     extend google.protobuf.FieldOptions {
//         optional bool this_is_json = 58253;   // do not forget assign some more or less unique tag
//     }
//
// 2) Mark some fields of your protobuf message with this option, e.g.:
//
//     message TMyObject {
//         optional string A = 1 [(this_is_json) = true];
//     }
//
// 3) In the C++ code you prepare somehow an object of TMyObject type
//
//     TMyObject o;
//     o.Set("{\"inner\":\"value\"}");
//
// 4) And then serialize it to json string with inlining, e.g.:
//
//     Cout << NProtobufJson::PrintInlined(o, MakeFieldOptionFunctor(this_is_json)) << Endl;
//
// 5) Alternatively you can specify a some more abstract functor for defining raw json fields
//
// which will print following json to stdout:
//     {"A":{"inner":"value"}}
// instead of
//     {"A":"{\"inner\":\"value\"}"}
// which would be printed with normal Proto2Json printer.
//
// See ut/inline_ut.cpp for additional examples of usage.

#include "config.h"
#include "proto2json_printer.h"
#include "json_output_create.h"

#include <library/cpp/protobuf/util/simple_reflection.h>

#include <util/generic/maybe.h>
#include <util/generic/yexception.h>
#include <util/generic/utility.h>

#include <functional>

namespace NProtobufJson {
    template <typename TBasePrinter = TProto2JsonPrinter> // TBasePrinter is assumed to be a TProto2JsonPrinter descendant
    class TInliningPrinter: public TBasePrinter {
    public:
        using TFieldPredicate = std::function<bool(const NProtoBuf::Message&,
                                                   const NProtoBuf::FieldDescriptor*)>;

        template <typename... TArgs>
        TInliningPrinter(TFieldPredicate isInlined, TArgs&&... args)
            : TBasePrinter(std::forward<TArgs>(args)...)
            , IsInlined(std::move(isInlined))
        {
        }

        virtual void PrintField(const NProtoBuf::Message& proto,
                                const NProtoBuf::FieldDescriptor& field,
                                IJsonOutput& json,
                                TStringBuf key) override {
            const NProtoBuf::TConstField f(proto, &field);
            if (!key && IsInlined(proto, &field) && ShouldPrint(f)) {
                key = this->MakeKey(field);
                json.WriteKey(key);
                if (!field.is_repeated()) {
                    json.WriteRawJson(f.Get<TString>());
                } else {
                    json.BeginList();
                    for (size_t i = 0, sz = f.Size(); i < sz; ++i)
                        json.WriteRawJson(f.Get<TString>(i));
                    json.EndList();
                }

            } else {
                TBasePrinter::PrintField(proto, field, json, key);
            }
        }

    private:
        bool ShouldPrint(const NProtoBuf::TConstField& f) const {
            if (!f.IsString())
                ythrow yexception() << "TInliningPrinter: json field "
                                    << f.Field()->name() << " should be a string";

            if (f.HasValue())
                return true;

            // we may want write default value for given field in case of its absence
            const auto& cfg = this->GetConfig();
            return (f.Field()->is_repeated() ? cfg.MissingRepeatedKeyMode : cfg.MissingSingleKeyMode) == TProto2JsonConfig::MissingKeyDefault;
        }

    private:
        TFieldPredicate IsInlined;
    };

    inline void PrintInlined(const NProtoBuf::Message& msg, TInliningPrinter<>::TFieldPredicate isInlined, IJsonOutput& output, const TProto2JsonConfig& config = TProto2JsonConfig()) {
        TInliningPrinter<> printer(std::move(isInlined), config);
        printer.Print(msg, output);
    }

    inline TString PrintInlined(const NProtoBuf::Message& msg, TInliningPrinter<>::TFieldPredicate isInlined, const TProto2JsonConfig& config = TProto2JsonConfig()) {
        TString ret;
        PrintInlined(msg, std::move(isInlined), *CreateJsonMapOutput(ret, config), config);
        return ret;
    }

}