1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* A JSON pretty-printer class. */
9 // A typical JSON-writing library requires you to first build up a data
10 // structure that represents a JSON object and then serialize it (to file, or
11 // somewhere else). This approach makes for a clean API, but building the data
12 // structure takes up memory. Sometimes that isn't desirable, such as when the
13 // JSON data is produced for memory reporting.
15 // The JSONWriter class instead allows JSON data to be written out
16 // incrementally without building up large data structures.
18 // The API is slightly uglier than you would see in a typical JSON-writing
19 // library, but still fairly easy to use. It's possible to generate invalid
20 // JSON with JSONWriter, but typically the most basic testing will identify any
23 // Similarly, there are no RAII facilities for automatically closing objects
24 // and arrays. These would be nice if you are generating all your code within
25 // nested functions, but in other cases you'd have to maintain an explicit
26 // stack of RAII objects and manually unwind it, which is no better than just
27 // calling "end" functions. Furthermore, the consequences of forgetting to
28 // close an object or array are obvious and, again, will be identified via
29 // basic testing, unlike other cases where RAII is typically used (e.g. smart
30 // pointers) and the consequences of defects are more subtle.
32 // Importantly, the class does solve the two hard problems of JSON
33 // pretty-printing, which are (a) correctly escaping strings, and (b) adding
34 // appropriate indentation and commas between items.
36 // By default, every property is placed on its own line. However, it is
37 // possible to request that objects and arrays be placed entirely on a single
38 // line, which can reduce output size significantly in some cases.
40 // Strings used (for property names and string property values) are |const
41 // char*| throughout, and can be ASCII or UTF-8.
45 // Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The
48 // JSONWriter w(MakeUnique<MyWriteFunc>());
51 // w.NullProperty("null");
52 // w.BoolProperty("bool", true);
53 // w.IntProperty("int", 1);
54 // w.StartArrayProperty("array");
56 // w.StringElement("string");
57 // w.StartObjectElement();
59 // w.DoubleProperty("double", 3.4);
60 // w.StartArrayProperty("single-line array", w.SingleLineStyle);
63 // w.StartObjectElement(); // SingleLineStyle is inherited from
64 // w.EndObjectElement(); // above for this collection
68 // w.EndObjectElement();
70 // w.EndArrayProperty();
74 // will produce pretty-printed output for the following JSON object:
84 // "single-line array": [1, {}]
89 // The nesting in the example code is obviously optional, but can aid
92 #ifndef mozilla_JSONWriter_h
93 #define mozilla_JSONWriter_h
95 #include "double-conversion/double-conversion.h"
96 #include "mozilla/Assertions.h"
97 #include "mozilla/IntegerPrintfMacros.h"
98 #include "mozilla/PodOperations.h"
99 #include "mozilla/Sprintf.h"
100 #include "mozilla/UniquePtr.h"
101 #include "mozilla/Vector.h"
107 // A quasi-functor for JSONWriter. We don't use a true functor because that
108 // requires templatizing JSONWriter, and the templatization seeps to lots of
109 // places we don't want it to.
110 class JSONWriteFunc
{
112 virtual void Write(const char* aStr
) = 0;
113 virtual ~JSONWriteFunc() {}
116 // Ideally this would be within |EscapedString| but when compiling with GCC
117 // on Linux that caused link errors, whereas this formulation didn't.
119 extern MFBT_DATA
const char gTwoCharEscapes
[256];
120 } // namespace detail
123 // From http://www.ietf.org/rfc/rfc4627.txt:
125 // "All Unicode characters may be placed within the quotation marks except
126 // for the characters that must be escaped: quotation mark, reverse
127 // solidus, and the control characters (U+0000 through U+001F)."
129 // This implementation uses two-char escape sequences where possible, namely:
131 // \", \\, \b, \f, \n, \r, \t
133 // All control characters not in the above list are represented with a
134 // six-char escape sequence, e.g. '\u000b' (a.k.a. '\v').
136 class EscapedString
{
137 // Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned|
138 // indicates which one is in use. They're not within a union because that
139 // wouldn't work with UniquePtr.
141 const char* mUnownedStr
;
142 UniquePtr
<char[]> mOwnedStr
;
144 void SanityCheck() const {
145 MOZ_ASSERT_IF(mIsOwned
, mOwnedStr
.get() && !mUnownedStr
);
146 MOZ_ASSERT_IF(!mIsOwned
, !mOwnedStr
.get() && mUnownedStr
);
149 static char hexDigitToAsciiChar(uint8_t u
) {
151 return u
< 10 ? '0' + u
: 'a' + (u
- 10);
155 explicit EscapedString(const char* aStr
)
156 : mUnownedStr(nullptr), mOwnedStr(nullptr) {
159 // First, see if we need to modify the string.
163 uint8_t u
= *p
; // ensure it can't be interpreted as negative
167 if (detail::gTwoCharEscapes
[u
]) {
169 } else if (u
<= 0x1f) {
176 // No escapes needed. Easy.
182 // Escapes are needed. We'll create a new string.
184 size_t len
= (p
- aStr
) + nExtra
;
185 mOwnedStr
= MakeUnique
<char[]>(len
+ 1);
191 uint8_t u
= *p
; // ensure it can't be interpreted as negative
196 if (detail::gTwoCharEscapes
[u
]) {
197 mOwnedStr
[i
++] = '\\';
198 mOwnedStr
[i
++] = detail::gTwoCharEscapes
[u
];
199 } else if (u
<= 0x1f) {
200 mOwnedStr
[i
++] = '\\';
201 mOwnedStr
[i
++] = 'u';
202 mOwnedStr
[i
++] = '0';
203 mOwnedStr
[i
++] = '0';
204 mOwnedStr
[i
++] = hexDigitToAsciiChar((u
& 0x00f0) >> 4);
205 mOwnedStr
[i
++] = hexDigitToAsciiChar(u
& 0x000f);
213 ~EscapedString() { SanityCheck(); }
215 const char* get() const {
217 return mIsOwned
? mOwnedStr
.get() : mUnownedStr
;
222 // Collections (objects and arrays) are printed in a multi-line style by
223 // default. This can be changed to a single-line style if SingleLineStyle is
224 // specified. If a collection is printed in single-line style, every nested
225 // collection within it is also printed in single-line style, even if
226 // multi-line style is requested.
227 enum CollectionStyle
{
228 MultiLineStyle
, // the default
233 const UniquePtr
<JSONWriteFunc
> mWriter
;
234 Vector
<bool, 8> mNeedComma
; // do we need a comma at depth N?
235 Vector
<bool, 8> mNeedNewlines
; // do we need newlines at depth N?
236 size_t mDepth
; // the current nesting depth
239 for (size_t i
= 0; i
< mDepth
; i
++) {
244 // Adds whatever is necessary (maybe a comma, and then a newline and
245 // whitespace) to separate an item (property or element) from what's come
248 if (mNeedComma
[mDepth
]) {
251 if (mDepth
> 0 && mNeedNewlines
[mDepth
]) {
252 mWriter
->Write("\n");
254 } else if (mNeedComma
[mDepth
]) {
259 void PropertyNameAndColon(const char* aName
) {
260 EscapedString
escapedName(aName
);
261 mWriter
->Write("\"");
262 mWriter
->Write(escapedName
.get());
263 mWriter
->Write("\": ");
266 void Scalar(const char* aMaybePropertyName
, const char* aStringValue
) {
268 if (aMaybePropertyName
) {
269 PropertyNameAndColon(aMaybePropertyName
);
271 mWriter
->Write(aStringValue
);
272 mNeedComma
[mDepth
] = true;
275 void QuotedScalar(const char* aMaybePropertyName
, const char* aStringValue
) {
277 if (aMaybePropertyName
) {
278 PropertyNameAndColon(aMaybePropertyName
);
280 mWriter
->Write("\"");
281 mWriter
->Write(aStringValue
);
282 mWriter
->Write("\"");
283 mNeedComma
[mDepth
] = true;
286 void NewVectorEntries() {
287 // If these tiny allocations OOM we might as well just crash because we
288 // must be in serious memory trouble.
289 MOZ_RELEASE_ASSERT(mNeedComma
.resizeUninitialized(mDepth
+ 1));
290 MOZ_RELEASE_ASSERT(mNeedNewlines
.resizeUninitialized(mDepth
+ 1));
291 mNeedComma
[mDepth
] = false;
292 mNeedNewlines
[mDepth
] = true;
295 void StartCollection(const char* aMaybePropertyName
, const char* aStartChar
,
296 CollectionStyle aStyle
= MultiLineStyle
) {
298 if (aMaybePropertyName
) {
299 PropertyNameAndColon(aMaybePropertyName
);
301 mWriter
->Write(aStartChar
);
302 mNeedComma
[mDepth
] = true;
305 mNeedNewlines
[mDepth
] =
306 mNeedNewlines
[mDepth
- 1] && aStyle
== MultiLineStyle
;
309 // Adds the whitespace and closing char necessary to end a collection.
310 void EndCollection(const char* aEndChar
) {
311 MOZ_ASSERT(mDepth
> 0);
312 if (mNeedNewlines
[mDepth
]) {
313 mWriter
->Write("\n");
319 mWriter
->Write(aEndChar
);
323 explicit JSONWriter(UniquePtr
<JSONWriteFunc
> aWriter
)
324 : mWriter(std::move(aWriter
)), mNeedComma(), mNeedNewlines(), mDepth(0) {
328 // Returns the JSONWriteFunc passed in at creation, for temporary use. The
329 // JSONWriter object still owns the JSONWriteFunc.
330 JSONWriteFunc
* WriteFunc() const { return mWriter
.get(); }
332 // For all the following functions, the "Prints:" comment indicates what the
333 // basic output looks like. However, it doesn't indicate the whitespace and
334 // trailing commas, which are automatically added as required.
336 // All property names and string properties are escaped as necessary.
339 void Start(CollectionStyle aStyle
= MultiLineStyle
) {
340 StartCollection(nullptr, "{", aStyle
);
344 void End() { EndCollection("}\n"); }
346 // Prints: "<aName>": null
347 void NullProperty(const char* aName
) { Scalar(aName
, "null"); }
350 void NullElement() { NullProperty(nullptr); }
352 // Prints: "<aName>": <aBool>
353 void BoolProperty(const char* aName
, bool aBool
) {
354 Scalar(aName
, aBool
? "true" : "false");
358 void BoolElement(bool aBool
) { BoolProperty(nullptr, aBool
); }
360 // Prints: "<aName>": <aInt>
361 void IntProperty(const char* aName
, int64_t aInt
) {
363 SprintfLiteral(buf
, "%" PRId64
, aInt
);
368 void IntElement(int64_t aInt
) { IntProperty(nullptr, aInt
); }
370 // Prints: "<aName>": <aDouble>
371 void DoubleProperty(const char* aName
, double aDouble
) {
372 static const size_t buflen
= 64;
374 const double_conversion::DoubleToStringConverter
& converter
=
375 double_conversion::DoubleToStringConverter::EcmaScriptConverter();
376 double_conversion::StringBuilder
builder(buf
, buflen
);
377 converter
.ToShortest(aDouble
, &builder
);
378 Scalar(aName
, builder
.Finalize());
382 void DoubleElement(double aDouble
) { DoubleProperty(nullptr, aDouble
); }
384 // Prints: "<aName>": "<aStr>"
385 void StringProperty(const char* aName
, const char* aStr
) {
386 EscapedString
escapedStr(aStr
);
387 QuotedScalar(aName
, escapedStr
.get());
391 void StringElement(const char* aStr
) { StringProperty(nullptr, aStr
); }
393 // Prints: "<aName>": [
394 void StartArrayProperty(const char* aName
,
395 CollectionStyle aStyle
= MultiLineStyle
) {
396 StartCollection(aName
, "[", aStyle
);
400 void StartArrayElement(CollectionStyle aStyle
= MultiLineStyle
) {
401 StartArrayProperty(nullptr, aStyle
);
405 void EndArray() { EndCollection("]"); }
407 // Prints: "<aName>": {
408 void StartObjectProperty(const char* aName
,
409 CollectionStyle aStyle
= MultiLineStyle
) {
410 StartCollection(aName
, "{", aStyle
);
414 void StartObjectElement(CollectionStyle aStyle
= MultiLineStyle
) {
415 StartObjectProperty(nullptr, aStyle
);
419 void EndObject() { EndCollection("}"); }
422 } // namespace mozilla
424 #endif /* mozilla_JSONWriter_h */