123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /* A JSON pretty-printer class. */
- // A typical JSON-writing library requires you to first build up a data
- // structure that represents a JSON object and then serialize it (to file, or
- // somewhere else). This approach makes for a clean API, but building the data
- // structure takes up memory. Sometimes that isn't desirable, such as when the
- // JSON data is produced for memory reporting.
- //
- // The JSONWriter class instead allows JSON data to be written out
- // incrementally without building up large data structures.
- //
- // The API is slightly uglier than you would see in a typical JSON-writing
- // library, but still fairly easy to use. It's possible to generate invalid
- // JSON with JSONWriter, but typically the most basic testing will identify any
- // such problems.
- //
- // Similarly, there are no RAII facilities for automatically closing objects
- // and arrays. These would be nice if you are generating all your code within
- // nested functions, but in other cases you'd have to maintain an explicit
- // stack of RAII objects and manually unwind it, which is no better than just
- // calling "end" functions. Furthermore, the consequences of forgetting to
- // close an object or array are obvious and, again, will be identified via
- // basic testing, unlike other cases where RAII is typically used (e.g. smart
- // pointers) and the consequences of defects are more subtle.
- //
- // Importantly, the class does solve the two hard problems of JSON
- // pretty-printing, which are (a) correctly escaping strings, and (b) adding
- // appropriate indentation and commas between items.
- //
- // By default, every property is placed on its own line. However, it is
- // possible to request that objects and arrays be placed entirely on a single
- // line, which can reduce output size significantly in some cases.
- //
- // Strings used (for property names and string property values) are |const
- // char*| throughout, and can be ASCII or UTF-8.
- //
- // EXAMPLE
- // -------
- // Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The
- // following code:
- //
- // JSONWriter w(MakeUnique<MyWriteFunc>());
- // w.Start();
- // {
- // w.NullProperty("null");
- // w.BoolProperty("bool", true);
- // w.IntProperty("int", 1);
- // w.StartArrayProperty("array");
- // {
- // w.StringElement("string");
- // w.StartObjectElement();
- // {
- // w.DoubleProperty("double", 3.4);
- // w.StartArrayProperty("single-line array", w.SingleLineStyle);
- // {
- // w.IntElement(1);
- // w.StartObjectElement(); // SingleLineStyle is inherited from
- // w.EndObjectElement(); // above for this collection
- // }
- // w.EndArray();
- // }
- // w.EndObjectElement();
- // }
- // w.EndArrayProperty();
- // }
- // w.End();
- //
- // will produce pretty-printed output for the following JSON object:
- //
- // {
- // "null": null,
- // "bool": true,
- // "int": 1,
- // "array": [
- // "string",
- // {
- // "double": 3.4,
- // "single-line array": [1, {}]
- // }
- // ]
- // }
- //
- // The nesting in the example code is obviously optional, but can aid
- // readability.
- #ifndef mozilla_JSONWriter_h
- #define mozilla_JSONWriter_h
- #include "mozilla/double-conversion.h"
- #include "mozilla/IntegerPrintfMacros.h"
- #include "mozilla/PodOperations.h"
- #include "mozilla/Sprintf.h"
- #include "mozilla/UniquePtr.h"
- #include "mozilla/Vector.h"
- #include <stdio.h>
- namespace mozilla {
- // A quasi-functor for JSONWriter. We don't use a true functor because that
- // requires templatizing JSONWriter, and the templatization seeps to lots of
- // places we don't want it to.
- class JSONWriteFunc
- {
- public:
- virtual void Write(const char* aStr) = 0;
- virtual ~JSONWriteFunc() {}
- };
- // Ideally this would be within |EscapedString| but when compiling with GCC
- // on Linux that caused link errors, whereas this formulation didn't.
- namespace detail {
- extern MFBT_DATA const char gTwoCharEscapes[256];
- } // namespace detail
- class JSONWriter
- {
- // From http://www.ietf.org/rfc/rfc4627.txt:
- //
- // "All Unicode characters may be placed within the quotation marks except
- // for the characters that must be escaped: quotation mark, reverse
- // solidus, and the control characters (U+0000 through U+001F)."
- //
- // This implementation uses two-char escape sequences where possible, namely:
- //
- // \", \\, \b, \f, \n, \r, \t
- //
- // All control characters not in the above list are represented with a
- // six-char escape sequence, e.g. '\u000b' (a.k.a. '\v').
- //
- class EscapedString
- {
- // Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned|
- // indicates which one is in use. They're not within a union because that
- // wouldn't work with UniquePtr.
- bool mIsOwned;
- const char* mUnownedStr;
- UniquePtr<char[]> mOwnedStr;
- void SanityCheck() const
- {
- MOZ_ASSERT_IF( mIsOwned, mOwnedStr.get() && !mUnownedStr);
- MOZ_ASSERT_IF(!mIsOwned, !mOwnedStr.get() && mUnownedStr);
- }
- static char hexDigitToAsciiChar(uint8_t u)
- {
- u = u & 0xf;
- return u < 10 ? '0' + u : 'a' + (u - 10);
- }
- public:
- explicit EscapedString(const char* aStr)
- : mUnownedStr(nullptr)
- , mOwnedStr(nullptr)
- {
- const char* p;
- // First, see if we need to modify the string.
- size_t nExtra = 0;
- p = aStr;
- while (true) {
- uint8_t u = *p; // ensure it can't be interpreted as negative
- if (u == 0) {
- break;
- }
- if (detail::gTwoCharEscapes[u]) {
- nExtra += 1;
- } else if (u <= 0x1f) {
- nExtra += 5;
- }
- p++;
- }
- if (nExtra == 0) {
- // No escapes needed. Easy.
- mIsOwned = false;
- mUnownedStr = aStr;
- return;
- }
- // Escapes are needed. We'll create a new string.
- mIsOwned = true;
- size_t len = (p - aStr) + nExtra;
- mOwnedStr = MakeUnique<char[]>(len + 1);
- p = aStr;
- size_t i = 0;
- while (true) {
- uint8_t u = *p; // ensure it can't be interpreted as negative
- if (u == 0) {
- mOwnedStr[i] = 0;
- break;
- }
- if (detail::gTwoCharEscapes[u]) {
- mOwnedStr[i++] = '\\';
- mOwnedStr[i++] = detail::gTwoCharEscapes[u];
- } else if (u <= 0x1f) {
- mOwnedStr[i++] = '\\';
- mOwnedStr[i++] = 'u';
- mOwnedStr[i++] = '0';
- mOwnedStr[i++] = '0';
- mOwnedStr[i++] = hexDigitToAsciiChar((u & 0x00f0) >> 4);
- mOwnedStr[i++] = hexDigitToAsciiChar(u & 0x000f);
- } else {
- mOwnedStr[i++] = u;
- }
- p++;
- }
- }
- ~EscapedString()
- {
- SanityCheck();
- }
- const char* get() const
- {
- SanityCheck();
- return mIsOwned ? mOwnedStr.get() : mUnownedStr;
- }
- };
- public:
- // Collections (objects and arrays) are printed in a multi-line style by
- // default. This can be changed to a single-line style if SingleLineStyle is
- // specified. If a collection is printed in single-line style, every nested
- // collection within it is also printed in single-line style, even if
- // multi-line style is requested.
- enum CollectionStyle {
- MultiLineStyle, // the default
- SingleLineStyle
- };
- protected:
- const UniquePtr<JSONWriteFunc> mWriter;
- Vector<bool, 8> mNeedComma; // do we need a comma at depth N?
- Vector<bool, 8> mNeedNewlines; // do we need newlines at depth N?
- size_t mDepth; // the current nesting depth
- void Indent()
- {
- for (size_t i = 0; i < mDepth; i++) {
- mWriter->Write(" ");
- }
- }
- // Adds whatever is necessary (maybe a comma, and then a newline and
- // whitespace) to separate an item (property or element) from what's come
- // before.
- void Separator()
- {
- if (mNeedComma[mDepth]) {
- mWriter->Write(",");
- }
- if (mDepth > 0 && mNeedNewlines[mDepth]) {
- mWriter->Write("\n");
- Indent();
- } else if (mNeedComma[mDepth]) {
- mWriter->Write(" ");
- }
- }
- void PropertyNameAndColon(const char* aName)
- {
- EscapedString escapedName(aName);
- mWriter->Write("\"");
- mWriter->Write(escapedName.get());
- mWriter->Write("\": ");
- }
- void Scalar(const char* aMaybePropertyName, const char* aStringValue)
- {
- Separator();
- if (aMaybePropertyName) {
- PropertyNameAndColon(aMaybePropertyName);
- }
- mWriter->Write(aStringValue);
- mNeedComma[mDepth] = true;
- }
- void QuotedScalar(const char* aMaybePropertyName, const char* aStringValue)
- {
- Separator();
- if (aMaybePropertyName) {
- PropertyNameAndColon(aMaybePropertyName);
- }
- mWriter->Write("\"");
- mWriter->Write(aStringValue);
- mWriter->Write("\"");
- mNeedComma[mDepth] = true;
- }
- void NewVectorEntries()
- {
- // If these tiny allocations OOM we might as well just crash because we
- // must be in serious memory trouble.
- MOZ_RELEASE_ASSERT(mNeedComma.resizeUninitialized(mDepth + 1));
- MOZ_RELEASE_ASSERT(mNeedNewlines.resizeUninitialized(mDepth + 1));
- mNeedComma[mDepth] = false;
- mNeedNewlines[mDepth] = true;
- }
- void StartCollection(const char* aMaybePropertyName, const char* aStartChar,
- CollectionStyle aStyle = MultiLineStyle)
- {
- Separator();
- if (aMaybePropertyName) {
- mWriter->Write("\"");
- mWriter->Write(aMaybePropertyName);
- mWriter->Write("\": ");
- }
- mWriter->Write(aStartChar);
- mNeedComma[mDepth] = true;
- mDepth++;
- NewVectorEntries();
- mNeedNewlines[mDepth] =
- mNeedNewlines[mDepth - 1] && aStyle == MultiLineStyle;
- }
- // Adds the whitespace and closing char necessary to end a collection.
- void EndCollection(const char* aEndChar)
- {
- if (mNeedNewlines[mDepth]) {
- mWriter->Write("\n");
- mDepth--;
- Indent();
- } else {
- mDepth--;
- }
- mWriter->Write(aEndChar);
- }
- public:
- explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter)
- : mWriter(Move(aWriter))
- , mNeedComma()
- , mNeedNewlines()
- , mDepth(0)
- {
- NewVectorEntries();
- }
- // Returns the JSONWriteFunc passed in at creation, for temporary use. The
- // JSONWriter object still owns the JSONWriteFunc.
- JSONWriteFunc* WriteFunc() const { return mWriter.get(); }
- // For all the following functions, the "Prints:" comment indicates what the
- // basic output looks like. However, it doesn't indicate the whitespace and
- // trailing commas, which are automatically added as required.
- //
- // All property names and string properties are escaped as necessary.
- // Prints: {
- void Start(CollectionStyle aStyle = MultiLineStyle)
- {
- StartCollection(nullptr, "{", aStyle);
- }
- // Prints: }
- void End() { EndCollection("}\n"); }
- // Prints: "<aName>": null
- void NullProperty(const char* aName)
- {
- Scalar(aName, "null");
- }
- // Prints: null
- void NullElement() { NullProperty(nullptr); }
- // Prints: "<aName>": <aBool>
- void BoolProperty(const char* aName, bool aBool)
- {
- Scalar(aName, aBool ? "true" : "false");
- }
- // Prints: <aBool>
- void BoolElement(bool aBool) { BoolProperty(nullptr, aBool); }
- // Prints: "<aName>": <aInt>
- void IntProperty(const char* aName, int64_t aInt)
- {
- char buf[64];
- SprintfLiteral(buf, "%" PRId64, aInt);
- Scalar(aName, buf);
- }
- // Prints: <aInt>
- void IntElement(int64_t aInt) { IntProperty(nullptr, aInt); }
- // Prints: "<aName>": <aDouble>
- void DoubleProperty(const char* aName, double aDouble)
- {
- static const size_t buflen = 64;
- char buf[buflen];
- const double_conversion::DoubleToStringConverter &converter =
- double_conversion::DoubleToStringConverter::EcmaScriptConverter();
- double_conversion::StringBuilder builder(buf, buflen);
- converter.ToShortest(aDouble, &builder);
- Scalar(aName, builder.Finalize());
- }
- // Prints: <aDouble>
- void DoubleElement(double aDouble) { DoubleProperty(nullptr, aDouble); }
- // Prints: "<aName>": "<aStr>"
- void StringProperty(const char* aName, const char* aStr)
- {
- EscapedString escapedStr(aStr);
- QuotedScalar(aName, escapedStr.get());
- }
- // Prints: "<aStr>"
- void StringElement(const char* aStr) { StringProperty(nullptr, aStr); }
- // Prints: "<aName>": [
- void StartArrayProperty(const char* aName,
- CollectionStyle aStyle = MultiLineStyle)
- {
- StartCollection(aName, "[", aStyle);
- }
- // Prints: [
- void StartArrayElement(CollectionStyle aStyle = MultiLineStyle)
- {
- StartArrayProperty(nullptr, aStyle);
- }
- // Prints: ]
- void EndArray() { EndCollection("]"); }
- // Prints: "<aName>": {
- void StartObjectProperty(const char* aName,
- CollectionStyle aStyle = MultiLineStyle)
- {
- StartCollection(aName, "{", aStyle);
- }
- // Prints: {
- void StartObjectElement(CollectionStyle aStyle = MultiLineStyle)
- {
- StartObjectProperty(nullptr, aStyle);
- }
- // Prints: }
- void EndObject() { EndCollection("}"); }
- };
- } // namespace mozilla
- #endif /* mozilla_JSONWriter_h */
|