Skip to content

Please add a Pretty-Print option for arrays to stay always in one line #229

Description

@mireiner

Please add a Pretty-Printing option for arrays to stay always in one line (don't add lines) if dump() parameter > -1 or std::setw()) is set. So that only other JSON types than arrays are expanded.

Example:

json j;
j["person"]["name"] = "Glory Rainbow";
j["person"]["age"] = 61;
j["person"]["mind"] = "Easy going";
j["color"]["month"] = { 1, 3, 5, 7, 8, 10, 12};
j["color"]["order"] = {"red", "orange", "yellow", "green", "blue", "indigo", "violet"};
std::cout << j.dump(4);

Wanted output:

{
    "color": {
        "month":  [1, 3, 5, 7, 8, 10, 12],
        "order": ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
    } ,
    "person": {
        "age": 61,
        "name": "Glory Rainbow",
        "mind": "Easy going"
    }
} 

Thank you!

Activity

  1. changed the title [-]Please add an Pretty-Printing option for arrays to stay always in one line[/-] [+]Please add a Pretty-Printing option for arrays to stay always in one line[/+] on Apr 4, 2016
  2. changed the title [-]Please add a Pretty-Printing option for arrays to stay always in one line[/-] [+]Please add a Pretty-Print option for arrays to stay always in one line[/+] on Apr 4, 2016
  3. arnaudbrejeon commented on Apr 29, 2016

    @arnaudbrejeon

    I would really like to be able to improve the dump output, too.
    I don't have an exact idea on how to do it. I think the arrays on one-line is a good solution.

  4. nlohmann commented on Apr 29, 2016

    @nlohmann
    Owner

    Let's assume that the implementation is not the issue - how would the interface to the dump function look like? Like https://docs.python.org/2/library/json.html#json.dump ?

  5. arnaudbrejeon commented on Apr 30, 2016

    @arnaudbrejeon

    I'm not convinced by having so many parameters.
    I wonder if the dumps should not be performed by a JsonDumper helper class. We could provide a couple of built-ins:

    • denseDumper : never add any line breaking or space, just make the json as compact as possible
    • expandDumper : same as current dump, with indent_step as a parameter

    and users could inherit from JsonDumper to create their own specific dumpers.

    It has the advantage of providing basic dump for people don't want worry much about dumping format and letting customize easily for the others.

  6. nlohmann commented on Apr 30, 2016

    @nlohmann
    Owner

    I currently see two approaches to make the serialization more configurable:

    Configurable Separators

    One way would be to make the characters that structure a JSON value configurable these characters are

    • "," to seperate values
    • ":" to seperate keys from values
    • "[" and "]" for arrays
    • "{" and "}" for objects

    The idea would be to configure the serialization by adding whitespace to these characters. Take the following value as example:

    {
        "foo": 1,
        "bar": {
            "baz": 2,
            "fob": 3
        },
        "nop": [1, 2, 3, 4]
    }

    For the default values (no whitespace), the result would be:

    {"foo":1,"bar":{"baz":2,"fob":3},"nop":[1,2,3,4]}

    Now let's assume the key/value seperator is set to " : ". Then we'd have:

    {"foo" : 1,"bar" : {"baz" : 2,"fob" : 3},"nop" : [1,2,3,4]}

    Similarly, we could change the value separator to ", ":

    {"foo":1, "bar":{"baz":2, "fob":3}, "nop":[1, 2, 3, 4]}

    We could also think of treating the value seperator differently when used inside an object or an array. For instance, let's assume we want array values to be separated by ",", but object values to be separated by ", ":

    {"foo":1, "bar":{"baz":2, "fob":3}, "nop":[1,2,3,4]}

    Newlines would be possible, too, but the library would add indentation:

    Assume the object would be enclosed by "{\n" and "\n}", object values to be separated by ",\n", and array values to be separated by ", ":

    {
        "foo":1,
        "bar":{
            "baz":2,
            "fob":3
        },
        "nop":[1, 2, 3, 4]
    }

    Here, the indentation would be increased for opening braces (i.e., after the newline in "{\n"), decreased for closing braces (i.e., after the newline in "\n}"), and kept as is for all other newlines.

    This approach would be very flexible as it would make more or less all aspects of the serialization configurable. Additional configuration items could be:

    • Spaces before/after the quotes in string values ("foo" vs "foo")?
    • The format of empty arrays ([] vs [ ]) and objects?

    I have no idea yet how to make a nice interface for such a dump() function, but I think the idea should be clear.

    Styles

    Another approach could be to define a small number of presets to choose from. That is, choose names for reasonable parameters, for instance "compact" or "pretty" for the current versions, but also other presets for things like "no newline after comma", etc.

  7. arnaudbrejeon commented on Apr 30, 2016

    @arnaudbrejeon

    I like both ideas.
    Maybe the configurable separators could be stored in a struct, and styles would be hard-coded values of the structure.

    Something like:

    struct DumpStyle {
        std::string objectValueSeparator;
        std::string objectKeySeparator;
        std::string arraySeparator;
        size_t indentation;
        ....
    };
    
    const DumpStyle compact = { ",", ":", ",", 0, ... };
    
    string_t dump(const DumpStyle style);
    
  8. mireiner commented on May 3, 2016

    @mireiner
    Author

    Don't give to much on my words, because I'm a JSON beginner and use it only to store single variables and arrays to JSON configuration files for a single application. Therefore I use exclusively 'dump(4)' for pretty printing.

    Except from arrays I'm happy with the actual pretty printing. Because my configuration file got many arrays (with 20 and more items) the pretty printed multiline arrays are looking confusing to my eyes now. Even more on 16:9 monitors with its small vertical screen height. So I would be glad to have the option to pretty print arrays in a single line.

    For my needs it would be sufficient to have a second 'style' parameter for 'dump()' with a variety of presets. This 'style' parameter for 'dump()' could be implemented as a standard parameter which can be omitted if it is not needed. And not using it 'dump()' should perform as it was before. So written code with dump() in it can be left unchanged.

  9. arnaudbrejeon commented on May 8, 2016

    @arnaudbrejeon

    Here is a first implementation based on the existing dump:

      struct dump_parameters {
        const std::string object_start = "{";
        const std::string object_end = "}";
        const std::string object_key_sep = ": ";
        const std::string object_value_sep = ",";
        const std::string object_empty = "";
        const unsigned int object_newline_indent = 3;
    
        const std::string array_start = "[";
        const std::string array_end = "]";
        const std::string array_sep = ", ";
        const std::string array_empty = "";
        const unsigned int array_newline_indent = 3;
    
        unsigned int current_indent = 0;
      };
    
      const dump_parameters compact = {"{", "}", ":", ",", "", 0,
                                       "[", "]", ",", "",  0,  0};
      const dump_parameters pretty = {"{", "}", ": ", ",", "", 3,
                                      "[", "]", ", ", "",  3,  0};
      const dump_parameters array_oneliner = {"{", "}", ": ", ",", "", 3,
                                              "[", "]", ", ", "",  0,  0};
    
      string_t dump(const dump_parameters &param) const {
        auto ss = std::stringstream();
        dump(ss, param);
        return ss.str();
      }
    
      void dump(std::ostream &o, const dump_parameters &param) const {
        // variable to hold indentation for recursive calls
        auto new_indent = param.current_indent;
    
        switch (m_type) {
        case value_t::object: {
          assert(m_value.object != nullptr);
    
          o << param.object_start;
    
          // increase indentation
          if (param.object_newline_indent > 0) {
            new_indent += param.object_newline_indent;
            o << "\n" << string_t(new_indent, ' ');
          }
    
          for (auto i = m_value.object->cbegin(); i != m_value.object->cend();
               ++i) {
            if (i != m_value.object->cbegin()) {
              o << param.object_value_sep;
              if (param.object_newline_indent > 0) {
                o << "\n" << string_t(new_indent, ' ');
              }
            }
            o << "\"" << escape_string(i->first) << "\"" << param.object_key_sep;
            auto new_param = param;
            new_param.current_indent = new_indent;
            i->second.dump(o, new_param);
          }
    
          if (m_value.object->empty()) {
            o << param.object_empty;
          }
    
          // decrease indentation
          if (param.object_newline_indent > 0) {
            new_indent -= param.object_newline_indent;
            o << "\n" << string_t(new_indent, ' ');
          }
    
          o << param.object_end;
          return;
        }
    
        case value_t::array: {
          assert(m_value.array != nullptr);
    
          o << param.array_start;
    
          // increase indentation
          if (param.array_newline_indent > 0) {
            new_indent += param.array_newline_indent;
            o << "\n" << string_t(new_indent, ' ');
          }
    
          for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) {
            if (i != m_value.array->cbegin()) {
              o << param.array_sep;
              if (param.array_newline_indent > 0) {
                o << "\n" << string_t(new_indent, ' ');
              }
            }
            auto new_param = param;
            new_param.current_indent = new_indent;
            i->dump(o, new_param);
          }
    
          if (m_value.array->empty()) {
            o << param.array_empty;
          }
    
          // decrease indentation
          if (param.array_newline_indent > 0) {
            new_indent -= param.array_newline_indent;
            o << "\n" << string_t(new_indent, ' ');
          }
    
          o << param.array_end;
          return;
        }
    
        case value_t::string: {
          assert(m_value.string != nullptr);
          o << string_t("\"") << escape_string(*m_value.string) << "\"";
          return;
        }
    
        case value_t::boolean: {
          o << (m_value.boolean ? "true" : "false");
          return;
        }
    
        case value_t::number_integer: {
          o << m_value.number_integer;
          return;
        }
    
        case value_t::number_unsigned: {
          o << m_value.number_unsigned;
          return;
        }
    
        case value_t::number_float: {
          // check if number was parsed from a string
          if (m_type.bits.parsed) {
            // check if parsed number had an exponent given
            if (m_type.bits.has_exp) {
              // buffer size: precision (2^8-1 = 255) + other ('-.e-xxx' = 7) + null
              // (1)
              char buf[263];
              int len;
    
              // handle capitalization of the exponent
              if (m_type.bits.exp_cap) {
                len = snprintf(buf, sizeof(buf), "%.*E", m_type.bits.precision,
                               m_value.number_float) +
                      1;
              } else {
                len = snprintf(buf, sizeof(buf), "%.*e", m_type.bits.precision,
                               m_value.number_float) +
                      1;
              }
    
              // remove '+' sign from the exponent if necessary
              if (not m_type.bits.exp_plus) {
                if (len > static_cast<int>(sizeof(buf))) {
                  len = sizeof(buf);
                }
                for (int i = 0; i < len; i++) {
                  if (buf[i] == '+') {
                    for (; i + 1 < len; i++) {
                      buf[i] = buf[i + 1];
                    }
                  }
                }
              }
    
              o << buf;
            } else {
              // no exponent - output as a decimal
              std::stringstream ss;
              ss.imbue(std::locale(std::locale(),
                                   new DecimalSeparator)); // fix locale problems
              ss << std::setprecision(m_type.bits.precision) << std::fixed
                 << m_value.number_float;
              o << ss.str();
            }
          } else {
            if (m_value.number_float == 0) {
              // special case for zero to get "0.0"/"-0.0"
              o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0");
            } else {
              // Otherwise 6, 15 or 16 digits of precision allows
              // round-trip IEEE 754 string->float->string,
              // string->double->string or string->long double->string;
              // to be safe, we read this value from
              // std::numeric_limits<number_float_t>::digits10
              std::stringstream ss;
              ss.imbue(std::locale(std::locale(),
                                   new DecimalSeparator)); // fix locale problems
              ss << std::setprecision(std::numeric_limits<double>::digits10)
                 << m_value.number_float;
              o << ss.str();
            }
          }
          return;
        }
    
        case value_t::discarded: {
          o << "<discarded>";
          return;
        }
    
        case value_t::null: {
          o << "null";
          return;
        }
        }
      }
    

    and the call:

      auto ifs = std::ifstream(filename);
      const auto json = nlohmann::json::parse(ifs);
      std::cout << json.dump(json.array_oneliner) << std::endl;
    
  10. nlohmann commented on May 8, 2016

    @nlohmann
    Owner

    Hi! I implemented my proposal. Please have a look at https://github.com/nlohmann/json/tree/feature/dump. File doc/examples/dump.cpp contains an example for a call with user-defined parameters.

  11. nlohmann commented on May 8, 2016

    @nlohmann
    Owner

    Hi @arnaudbrejeon, I only saw now that you also proposed a way to implement this. Though our approaches are similar, it seems that my realization (https://github.com/nlohmann/json/tree/feature/dump) is a bit more generic as it allows to select for each separating item whether or not to add newlines. It would be great if you could comment on this.

  12. arnaudbrejeon commented on May 8, 2016

    @arnaudbrejeon

    Hi,
    your implementation is indeed more generic than mine. It also looks better, as the code for dumping objects and arrays is much simpler now.
    From my perspective, it is very nice.

    The improvement I would suggest is that we could cache the result of the following code
    s.find('\n') == std::string::npos in the lambdas.
    I wonder if it can not lead to performance drop for large json files. Not 100% sure, but it would be interesting to measure.

  13. mireiner commented on May 9, 2016

    @mireiner
    Author

    Hi,
    I'm very glad the pretty-printing will be extended!

    But the options are complex and therefore missing the clarity nlohmann::json is popular for.

    For example is the option splitting for brackets in empty, open and closed really that usefull? Do we really need all these options in practice? Or does this complexity just lead to more confusion than it helps?

    Why not just use a small number of self explanatory presets (bit flags?) to choose from, like:

    object_single_line
    object_multi_line
    array_single_line
    array_multi_line
    indent_1
    indent_2
    indent_3
    indent_4
    spaced_0
    spaced_1

    For example using bit flags:
    dump(indent_4 || spaced_1 || object_multiline || array_single_line)

    This will print multiline objects, single line arrays, 1 space separated and 4 space indented. This is as clear as it could be. And the programmer can leave out all or just the preset flags he doesn't need.

    In contrast look at an array example of the current implementation. Can you predict the output? I wasn't able to do that and wondered about the outcome:

    #include "json.hpp"
    #include <set>
    
    using namespace std;
    using json = nlohmann::json;
    
    int main()
    {
        json JSON;
    
        std::set<int> foo = { 1,2,3,4 };
    
        JSON["array"] = foo;
    
        cout << JSON.dump(4) << "\n\n";
    
        json::printer pp2 = json::printer::pretty_printer();
        pp2.array_empty = "[]";
        cout << JSON.dump(4, pp2) << "\n\n";
    
        json::printer pp3 = json::printer::pretty_printer();
        pp3.array_comma = ", ";
        pp3.array_empty = "[]";
        cout << JSON.dump(4, pp3) << "\n\n";
    
        json::printer pp4 = json::printer::pretty_printer();
        pp4.array_open = "[";
        cout << JSON.dump(3, pp4) << "\n\n";
    
        json::printer pp5 = json::printer::pretty_printer();
        pp5.array_open = "]";
        cout << JSON.dump(3, pp5) << "\n\n";
    
        json::printer pp6 = json::printer::pretty_printer();
        pp6.array_open = "[";
        pp6.array_close = "]";
        cout << JSON.dump(3, pp6) << "\n\n";
    
        json::printer pp7 = json::printer::pretty_printer();
        pp7.array_comma = ", ";
        pp7.array_open = "[";
        pp7.array_close = "]";
        cout << JSON.dump(4, pp7) << "\n\n";
    
        json::printer pp8 = json::printer::pretty_printer();
        pp8.array_open = "[\n";
        cout << JSON.dump(4, pp8) << "\n\n";
    
        json::printer pp9 = json::printer::pretty_printer();
        pp9.array_comma = ", ";
        pp9.array_open = "[\n";
        pp9.array_close = "\n]";
        cout << JSON.dump(4, pp9) << "\n\n";
    
        return 0;
    }
    
    Output:
    
    // dump(4)
    {
        "array": [
            1,
            2,
            3,
            4
        ]
    }
    
    // array_empty = "[]";
    {
        "array": [
            1,
            2,
            3,
            4
        ]
    }
    
    // array_comma = ", ";
    // array_empty = "[]";
    {
        "array": [
            1, 2, 3, 4
        ]
    }
    
    // array_open = "[";
    {
       "array": [1,
       2,
       3,
       4
    ]
    }
    
    // array_open = "]";
    {
       "array": ]1,
       2,
       3,
       4
    ]
    }
    
    // array_open = "[";
    // array_close = "]";
    {
       "array": [1,
       2,
       3,
       4   ]
    }
    
    // array_comma = ", ";
    // array_open = "[";
    // array_close = "]";
    {
        "array": [1, 2, 3, 4]
    }
    
    // array_open = "[\n";
    {
        "array": [
            1,
            2,
            3,
            4
        ]
    }
    
    // array_comma = ", ";
    // array_open = "[\n";
    // array_close = "\n]";
    {
        "array": [
            1, 2, 3, 4
        ]
    }
  14. 47 remaining items

  15. emmenlau commented on Jul 4, 2022

    @emmenlau

    Dear @nlohmann and all, I just found this nice thread and think it should be resurrected. The improved dump() that @nlohmann proposed sounds very valuable, and I think having complex configuration for dumping is reasonable and not out of scope. It seems a good balance of user-comfort and maintenance-effort. It would also be a great starting point for users that want to go further with their configuration.

    In this thread I lost a bit track why an improved dump() was not merged to master. I understand it would not be a breaking change to add a new dump(config) method, and then maybe even implement the current dump(int) based on the new method (with defaults for most config values).

    @nlohmann are there other problems or blockers from proceeding with your nice suggestion? Anything I can do to help move this forward?

  16. sumitbeniwal commented on Jul 15, 2022

    @sumitbeniwal

    It's naive, but I think a general print behavior I want is: "Print the entire value on one line (number, string, array, object, etc). If a compound value would not fit within an X character rule width, recursively print each sub-value onto separate lines with the same logic."

    Exactly what I want too. Would make big jsons more readable.

  17. falbrechtskirchinger commented on Jul 15, 2022

    @falbrechtskirchinger
    Contributor

    @emmenlau If you're serious about contributing, I suggest you start a discussion.

    Ideally, you'd summarize the proposed solutions from existing issues and PRs, give your thoughts, and ask for requirements, uses cases, and other feedback. That summary alone would be incredibly useful for anyone willing to work towards getting a PR merged. I know I'd appreciate it.

    I favor creating an event-driven interface for dumping equivalent to sax_parse(). Both the existing dump() function and the proposed dump(config) function could then be implemented on top of this new interface and give people ultimate flexibility.
    For example, a user recently asked about serializing to XML, which would be trivial with such an interface.

  18. emmenlau commented on Jul 15, 2022

    @emmenlau

    Dear @falbrechtskirchinger ok I can now see how this is a bit more involved than what I hoped for. Your suggestion for the implementation sounds very good, but was rather hoping I could extend a bit the code of an existing PR, rather than start by gathering community feedback. While I very much agree that this is a valid path, it seems also beyond what I could stem.

    What I could not understand from this discussion: Why was the original PR from @nlohmann eventually dropped?

  19. nlohmann commented on Jul 17, 2022

    @nlohmann
    Owner

    IIRC I dropped the PR eventually, because it got more and more complex, yet only addressed some of the use cases discussed here.

    I also once tried the approach sketched by @falbrechtskirchinger to have an event-based API, but I did not manage to make it fast enough to be a replacement for the status quo. In the meantime, I think it's the way to move forward as it should be flexible enough to allow everybody to plug in their faviorite way of styling JSON.

  20. gregmarr commented on Jul 18, 2022

    @gregmarr
    Contributor

    I also once tried the approach sketched by @falbrechtskirchinger to have an event-based API, but I did not manage to make it fast enough to be a replacement for the status quo.

    I could see a case for supporting a "fast but not really customizable" dump() function and a "customize to your heart's content, but pay for it in performance" pretty() function if an event-based pretty() function can't be made to be comparable to the current dump() in performance. Then again, maybe pretty() is just a visit() (#1480) with something that creates JSON output.

  21. nlohmann commented on Jul 18, 2022

    @nlohmann
    Owner

    If we would be able to create a dump based on the SAX events, we may even be able to link any of the parsers with any of the serializers - including the binary formats. The SAX events should be sufficient for a pretty printer to keep track of the depth of nesting, etc.

  22. nlohmann commented on Jul 20, 2022

    @nlohmann
    Owner

    Let's discuss #3594.

  23. BKSpek commented on Mar 21, 2024

    @BKSpek

    For anyone looking for how to do it, just change one line in serializer.hpp dump function. If (pretty_print) { // pretty print array json... } else { // print normally }

    Change " if (pretty_print) " to " if (false && pretty_print) "

    Maybe add a 4rth parameter to dump that takes in a bitfield of types of elements to exclude from pretty print? Then you can do something like dump(4, ' ', false, JT_ARRAY | JT_...;

    Seems easy to implement and simple to use.

  24. Cleroth commented on Jun 23, 2024

    @Cleroth

    This is sorely needed.

  25. ZhuLingfeng1993 commented on Jan 22, 2025

    @ZhuLingfeng1993

    @nlohmann I find the https://github.com/nlohmann/json/tree/feature/dump branch is deleted, and seems the feature is not merge into main branch, why?

  26. nlohmann commented on Jan 22, 2025

    @nlohmann
    Owner

    I guess it belonged to a PR that eventually was not merged.

  27. patrikhuber commented on Apr 24, 2025

    @patrikhuber
    Contributor

    I'd also really like something like this. I'm serialising 2D matrices, and currently save each row as a json array. Using dump() results in:

    Image

    which is is not very human-readable (or editable) (and in fact might mislead the reader to think those are matrix columns, not rows). I'd rather have this:

    Image

    That would be good enough for me.

  28. alainsanguinetti commented on Apr 24, 2025

    @alainsanguinetti

    Would it be acceptable to define: pretty printing of an array of values is to print the values on the same line, pretty printing of an array of anything else means to indent each entry?

    This does what I want: Capture

    @patrikhuber I think this would work for your case as well

  29. patrikhuber commented on Apr 24, 2025

    @patrikhuber
    Contributor

    @alainsanguinetti Thank you. Though I'd prefer to stick to the nlohmann-json version in vcpkg, and not have to create an overlay / patch it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions