/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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.
// 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: virtualvoid Write(const Span<constchar>& aStr) = 0; virtual ~JSONWriteFunc() = default;
};
// 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 constchar 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 { // `mStringSpan` initially points at the user-provided string. If that // string needs escaping, `mStringSpan` will point at `mOwnedStr` below.
Span<constchar> mStringSpan; // String storage in case escaping is actually needed, null otherwise.
UniquePtr<char[]> mOwnedStr;
void CheckInvariants() const { // Either there was no escaping so `mOwnedStr` is null, or escaping was // needed, in which case `mStringSpan` should point at `mOwnedStr`.
MOZ_ASSERT(!mOwnedStr || mStringSpan.data() == mOwnedStr.get());
}
staticchar hexDigitToAsciiChar(uint8_t u) {
u = u & 0xf; return u < 10 ? '0' + u : 'a' + (u - 10);
}
public: explicit EscapedString(const Span<constchar>& aStr) : mStringSpan(aStr) { // First, see if we need to modify the string.
size_t nExtra = 0; for (constchar& c : aStr) { // ensure it can't be interpreted as negative
uint8_t u = static_cast<uint8_t>(c); if (u == 0) { // Null terminator within the span, assume we may have been given a // span to a buffer that contains a null-terminated string in it. // We need to truncate the Span so that it doesn't include this null // terminator and anything past it; Either we will return it as-is, or // processing should stop there.
mStringSpan = mStringSpan.First(&c - mStringSpan.data()); break;
} if (detail::gTwoCharEscapes[u]) {
nExtra += 1;
} elseif (u <= 0x1f) {
nExtra += 5;
}
}
// Note: Don't use `aStr` anymore, as it could contain a null terminator; // use the correctly-sized `mStringSpan` instead.
if (nExtra == 0) { // No escapes needed. mStringSpan already points at the original string.
CheckInvariants(); return;
}
// Escapes are needed. We'll create a new string.
mOwnedStr = MakeUnique<char[]>(mStringSpan.Length() + nExtra);
size_t i = 0; for (constchar c : mStringSpan) { // ensure it can't be interpreted as negative
uint8_t u = static_cast<uint8_t>(c);
MOZ_ASSERT(u != 0, "Null terminator should have been handled above"); if (detail::gTwoCharEscapes[u]) {
mOwnedStr[i++] = '\\';
mOwnedStr[i++] = detail::gTwoCharEscapes[u];
} elseif (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;
}
}
MOZ_ASSERT(i == mStringSpan.Length() + nExtra);
mStringSpan = Span<constchar>(mOwnedStr.get(), i);
CheckInvariants();
}
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. // If SingleLineStyle is set in the constructer, all JSON whitespace is // eliminated, including spaces after colons and commas, for the most compact // encoding possible. enum CollectionStyle {
MultiLineStyle, // the default
SingleLineStyle
};
JSONWriteFunc& mWriter; const UniquePtr<JSONWriteFunc> mMaybeOwnedWriter;
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(scSpaceString);
}
}
// 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(scCommaString);
} if (mDepth > 0 && mNeedNewlines[mDepth]) {
mWriter.Write(scNewLineString);
Indent();
} elseif (mNeedComma[mDepth] && mNeedNewlines[0]) {
mWriter.Write(scSpaceString);
}
}
void NewVectorEntries(bool aNeedNewLines) { // 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] = aNeedNewLines;
}
explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter,
CollectionStyle aStyle = MultiLineStyle)
: mWriter(*aWriter),
mMaybeOwnedWriter(std::move(aWriter)),
mNeedComma(),
mNeedNewlines(),
mDepth(0) {
MOZ_RELEASE_ASSERT(
mMaybeOwnedWriter, "JSONWriter must be given a non-null UniquePtr");
NewVectorEntries(aStyle == MultiLineStyle);
}
// Returns the JSONWriteFunc passed in at creation, for temporary use. The // JSONWriter object still owns the JSONWriteFunc.
JSONWriteFunc& WriteFunc() const { return mWriter; }
// 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.
template <size_t N> void NullProperty(constchar (&aName)[N]) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
NullProperty(Span<constchar>(aName, N));
}
template <size_t N> void BoolProperty(constchar (&aName)[N], bool aBool) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
BoolProperty(Span<constchar>(aName, N), aBool);
}
template <size_t N> void IntProperty(constchar (&aName)[N], int64_t aInt) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
IntProperty(Span<constchar>(aName, N), aInt);
}
template <size_t N> void DoubleProperty(constchar (&aName)[N], double aDouble) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
DoubleProperty(Span<constchar>(aName, N), aDouble);
}
template <size_t NN> void StringProperty(constchar (&aName)[NN], const Span<constchar>& aStr) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StringProperty(Span<constchar>(aName, NN), aStr);
}
template <size_t SN> void StringProperty(const Span<constchar>& aName, constchar (&aStr)[SN]) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StringProperty(aName, Span<constchar>(aStr, SN));
}
template <size_t NN, size_t SN> void StringProperty(constchar (&aName)[NN], constchar (&aStr)[SN]) { // Keep null terminators from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StringProperty(Span<constchar>(aName, NN), Span<constchar>(aStr, SN));
}
template <size_t N> void StringElement(constchar (&aName)[N]) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StringElement(Span<constchar>(aName, N));
}
template <size_t N> void StartArrayProperty(constchar (&aName)[N],
CollectionStyle aStyle = MultiLineStyle) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StartArrayProperty(Span<constchar>(aName, N), aStyle);
}
template <size_t N> void StartObjectProperty(constchar (&aName)[N],
CollectionStyle aStyle = MultiLineStyle) { // Keep null terminator from literal strings, will be removed by // EscapedString. This way C buffer arrays can be used as well.
StartObjectProperty(Span<constchar>(aName, N), aStyle);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.