Bug 1074591 (part 1) - Allow JSON collections to be printed on a single line. r=froydnj.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 02 Oct 2014 18:01:19 -0700
changeset 231719 7151ebc290753ea29fa7ba53988d22e0d11ae61d
parent 231718 2095011ec061a02a94c47c881f6a2fc83bddbcd6
child 231720 1eb845ba1fc6328cd7c56b73d9948d6e227de271
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1074591
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1074591 (part 1) - Allow JSON collections to be printed on a single line. r=froydnj.
mfbt/JSONWriter.h
mfbt/tests/TestJSONWriter.cpp
--- a/mfbt/JSONWriter.h
+++ b/mfbt/JSONWriter.h
@@ -28,16 +28,20 @@
 // 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:
 //
@@ -49,16 +53,23 @@
 //     w.IntProperty("int", 1);
 //     w.StringProperty("string", "hello");
 //     w.StartArrayProperty("array");
 //     {
 //       w.DoubleElement(3.4);
 //       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();
 //     }
 //     w.EndArrayProperty();
 //   }
 //   w.End();
 //
 // will produce pretty-printed output for the following JSON object:
@@ -66,17 +77,18 @@
 //  {
 //   "null": null,
 //   "bool": true,
 //   "int": 1,
 //   "string": "hello",
 //   "array": [
 //    3.4,
 //    {
-//     "ptr": "0x12345678"
+//     "ptr": "0x12345678",
+//     "single-line array": [1, {}]
 //    }
 //   ]
 //  }
 //
 // The nesting in the example code is obviously optional, but can aid
 // readability.
 
 #ifndef mozilla_JSONWriter_h
@@ -212,18 +224,31 @@ class JSONWriter
 
     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
+  };
+
+private:
   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(" ");
     }
   }
@@ -231,20 +256,22 @@ class JSONWriter
   // 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) {
+    if (mDepth > 0 && mNeedNewlines[mDepth]) {
       mWriter->Write("\n");
+      Indent();
+    } else if (mNeedComma[mDepth]) {
+      mWriter->Write(" ");
     }
-    Indent();
   }
 
   void PropertyNameAndColon(const char* aName)
   {
     EscapedString escapedName(aName);
     mWriter->Write("\"");
     mWriter->Write(escapedName.get());
     mWriter->Write("\": ");
@@ -267,70 +294,83 @@ class JSONWriter
       PropertyNameAndColon(aMaybePropertyName);
     }
     mWriter->Write("\"");
     mWriter->Write(aStringValue);
     mWriter->Write("\"");
     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) {
       mWriter->Write("\"");
       mWriter->Write(aMaybePropertyName);
       mWriter->Write("\": ");
     }
     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);
   }
 
 public:
   explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter)
     : mWriter(Move(aWriter))
     , mNeedComma()
+    , mNeedNewlines()
     , mDepth(0)
   {
-    NewCommaEntry();
+    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 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: "<aName>": null
   void NullProperty(const char* aName)
   {
     Scalar(aName, "null");
   }
 
@@ -391,29 +431,43 @@ public:
     QuotedScalar(aName, buf);
   }
 
   // Prints: "<aPtr>"
   // The pointer is printed as a hexadecimal integer with a leading '0x'.
   void PointerElement(const void* aPtr) { PointerProperty(nullptr, aPtr); }
 
   // Prints: "<aName>": [
-  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: "<aName>": {
-  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("}"); }
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_JSONWriter_h */
--- a/mfbt/tests/TestJSONWriter.cpp
+++ b/mfbt/tests/TestJSONWriter.cpp
@@ -68,40 +68,44 @@ void TestBasicProperties()
  \"double4\": 1.1111111111111111e+21,\n\
  \"string1\": \"\",\n\
  \"string2\": \"1234\",\n\
  \"string3\": \"hello\",\n\
  \"string4\": \"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
  \"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\
  \"len 5 object\": {\n\
   \"one\": 1,\n\
   \"two\": 2,\n\
   \"three\": 3,\n\
   \"four\": 4,\n\
   \"five\": 5\n\
- }\n\
+ },\n\
+ \"len 3 object, single-line\": {\"a\": 1, \"b\": [{}, 2, []], \"c\": 3}\n\
 }\n\
 ";
 
   JSONWriter w(MakeUnique<StringWriteFunc>());
 
   w.Start();
   {
     w.NullProperty("null");
@@ -122,36 +126,60 @@ void TestBasicProperties()
     w.StringProperty("string2", "1234");
     w.StringProperty("string3", "hello");
     w.StringProperty("string4", "\" \\ \a \b \t \n \v \f \r");
 
     w.PointerProperty("ptr1", (void*)0x0);
     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");
     {
       w.IntElement(1);
     }
     w.EndArray();
 
-    w.StartArrayProperty("len 5 array");
+    w.StartArrayProperty("len 5 array, multi-line", w.MultiLineStyle);
     {
       w.IntElement(1);
       w.IntElement(2);
       w.IntElement(3);
       w.IntElement(4);
       w.IntElement(5);
     }
     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");
     {
       w.IntProperty("one", 1);
     }
     w.EndObject();
 
@@ -159,16 +187,34 @@ void TestBasicProperties()
     {
       w.IntProperty("one", 1);
       w.IntProperty("two", 2);
       w.IntProperty("three", 3);
       w.IntProperty("four", 4);
       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();
 
   Check(w.WriteFunc(), expected);
 }
 
 void TestBasicElements()
 {
@@ -189,38 +235,42 @@ void TestBasicElements()
   \"1234\",\n\
   \"hello\",\n\
   \"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
   \"0x0\",\n\
   \"0xdeadbeef\",\n\
   \"0xfacade\",\n\
   [\n\
   ],\n\
+  [],\n\
   [\n\
    1\n\
   ],\n\
   [\n\
    1,\n\
    2,\n\
    3,\n\
    4,\n\
    5\n\
   ],\n\
+  [1, [{}, 2, []], 3],\n\
   {\n\
   },\n\
+  {},\n\
   {\n\
    \"one\": 1\n\
   },\n\
   {\n\
    \"one\": 1,\n\
    \"two\": 2,\n\
    \"three\": 3,\n\
    \"four\": 4,\n\
    \"five\": 5\n\
-  }\n\
+  },\n\
+  {\"a\": 1, \"b\": [{}, 2, []], \"c\": 3}\n\
  ]\n\
 }\n\
 ";
 
   JSONWriter w(MakeUnique<StringWriteFunc>());
 
   w.Start();
   w.StartArrayProperty("array");
@@ -246,57 +296,140 @@ void TestBasicElements()
 
     w.PointerElement((void*)0x0);
     w.PointerElement((void*)0xdeadbeef);
     w.PointerElement((void*)0xFaCaDe);
 
     w.StartArrayElement();
     w.EndArray();
 
+    w.StartArrayElement(w.SingleLineStyle);
+    w.EndArray();
+
     w.StartArrayElement();
     {
       w.IntElement(1);
     }
     w.EndArray();
 
     w.StartArrayElement();
     {
       w.IntElement(1);
       w.IntElement(2);
       w.IntElement(3);
       w.IntElement(4);
       w.IntElement(5);
     }
     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);
     }
     w.EndObject();
 
     w.StartObjectElement();
     {
       w.IntProperty("one", 1);
       w.IntProperty("two", 2);
       w.IntProperty("three", 3);
       w.IntProperty("four", 4);
       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();
 
   Check(w.WriteFunc(), expected);
 }
 
+void TestOneLineObject()
+{
+  const char* expected = "\
+{\"i\": 1, \"array\": [null, [{}], {\"o\": {}}, \"s\"], \"d\": 3.33}\n\
+";
+
+  JSONWriter w(MakeUnique<StringWriteFunc>());
+
+  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
   // problems for some compilers (see bug 1069726).
   const char* expected = "\
 {\n\
  \"ascii\": \"\x7F~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\\\"! \\u001f\\u001e\\u001d\\u001c\\u001b\\u001a\\u0019\\u0018\\u0017\\u0016\\u0015\\u0014\\u0013\\u0012\\u0011\\u0010\\u000f\\u000e\\r\\f\\u000b\\n\\t\\b\\u0007\\u0006\\u0005\\u0004\\u0003\\u0002\\u0001\",\n\
  \"\xD9\x85\xD8\xB1\xD8\xAD\xD8\xA8\xD8\xA7 \xD9\x87\xD9\x86\xD8\xA7\xD9\x83\": true,\n\
@@ -407,13 +540,14 @@ void TestDeepNesting()
 
   Check(w.WriteFunc(), expected);
 }
 
 int main(void)
 {
   TestBasicProperties();
   TestBasicElements();
+  TestOneLineObject();
   TestStringEscaping();
   TestDeepNesting();
 
   return 0;
 }