1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31#ifndef GOOGLE_PROTOBUF_UTIL_INTERNAL_JSON_OBJECTWRITER_H__
32#define GOOGLE_PROTOBUF_UTIL_INTERNAL_JSON_OBJECTWRITER_H__
33
34#include <cstdint>
35#include <memory>
36#include <string>
37
38#include <google/protobuf/io/coded_stream.h>
39#include <google/protobuf/stubs/bytestream.h>
40#include <google/protobuf/util/internal/structured_objectwriter.h>
41
42// clang-format off
43#include <google/protobuf/port_def.inc>
44// clang-format on
45
46namespace google {
47namespace protobuf {
48namespace util {
49namespace converter {
50
51
52// An ObjectWriter implementation that outputs JSON. This ObjectWriter
53// supports writing a compact form or a pretty printed form.
54//
55// Sample usage:
56// string output;
57// StringOutputStream* str_stream = new StringOutputStream(&output);
58// CodedOutputStream* out_stream = new CodedOutputStream(str_stream);
59// JsonObjectWriter* ow = new JsonObjectWriter(" ", out_stream);
60// ow->StartObject("")
61// ->RenderString("name", "value")
62// ->RenderString("emptystring", string())
63// ->StartObject("nested")
64// ->RenderInt64("light", 299792458);
65// ->RenderDouble("pi", 3.141592653589793);
66// ->EndObject()
67// ->StartList("empty")
68// ->EndList()
69// ->EndObject();
70//
71// And then the output string would become:
72// {
73// "name": "value",
74// "emptystring": "",
75// "nested": {
76// "light": "299792458",
77// "pi": 3.141592653589793
78// },
79// "empty": []
80// }
81//
82// JsonObjectWriter does not validate if calls actually result in valid JSON.
83// For example, passing an empty name when one would be required won't result
84// in an error, just an invalid output.
85//
86// Note that all int64 and uint64 are rendered as strings instead of numbers.
87// This is because JavaScript parses numbers as 64-bit float thus int64 and
88// uint64 would lose precision if rendered as numbers.
89//
90// JsonObjectWriter is thread-unsafe.
91class PROTOBUF_EXPORT JsonObjectWriter : public StructuredObjectWriter {
92 public:
93 JsonObjectWriter(StringPiece indent_string, io::CodedOutputStream* out)
94 : element_(new Element(/*parent=*/nullptr, /*is_json_object=*/false)),
95 stream_(out),
96 sink_(out),
97 indent_string_(indent_string),
98 indent_char_('\0'),
99 indent_count_(0),
100 use_websafe_base64_for_bytes_(false) {
101 // See if we have a trivial sequence of indent characters.
102 if (!indent_string.empty()) {
103 indent_char_ = indent_string[0];
104 indent_count_ = indent_string.length();
105 for (int i = 1; i < indent_string.length(); i++) {
106 if (indent_char_ != indent_string_[i]) {
107 indent_char_ = '\0';
108 indent_count_ = 0;
109 break;
110 }
111 }
112 }
113 }
114 ~JsonObjectWriter() override;
115
116 // ObjectWriter methods.
117 JsonObjectWriter* StartObject(StringPiece name) override;
118 JsonObjectWriter* EndObject() override;
119 JsonObjectWriter* StartList(StringPiece name) override;
120 JsonObjectWriter* EndList() override;
121 JsonObjectWriter* RenderBool(StringPiece name, bool value) override;
122 JsonObjectWriter* RenderInt32(StringPiece name, int32_t value) override;
123 JsonObjectWriter* RenderUint32(StringPiece name,
124 uint32_t value) override;
125 JsonObjectWriter* RenderInt64(StringPiece name, int64_t value) override;
126 JsonObjectWriter* RenderUint64(StringPiece name,
127 uint64_t value) override;
128 JsonObjectWriter* RenderDouble(StringPiece name, double value) override;
129 JsonObjectWriter* RenderFloat(StringPiece name, float value) override;
130 JsonObjectWriter* RenderString(StringPiece name,
131 StringPiece value) override;
132 JsonObjectWriter* RenderBytes(StringPiece name, StringPiece value) override;
133 JsonObjectWriter* RenderNull(StringPiece name) override;
134 virtual JsonObjectWriter* RenderNullAsEmpty(StringPiece name);
135
136 void set_use_websafe_base64_for_bytes(bool value) {
137 use_websafe_base64_for_bytes_ = value;
138 }
139
140 protected:
141 class PROTOBUF_EXPORT Element : public BaseElement {
142 public:
143 Element(Element* parent, bool is_json_object)
144 : BaseElement(parent),
145 is_first_(true),
146 is_json_object_(is_json_object) {}
147
148 // Called before each field of the Element is to be processed.
149 // Returns true if this is the first call (processing the first field).
150 bool is_first() {
151 if (is_first_) {
152 is_first_ = false;
153 return true;
154 }
155 return false;
156 }
157
158 // Whether we are currently rendering inside a JSON object (i.e., between
159 // StartObject() and EndObject()).
160 bool is_json_object() const { return is_json_object_; }
161
162 private:
163 bool is_first_;
164 bool is_json_object_;
165
166 GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(Element);
167 };
168
169 Element* element() override { return element_.get(); }
170
171 private:
172 class PROTOBUF_EXPORT ByteSinkWrapper : public strings::ByteSink {
173 public:
174 explicit ByteSinkWrapper(io::CodedOutputStream* stream) : stream_(stream) {}
175 ~ByteSinkWrapper() override {}
176
177 // ByteSink methods.
178 void Append(const char* bytes, size_t n) override {
179 stream_->WriteRaw(buffer: bytes, size: n);
180 }
181
182 private:
183 io::CodedOutputStream* stream_;
184
185 GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ByteSinkWrapper);
186 };
187
188 // Renders a simple value as a string. By default all non-string Render
189 // methods convert their argument to a string and call this method. This
190 // method can then be used to render the simple value without escaping it.
191 JsonObjectWriter* RenderSimple(StringPiece name,
192 StringPiece value) {
193 WritePrefix(name);
194 WriteRawString(s: value);
195 return this;
196 }
197
198 // Pushes a new JSON array element to the stack.
199 void PushArray() {
200 element_.reset(p: new Element(element_.release(), /*is_json_object=*/false));
201 }
202
203 // Pushes a new JSON object element to the stack.
204 void PushObject() {
205 element_.reset(p: new Element(element_.release(), /*is_json_object=*/true));
206 }
207
208 // Pops an element off of the stack and deletes the popped element.
209 void Pop() {
210 bool needs_newline = !element_->is_first();
211 element_.reset(p: element_->pop<Element>());
212 if (needs_newline) NewLine();
213 }
214
215 // If pretty printing is enabled, this will write a newline to the output,
216 // followed by optional indentation. Otherwise this method is a noop.
217 void NewLine() {
218 if (!indent_string_.empty()) {
219 size_t len = sizeof('\n') + (indent_string_.size() * element()->level());
220
221 // Take the slow-path if we don't have sufficient characters remaining in
222 // our buffer or we have a non-trivial indent string which would prevent
223 // us from using memset.
224 uint8_t* out = nullptr;
225 if (indent_count_ > 0) {
226 out = stream_->GetDirectBufferForNBytesAndAdvance(size: len);
227 }
228
229 if (out != nullptr) {
230 out[0] = '\n';
231 memset(s: &out[1], c: indent_char_, n: len - 1);
232 } else {
233 // Slow path, no contiguous output buffer available.
234 WriteChar(c: '\n');
235 for (int i = 0; i < element()->level(); i++) {
236 stream_->WriteRaw(buffer: indent_string_.c_str(), size: indent_string_.length());
237 }
238 }
239 }
240 }
241
242 // Writes a prefix. This will write out any pretty printing and
243 // commas that are required, followed by the name and a ':' if
244 // the name is not null.
245 void WritePrefix(StringPiece name);
246
247 // Writes an individual character to the output.
248 void WriteChar(const char c) { stream_->WriteRaw(buffer: &c, size: sizeof(c)); }
249
250 // Writes a string to the output.
251 void WriteRawString(StringPiece s) {
252 stream_->WriteRaw(buffer: s.data(), size: s.length());
253 }
254
255 std::unique_ptr<Element> element_;
256 io::CodedOutputStream* stream_;
257 ByteSinkWrapper sink_;
258 const std::string indent_string_;
259
260 // For the common case of indent being a single character repeated.
261 char indent_char_;
262 int indent_count_;
263
264 // Whether to use regular or websafe base64 encoding for byte fields. Defaults
265 // to regular base64 encoding.
266 bool use_websafe_base64_for_bytes_;
267
268 GOOGLE_DISALLOW_IMPLICIT_CONSTRUCTORS(JsonObjectWriter);
269};
270
271} // namespace converter
272} // namespace util
273} // namespace protobuf
274} // namespace google
275
276#include <google/protobuf/port_undef.inc>
277
278#endif // GOOGLE_PROTOBUF_UTIL_INTERNAL_JSON_OBJECTWRITER_H__
279