diff --git a/mfbt/JSONWriter.h b/mfbt/JSONWriter.h index 1aa83043c8c..22c262f53ed 100644 --- a/mfbt/JSONWriter.h +++ b/mfbt/JSONWriter.h @@ -33,6 +33,10 @@ // 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. // @@ -54,6 +58,13 @@ // w.StartObjectElement(); // { // w.PointerProperty("ptr", (void*)0x12345678); +// 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(); // } @@ -71,7 +82,8 @@ // "array": [ // 3.4, // { -// "ptr": "0x12345678" +// "ptr": "0x12345678", +// "single-line array": [1, {}] // } // ] // } @@ -217,8 +229,21 @@ class JSONWriter } }; +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 + }; + +private: const UniquePtr mWriter; Vector mNeedComma; // do we need a comma at depth N? + Vector mNeedNewlines; // do we need newlines at depth N? size_t mDepth; // the current nesting depth void Indent() @@ -236,10 +261,12 @@ class JSONWriter if (mNeedComma[mDepth]) { mWriter->Write(","); } - if (mDepth > 0) { + if (mDepth > 0 && mNeedNewlines[mDepth]) { mWriter->Write("\n"); + Indent(); + } else if (mNeedComma[mDepth]) { + mWriter->Write(" "); } - Indent(); } void PropertyNameAndColon(const char* aName) @@ -272,15 +299,18 @@ class JSONWriter mNeedComma[mDepth] = true; } - void NewCommaEntry() + void NewVectorEntries() { - // If this tiny allocation OOMs we might as well just crash because we must - // be in serious memory trouble. + // If these tiny allocations OOM we might as well just crash because we + // must be in serious memory trouble. MOZ_RELEASE_ASSERT(mNeedComma.growByUninitialized(1)); + MOZ_RELEASE_ASSERT(mNeedNewlines.growByUninitialized(1)); mNeedComma[mDepth] = false; + mNeedNewlines[mDepth] = true; } - void StartCollection(const char* aMaybePropertyName, const char* aStartChar) + void StartCollection(const char* aMaybePropertyName, const char* aStartChar, + CollectionStyle aStyle = MultiLineStyle) { Separator(); if (aMaybePropertyName) { @@ -291,15 +321,21 @@ class JSONWriter mWriter->Write(aStartChar); mNeedComma[mDepth] = true; mDepth++; - NewCommaEntry(); + NewVectorEntries(); + mNeedNewlines[mDepth] = + mNeedNewlines[mDepth - 1] && aStyle == MultiLineStyle; } // Adds the whitespace and closing char necessary to end a collection. void EndCollection(const char* aEndChar) { - mDepth--; - mWriter->Write("\n"); - Indent(); + if (mNeedNewlines[mDepth]) { + mWriter->Write("\n"); + mDepth--; + Indent(); + } else { + mDepth--; + } mWriter->Write(aEndChar); } @@ -307,9 +343,10 @@ public: explicit JSONWriter(UniquePtr aWriter) : mWriter(Move(aWriter)) , mNeedComma() + , mNeedNewlines() , mDepth(0) { - NewCommaEntry(); + NewVectorEntries(); } // Returns the JSONWriteFunc passed in at creation, for temporary use. The @@ -317,15 +354,18 @@ public: 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 indentation and + // 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() { StartCollection(nullptr, "{"); } + void Start(CollectionStyle aStyle = MultiLineStyle) + { + StartCollection(nullptr, "{", aStyle); + } - // Prints: }\n + // Prints: } void End() { EndCollection("}\n"); } // Prints: "": null @@ -396,19 +436,33 @@ public: void PointerElement(const void* aPtr) { PointerProperty(nullptr, aPtr); } // Prints: "": [ - void StartArrayProperty(const char* aName) { StartCollection(aName, "["); } + void StartArrayProperty(const char* aName, + CollectionStyle aStyle = MultiLineStyle) + { + StartCollection(aName, "[", aStyle); + } // Prints: [ - void StartArrayElement() { StartArrayProperty(nullptr); } + void StartArrayElement(CollectionStyle aStyle = MultiLineStyle) + { + StartArrayProperty(nullptr, aStyle); + } // Prints: ] void EndArray() { EndCollection("]"); } // Prints: "": { - void StartObjectProperty(const char* aName) { StartCollection(aName, "{"); } + void StartObjectProperty(const char* aName, + CollectionStyle aStyle = MultiLineStyle) + { + StartCollection(aName, "{", aStyle); + } // Prints: { - void StartObjectElement() { StartObjectProperty(nullptr); } + void StartObjectElement(CollectionStyle aStyle = MultiLineStyle) + { + StartObjectProperty(nullptr, aStyle); + } // Prints: } void EndObject() { EndCollection("}"); } diff --git a/mfbt/tests/TestJSONWriter.cpp b/mfbt/tests/TestJSONWriter.cpp index 87950a7c904..5bb64f77f22 100644 --- a/mfbt/tests/TestJSONWriter.cpp +++ b/mfbt/tests/TestJSONWriter.cpp @@ -73,20 +73,23 @@ void TestBasicProperties() \"ptr1\": \"0x0\",\n\ \"ptr2\": \"0xdeadbeef\",\n\ \"ptr3\": \"0xfacade\",\n\ - \"len 0 array\": [\n\ + \"len 0 array, multi-line\": [\n\ ],\n\ + \"len 0 array, single-line\": [],\n\ \"len 1 array\": [\n\ 1\n\ ],\n\ - \"len 5 array\": [\n\ + \"len 5 array, multi-line\": [\n\ 1,\n\ 2,\n\ 3,\n\ 4,\n\ 5\n\ ],\n\ - \"len 0 object\": {\n\ + \"len 3 array, single-line\": [1, [{}, 2, []], 3],\n\ + \"len 0 object, multi-line\": {\n\ },\n\ + \"len 0 object, single-line\": {},\n\ \"len 1 object\": {\n\ \"one\": 1\n\ },\n\ @@ -96,7 +99,8 @@ void TestBasicProperties() \"three\": 3,\n\ \"four\": 4,\n\ \"five\": 5\n\ - }\n\ + },\n\ + \"len 3 object, single-line\": {\"a\": 1, \"b\": [{}, 2, []], \"c\": 3}\n\ }\n\ "; @@ -127,7 +131,10 @@ void TestBasicProperties() w.PointerProperty("ptr2", (void*)0xdeadbeef); w.PointerProperty("ptr3", (void*)0xFaCaDe); - w.StartArrayProperty("len 0 array"); + w.StartArrayProperty("len 0 array, multi-line", w.MultiLineStyle); + w.EndArray(); + + w.StartArrayProperty("len 0 array, single-line", w.SingleLineStyle); w.EndArray(); w.StartArrayProperty("len 1 array"); @@ -136,7 +143,7 @@ void TestBasicProperties() } w.EndArray(); - w.StartArrayProperty("len 5 array"); + w.StartArrayProperty("len 5 array, multi-line", w.MultiLineStyle); { w.IntElement(1); w.IntElement(2); @@ -146,7 +153,28 @@ void TestBasicProperties() } w.EndArray(); - w.StartObjectProperty("len 0 object"); + w.StartArrayProperty("len 3 array, single-line", w.SingleLineStyle); + { + w.IntElement(1); + w.StartArrayElement(); + { + w.StartObjectElement(w.SingleLineStyle); + w.EndObject(); + + w.IntElement(2); + + w.StartArrayElement(w.MultiLineStyle); // style overridden from above + w.EndArray(); + } + w.EndArray(); + w.IntElement(3); + } + w.EndArray(); + + w.StartObjectProperty("len 0 object, multi-line"); + w.EndObject(); + + w.StartObjectProperty("len 0 object, single-line", w.SingleLineStyle); w.EndObject(); w.StartObjectProperty("len 1 object"); @@ -164,6 +192,24 @@ void TestBasicProperties() w.IntProperty("five", 5); } w.EndObject(); + + w.StartObjectProperty("len 3 object, single-line", w.SingleLineStyle); + { + w.IntProperty("a", 1); + w.StartArrayProperty("b"); + { + w.StartObjectElement(); + w.EndObject(); + + w.IntElement(2); + + w.StartArrayElement(w.SingleLineStyle); + w.EndArray(); + } + w.EndArray(); + w.IntProperty("c", 3); + } + w.EndObject(); } w.End(); @@ -194,6 +240,7 @@ void TestBasicElements() \"0xfacade\",\n\ [\n\ ],\n\ + [],\n\ [\n\ 1\n\ ],\n\ @@ -204,8 +251,10 @@ void TestBasicElements() 4,\n\ 5\n\ ],\n\ + [1, [{}, 2, []], 3],\n\ {\n\ },\n\ + {},\n\ {\n\ \"one\": 1\n\ },\n\ @@ -215,7 +264,8 @@ void TestBasicElements() \"three\": 3,\n\ \"four\": 4,\n\ \"five\": 5\n\ - }\n\ + },\n\ + {\"a\": 1, \"b\": [{}, 2, []], \"c\": 3}\n\ ]\n\ }\n\ "; @@ -251,6 +301,9 @@ void TestBasicElements() w.StartArrayElement(); w.EndArray(); + w.StartArrayElement(w.SingleLineStyle); + w.EndArray(); + w.StartArrayElement(); { w.IntElement(1); @@ -267,9 +320,30 @@ void TestBasicElements() } w.EndArray(); + w.StartArrayElement(w.SingleLineStyle); + { + w.IntElement(1); + w.StartArrayElement(); + { + w.StartObjectElement(w.SingleLineStyle); + w.EndObject(); + + w.IntElement(2); + + w.StartArrayElement(w.MultiLineStyle); // style overridden from above + w.EndArray(); + } + w.EndArray(); + w.IntElement(3); + } + w.EndArray(); + w.StartObjectElement(); w.EndObject(); + w.StartObjectElement(w.SingleLineStyle); + w.EndObject(); + w.StartObjectElement(); { w.IntProperty("one", 1); @@ -285,6 +359,24 @@ void TestBasicElements() w.IntProperty("five", 5); } w.EndObject(); + + w.StartObjectElement(w.SingleLineStyle); + { + w.IntProperty("a", 1); + w.StartArrayProperty("b"); + { + w.StartObjectElement(); + w.EndObject(); + + w.IntElement(2); + + w.StartArrayElement(w.SingleLineStyle); + w.EndArray(); + } + w.EndArray(); + w.IntProperty("c", 3); + } + w.EndObject(); } w.EndArray(); w.End(); @@ -292,6 +384,47 @@ void TestBasicElements() Check(w.WriteFunc(), expected); } +void TestOneLineObject() +{ + const char* expected = "\ +{\"i\": 1, \"array\": [null, [{}], {\"o\": {}}, \"s\"], \"d\": 3.33}\n\ +"; + + JSONWriter w(MakeUnique()); + + w.Start(w.SingleLineStyle); + + w.IntProperty("i", 1); + + w.StartArrayProperty("array"); + { + w.NullElement(); + + w.StartArrayElement(w.MultiLineStyle); // style overridden from above + { + w.StartObjectElement(); + w.EndObject(); + } + w.EndArray(); + + w.StartObjectElement(); + { + w.StartObjectProperty("o"); + w.EndObject(); + } + w.EndObject(); + + w.StringElement("s"); + } + w.EndArray(); + + w.DoubleProperty("d", 3.33); + + w.End(); + + Check(w.WriteFunc(), expected); +} + void TestStringEscaping() { // This test uses hexadecimal character escapes because UTF8 literals cause @@ -412,6 +545,7 @@ int main(void) { TestBasicProperties(); TestBasicElements(); + TestOneLineObject(); TestStringEscaping(); TestDeepNesting();