0

I am trying to format a JSON string avoiding using fmt::format because json does include {0}'s and fmt cannot distinguish JSON's bracket from format specifier {0}'s brackets. If use fmt::format i get the following error:

terminate called after throwing an instance of 'fmt::v7::format_error'
  what():  invalid format string
Aborted (core dumped)

This is why I try and use fmt::sprintf. I could not find a helpful solution for c++ but i've seen a Golang solution but it did not work which is: fmt.Sprintf("%[2]d %[1]d\n", 11, 22) (of course I've changed it to fmt::sprintf).

How can I give argument index to a format specifier using fmt::sprintf?

Thanks in advance.

EDIT:

JSONs are not generated by me but sqlite. That json includes a {0} and it is not being formatted because of the outer JSON braces.

no more sigsegv
  • 468
  • 5
  • 17
  • 1
    `because i cannot escape all` why not? `I need the same thing but with fmt::sprintf()` I very much doubt you _need that_, anyway, [here's the docs](https://github.com/fmtlib/fmt/blob/00f3d16b128b1b866c8fd6555a2d753c1186d622/doc/api.rst#printf-formatting) and [her's posix docs](https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html) and you are searching for `%1d`. – KamilCuk May 06 '21 at 08:17
  • @KamilCuk Read the question carefully (including error message) and you will understand why. – no more sigsegv May 06 '21 at 08:18
  • I do not understand, how does it change anything? So escape them. *and you are searching for `%1$d`. – KamilCuk May 06 '21 at 08:20
  • Curly braces are not generated by me but sqlite, I will never know how many of them. – no more sigsegv May 06 '21 at 08:23
  • How does that change anything? So iterate _for_ each of the curly braces and replace them with escaped one. You could even just use `std::regex_replace(...., "{", "\\{")` the end. – KamilCuk May 06 '21 at 08:27
  • JSONs contain curly braces and fmt::format use curly braces as format specifier. If you do know something that i don't, stop asking questions that take us nowhere, and be constructive. What you are doing is not helpful. However, if you are actually asking these questions to understand the issue, please do not try and respond to questions that you are not qualified to do so. – no more sigsegv May 06 '21 at 08:29
  • I will. I just suggest to replace all `{` to `\\{` irrelevant of how many there are. – KamilCuk May 06 '21 at 08:31
  • Then how am i supposed to distinguish outer bracket from inner bracket in this case:`{"iq":{0}}` ? I do not want to escape inner one. – no more sigsegv May 06 '21 at 08:34
  • 2
    @KamilCuk: fmt uses `{{` to escape braces, not `\{`... – user1810087 May 06 '21 at 08:35
  • Well, `%1$d` did it. I do not want to use regex or anything else that fmt can already do for the sake of readability and clarity. Thanks – no more sigsegv May 06 '21 at 08:42
  • 1
    The issue has nothing to do with fmt::format or fmt::sprintf, but rather with JSON and FMT using the same token. I don't think you can solve this easily by using fmt::sprintf. JSON parser would try to parse `{0}` just like FMT would try to parse `{"iq"....}`. – user1810087 May 06 '21 at 08:56
  • @user1810087 you are the only one who gets what the issue is here. But it can be solved with fmt::sprintf. – no more sigsegv May 06 '21 at 08:59

3 Answers3

1
std::string json = fmt::format("{{ \"key\": [{}, {}] }}", 42, 17);
std::cout << json << std::endl; // prints { "key": [17, 42] }

Demo: https://godbolt.org/z/4KjTPq4T5

The convention fmt chose for escaping curly brackets is {{ and }}. A backslash, e.g. \{, won't work. While it's common to see double bracketing for string interpolation, e.g. {{ some_var }}, the use of double bracketing as an escape sequence is somewhat unique to cpp (see cppformat).

D.Deriso
  • 4,271
  • 2
  • 21
  • 14
0

I sometimes use fmt to format small JSON snippets too. The other comments about the pitfalls of doing this are valid, but sometimes it's not a critical component and it gets the job done.

From the fmt documentation:

https://fmt.dev/latest/api.html#printf-api

The header fmt/printf.h provides printf-like formatting functionality. The following functions use printf format string syntax with the POSIX extension for positional arguments

That positional argument extension is described here:

https://en.wikipedia.org/wiki/Printf_format_string#Parameter_field

You can %n$... instead of %..., with n repaced with the argument you'd like to use.

For example:

std::string json = fmt::sprintf(R"({ "key": [%2$d, %1$d] })", 42, 17);
std::cout << json << std::endl; // prints { "key": [17, 42] }

Demo: https://godbolt.org/z/9dd734hxG

parktomatomi
  • 3,851
  • 1
  • 14
  • 18
-1

You question isn't very clear if I have to be honest, but I am almost sure this is a classic example of an XY problem.

IMHO, the main mistake is is that you are trying to format JSON by hand, which is by experience quite tricky and error prone.

Unless you are actually operating on a very small and strict set of inputs, there's an almost 100% certainty you will generate malformed JSON outputs in some cases. For instance, you have to also remember that all strings should be valid UTF-8, which means you have to either check your inputs or escape any weird character they might contain. You also have to escape anything that is not valid in a JSON string, such as ", like specified by the JSON standard.

This is why I think generating JSON by hand should only be done as a solution of last resort, i.e. if you have an embedded device and you can't afford to use any kind of library due to extremely severe memory constraints.

One library I often recommend is nlohmann/json, which might not be the fastest around1 but it is definitely one of the easiest to use. This is mostly due to it being header-only library and the fact that nlohmann::json is compatible with STL algorithms and iterators (it basically behaves like a std::map or vector).

You can then do very neat things, such as:

#include <iostream>
#include <string_view>
#include <unordered_set>

#include <nlohmann/json.hpp>

int main() {
    const std::unordered_set<std::string_view> s { "a", "b", "c" };

    nlohmann::json jarr {};

    for (const auto sv : s) {
        jarr.push_back(sv);
    }

    nlohmann::json jv { { "values", std::move(jarr) } };

    std::cout << jv.dump() << '\n';

    return 0;
}

This prints {"values":["c","b","a"]}.

You can even straight out serialize and deserialize arbitrary types (even user defined ones) as a JSON:

#include <iostream>
#include <string_view>
#include <unordered_set>

#include <nlohmann/json.hpp>

int main() {
    const std::unordered_set<std::string_view> s { "a", "b", "c" };

    nlohmann::json jv { { "values", s } };

    std::cout << jv.dump() << '\n';

    return 0;
}

1: In my experience, unless you must parse MILLIONS of JSONs per second, most performance considerations become moot. I've seen nlohmann's JSON library in use on the ESP32 and even there it was fast enough for light to moderate JSON parsing and generation.

mcilloni
  • 693
  • 5
  • 9
  • I do use nlohmann and this has nothing to do with nlohmann because no parsing or serialization involved. I need to format only {0}'s in the JSON generated by sqlite and send it. – no more sigsegv May 06 '21 at 09:00
  • @grizzly As I have said at the beginning of my answer, your question was really unclear, you never mentioned SQLite in the first place. – mcilloni May 06 '21 at 09:15
  • At least i said that they are not generated by me, and I've mentioned it in one of my comments above. Thanks for your effort anyway. – no more sigsegv May 06 '21 at 09:17
  • By the way, you should _never_ use arbitrary strings as formats for either fmt::format or std::printf or whatever, it's a security risk. If the `{` are not in the format but in one string you are trying to use as an argument, they will not cause any trouble to `{fmt}`. If you are trying to edit the JSON returned by SQLite in any meaningful form you should parse it first, edit it and then re-serialize it again, otherwise the likelihood of it being unsound becomes extremely high. – mcilloni May 06 '21 at 09:19
  • It is especially important to remember that doing sed or regex substitutions on JSONs or XMLs or such is very fragile and will _definitely_ bite you in the back. Always use a parser if you have to do anything even remotely serious with that data (i.e., if you are not simply trying to `grep` what .json files contain a certain pattern) – mcilloni May 06 '21 at 09:22
  • If SQLite is generating malformed JSON (i.e. JSON that contains random `{0}` or such) it's a different topic, and that's definitely a code smell. – mcilloni May 06 '21 at 09:24
  • No I intentionally make a query to have a JSON including {0}, so that I can format it in my application before I send it. Here is a simple example: `select json_object('a','{0}')` and it will return `{"a":"{0}"}`. Then I will format that {0} to something. – no more sigsegv May 06 '21 at 09:28
  • @grizzly Don't try to format a string representation of your whole JSON. Parse the output of your database into a json object, find the placeholder value, assign to it, then create a new string representation of that (if neccecary) – Caleth May 06 '21 at 09:31
  • Then parse that JSON with nlohmann, find which key has a value containing "{0}" and then fill it with whatever you need it to be filled. You can then dump that to a string and get a JSON you are certain it has not been corrupted. – mcilloni May 06 '21 at 09:32
  • This also makes the sentence "you are trying to format JSON by hand" correct, because that is exactly what you are doing. You should always operate on JSON data using a JSON library. – mcilloni May 06 '21 at 09:34
  • Ok I will parse, change then serialize. I did not think it would get so complicated. Thanks. – no more sigsegv May 06 '21 at 09:36
  • 1
    @grizzly you are welcome. It is complicated, because JSON is a well defined serialization language with precise semantics and rules, and so you can't operate on it safely with just string operations. – mcilloni May 06 '21 at 09:37