blob: dbfbe98c1dc28886c1f3cd418896075b513234af [file] [log] [blame]
Devin Jeanpierre59e4d352017-07-21 03:44:36 -07001// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
Baptiste Lepilleur7469f1d2010-04-20 21:35:19 +00002// Distributed under MIT license, or public domain if desired and
3// recognized in your jurisdiction.
4// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
5
Marcel Raad240c85a2017-11-10 10:58:43 +01006#if defined(__GNUC__)
Christopher Dunn90591c72017-08-28 08:38:29 -05007#pragma GCC diagnostic push
8#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Marcel Raad240c85a2017-11-10 10:58:43 +01009#elif defined(_MSC_VER)
10#pragma warning(disable : 4996)
11#endif
Christopher Dunn90591c72017-08-28 08:38:29 -050012
Baptiste Lepilleurfa130ef2010-12-24 12:47:14 +000013/* This executable is used for testing parser/writer using real JSON files.
14 */
15
Christopher Dunnf9864232007-06-14 21:01:26 +000016#include <algorithm> // sort
Billy Donahuedc4a7f92019-01-17 11:07:53 -050017#include <cstdio>
Jordan Bayles645cd042019-11-08 19:49:16 -080018#include <iostream>
Billy Donahueb5e1fe82018-05-20 16:55:27 -040019#include <json/json.h>
Jordan Bayles645cd042019-11-08 19:49:16 -080020#include <memory>
Christopher Dunn2160c9a2015-01-23 09:02:44 -060021#include <sstream>
Christopher Dunnf9864232007-06-14 21:01:26 +000022
Billy Donahueb5e1fe82018-05-20 16:55:27 -040023struct Options {
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050024 Json::String path;
Christopher Dunn3682f602015-01-23 11:46:05 -060025 Json::Features features;
26 bool parseOnly;
Billy Donahue2b593a92019-01-18 03:46:57 -050027 using writeFuncType = Json::String (*)(Json::Value const&);
Christopher Dunn3682f602015-01-23 11:46:05 -060028 writeFuncType write;
29};
30
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050031static Json::String normalizeFloatingPointStr(double value) {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100032 char buffer[32];
Hans Johnson5c8e5392018-12-12 13:31:55 -060033 jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value);
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100034 buffer[sizeof(buffer) - 1] = 0;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050035 Json::String s(buffer);
36 Json::String::size_type index = s.find_last_of("eE");
37 if (index != Json::String::npos) {
38 Json::String::size_type hasSign =
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100039 (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050040 Json::String::size_type exponentStartIndex = index + 1 + hasSign;
41 Json::String normalized = s.substr(0, exponentStartIndex);
Billy Donahue2b593a92019-01-18 03:46:57 -050042 Json::String::size_type indexDigit =
43 s.find_first_not_of('0', exponentStartIndex);
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050044 Json::String exponent = "0";
45 if (indexDigit != Json::String::npos) // There is an exponent different
Billy Donahue2b593a92019-01-18 03:46:57 -050046 // from 0
Baptiste Lepilleurf0b24e72011-05-26 20:14:32 +000047 {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100048 exponent = s.substr(indexDigit);
Baptiste Lepilleurf0b24e72011-05-26 20:14:32 +000049 }
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100050 return normalized + exponent;
51 }
52 return s;
Baptiste Lepilleurf0b24e72011-05-26 20:14:32 +000053}
54
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050055static Json::String readInputTestFile(const char* path) {
Aaron Jacobs11086dd2014-09-15 10:15:29 +100056 FILE* file = fopen(path, "rb");
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100057 if (!file)
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050058 return "";
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100059 fseek(file, 0, SEEK_END);
Christopher Dunnd4513fc2016-02-06 09:25:20 -060060 long const size = ftell(file);
Hans Johnson1fc3de72019-01-14 17:09:15 -060061 size_t const usize = static_cast<unsigned long>(size);
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100062 fseek(file, 0, SEEK_SET);
Aaron Jacobs11086dd2014-09-15 10:15:29 +100063 char* buffer = new char[size + 1];
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100064 buffer[size] = 0;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -050065 Json::String text;
Christopher Dunnd4513fc2016-02-06 09:25:20 -060066 if (fread(buffer, 1, usize, file) == usize)
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100067 text = buffer;
68 fclose(file);
69 delete[] buffer;
70 return text;
Christopher Dunnf9864232007-06-14 21:01:26 +000071}
72
Jordan Baylesf34bf242019-10-11 11:19:00 -070073static void printValueTree(FILE* fout, Json::Value& value,
74 const Json::String& path = ".") {
Cory Quammen4d234922014-10-09 16:29:47 -040075 if (value.hasComment(Json::commentBefore)) {
76 fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str());
77 }
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100078 switch (value.type()) {
79 case Json::nullValue:
80 fprintf(fout, "%s=null\n", path.c_str());
81 break;
82 case Json::intValue:
Billy Donahueb5e1fe82018-05-20 16:55:27 -040083 fprintf(fout, "%s=%s\n", path.c_str(),
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100084 Json::valueToString(value.asLargestInt()).c_str());
85 break;
86 case Json::uintValue:
Billy Donahueb5e1fe82018-05-20 16:55:27 -040087 fprintf(fout, "%s=%s\n", path.c_str(),
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100088 Json::valueToString(value.asLargestUInt()).c_str());
89 break;
90 case Json::realValue:
Billy Donahueb5e1fe82018-05-20 16:55:27 -040091 fprintf(fout, "%s=%s\n", path.c_str(),
Aaron Jacobs9fa4e842014-07-01 08:48:54 +100092 normalizeFloatingPointStr(value.asDouble()).c_str());
93 break;
94 case Json::stringValue:
95 fprintf(fout, "%s=\"%s\"\n", path.c_str(), value.asString().c_str());
96 break;
97 case Json::booleanValue:
98 fprintf(fout, "%s=%s\n", path.c_str(), value.asBool() ? "true" : "false");
99 break;
100 case Json::arrayValue: {
101 fprintf(fout, "%s=[]\n", path.c_str());
Christopher Dunnd4513fc2016-02-06 09:25:20 -0600102 Json::ArrayIndex size = value.size();
103 for (Json::ArrayIndex index = 0; index < size; ++index) {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000104 static char buffer[16];
Hans Johnson5c8e5392018-12-12 13:31:55 -0600105 jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index);
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000106 printValueTree(fout, value[index], path + buffer);
107 }
108 } break;
109 case Json::objectValue: {
110 fprintf(fout, "%s={}\n", path.c_str());
111 Json::Value::Members members(value.getMemberNames());
112 std::sort(members.begin(), members.end());
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500113 Json::String suffix = *(path.end() - 1) == '.' ? "" : ".";
Hans Johnsoncbeed7b2019-01-14 17:09:12 -0600114 for (auto name : members) {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000115 printValueTree(fout, value[name], path + suffix + name);
116 }
117 } break;
118 default:
119 break;
120 }
Cory Quammen4d234922014-10-09 16:29:47 -0400121
122 if (value.hasComment(Json::commentAfter)) {
123 fprintf(fout, "%s\n", value.getComment(Json::commentAfter).c_str());
124 }
Christopher Dunnf9864232007-06-14 21:01:26 +0000125}
126
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500127static int parseAndSaveValueTree(const Json::String& input,
128 const Json::String& actual,
129 const Json::String& kind,
Jordan Baylesf34bf242019-10-11 11:19:00 -0700130 const Json::Features& features, bool parseOnly,
Jordan Bayles645cd042019-11-08 19:49:16 -0800131 Json::Value* root, bool use_legacy) {
132 if (!use_legacy) {
133 Json::CharReaderBuilder builder;
134
135 builder.settings_["allowComments"] = features.allowComments_;
136 builder.settings_["strictRoot"] = features.strictRoot_;
137 builder.settings_["allowDroppedNullPlaceholders"] =
138 features.allowDroppedNullPlaceholders_;
139 builder.settings_["allowNumericKeys"] = features.allowNumericKeys_;
140
141 std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
142 Json::String errors;
143 const bool parsingSuccessful =
144 reader->parse(input.data(), input.data() + input.size(), root, &errors);
145
146 if (!parsingSuccessful) {
147 std::cerr << "Failed to parse " << kind << " file: " << std::endl
148 << errors << std::endl;
149 return 1;
150 }
151
152 // We may instead check the legacy implementation (to ensure it doesn't
153 // randomly get broken).
154 } else {
155 Json::Reader reader(features);
156 const bool parsingSuccessful =
157 reader.parse(input.data(), input.data() + input.size(), *root);
158 if (!parsingSuccessful) {
159 std::cerr << "Failed to parse " << kind << " file: " << std::endl
160 << reader.getFormatedErrorMessages() << std::endl;
161 return 1;
162 }
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000163 }
Jordan Bayles645cd042019-11-08 19:49:16 -0800164
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000165 if (!parseOnly) {
Aaron Jacobs11086dd2014-09-15 10:15:29 +1000166 FILE* factual = fopen(actual.c_str(), "wt");
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000167 if (!factual) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800168 std::cerr << "Failed to create '" << kind << "' actual file."
169 << std::endl;
Christopher Dunnf9864232007-06-14 21:01:26 +0000170 return 2;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000171 }
Christopher Dunn632c9b52015-01-23 11:09:04 -0600172 printValueTree(factual, *root);
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000173 fclose(factual);
174 }
175 return 0;
Christopher Dunnf9864232007-06-14 21:01:26 +0000176}
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500177// static Json::String useFastWriter(Json::Value const& root) {
Christopher Dunn79211e12015-01-23 11:27:19 -0600178// Json::FastWriter writer;
179// writer.enableYAMLCompatibility();
180// return writer.write(root);
181// }
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500182static Json::String useStyledWriter(Json::Value const& root) {
Christopher Dunn79211e12015-01-23 11:27:19 -0600183 Json::StyledWriter writer;
184 return writer.write(root);
185}
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500186static Json::String useStyledStreamWriter(Json::Value const& root) {
Christopher Dunn2160c9a2015-01-23 09:02:44 -0600187 Json::StyledStreamWriter writer;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500188 Json::OStringStream sout;
Christopher Dunn2160c9a2015-01-23 09:02:44 -0600189 writer.write(sout, root);
Christopher Dunn79211e12015-01-23 11:27:19 -0600190 return sout.str();
191}
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500192static Json::String useBuiltStyledStreamWriter(Json::Value const& root) {
Christopher Dunn6065a1c2015-01-26 11:01:15 -0600193 Json::StreamWriterBuilder builder;
Christopher Dunn694dbcb2015-02-09 15:25:57 -0600194 return Json::writeString(builder, root);
Christopher Dunn9243d602015-01-23 08:38:32 -0600195}
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500196static int rewriteValueTree(const Json::String& rewritePath,
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400197 const Json::Value& root,
198 Options::writeFuncType write,
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500199 Json::String* rewrite) {
Christopher Dunn3682f602015-01-23 11:46:05 -0600200 *rewrite = write(root);
Aaron Jacobs11086dd2014-09-15 10:15:29 +1000201 FILE* fout = fopen(rewritePath.c_str(), "wt");
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000202 if (!fout) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800203 std::cerr << "Failed to create rewrite file: " << rewritePath << std::endl;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000204 return 2;
205 }
Christopher Dunn632c9b52015-01-23 11:09:04 -0600206 fprintf(fout, "%s\n", rewrite->c_str());
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000207 fclose(fout);
208 return 0;
Christopher Dunnf9864232007-06-14 21:01:26 +0000209}
210
Billy Donahue2b593a92019-01-18 03:46:57 -0500211static Json::String removeSuffix(const Json::String& path,
212 const Json::String& extension) {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000213 if (extension.length() >= path.length())
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500214 return Json::String("");
215 Json::String suffix = path.substr(path.length() - extension.length());
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000216 if (suffix != extension)
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500217 return Json::String("");
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000218 return path.substr(0, path.length() - extension.length());
219}
Baptiste Lepilleur201fb2c2010-04-19 07:37:41 +0000220
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000221static void printConfig() {
222// Print the configuration used to compile JsonCpp
Baptiste Lepilleur201fb2c2010-04-19 07:37:41 +0000223#if defined(JSON_NO_INT64)
Jordan Bayles645cd042019-11-08 19:49:16 -0800224 std::cout << "JSON_NO_INT64=1" << std::endl;
Baptiste Lepilleur201fb2c2010-04-19 07:37:41 +0000225#else
Jordan Bayles645cd042019-11-08 19:49:16 -0800226 std::cout << "JSON_NO_INT64=0" << std::endl;
Baptiste Lepilleur201fb2c2010-04-19 07:37:41 +0000227#endif
228}
229
Aaron Jacobs11086dd2014-09-15 10:15:29 +1000230static int printUsage(const char* argv[]) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800231 std::cout << "Usage: " << argv[0] << " [--strict] input-json-file"
232 << std::endl;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000233 return 3;
Baptiste Lepilleur88681472009-11-18 21:38:54 +0000234}
235
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400236static int parseCommandLine(int argc, const char* argv[], Options* opts) {
Christopher Dunn79211e12015-01-23 11:27:19 -0600237 opts->parseOnly = false;
Christopher Dunn3682f602015-01-23 11:46:05 -0600238 opts->write = &useStyledWriter;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000239 if (argc < 2) {
240 return printUsage(argv);
241 }
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000242 int index = 1;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500243 if (Json::String(argv[index]) == "--json-checker") {
Christopher Dunn79211e12015-01-23 11:27:19 -0600244 opts->features = Json::Features::strictMode();
245 opts->parseOnly = true;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000246 ++index;
247 }
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500248 if (Json::String(argv[index]) == "--json-config") {
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000249 printConfig();
250 return 3;
251 }
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500252 if (Json::String(argv[index]) == "--json-writer") {
Christopher Dunn3682f602015-01-23 11:46:05 -0600253 ++index;
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500254 Json::String const writerName(argv[index++]);
Christopher Dunn3682f602015-01-23 11:46:05 -0600255 if (writerName == "StyledWriter") {
256 opts->write = &useStyledWriter;
257 } else if (writerName == "StyledStreamWriter") {
258 opts->write = &useStyledStreamWriter;
Christopher Dunn9243d602015-01-23 08:38:32 -0600259 } else if (writerName == "BuiltStyledStreamWriter") {
260 opts->write = &useBuiltStyledStreamWriter;
Christopher Dunn3682f602015-01-23 11:46:05 -0600261 } else {
Jordan Bayles645cd042019-11-08 19:49:16 -0800262 std::cerr << "Unknown '--json-writer' " << writerName << std::endl;
Christopher Dunn3682f602015-01-23 11:46:05 -0600263 return 4;
264 }
265 }
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000266 if (index == argc || index + 1 < argc) {
267 return printUsage(argv);
268 }
Christopher Dunn79211e12015-01-23 11:27:19 -0600269 opts->path = argv[index];
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000270 return 0;
271}
Jordan Bayles645cd042019-11-08 19:49:16 -0800272
273static int runTest(Options const& opts, bool use_legacy) {
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600274 int exitCode = 0;
275
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500276 Json::String input = readInputTestFile(opts.path.c_str());
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600277 if (input.empty()) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800278 std::cerr << "Invalid input file: " << opts.path << std::endl;
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600279 return 3;
280 }
281
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500282 Json::String basePath = removeSuffix(opts.path, ".json");
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600283 if (!opts.parseOnly && basePath.empty()) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800284 std::cerr << "Bad input path '" << opts.path
285 << "'. Must end with '.expected'" << std::endl;
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600286 return 3;
287 }
288
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500289 Json::String const actualPath = basePath + ".actual";
290 Json::String const rewritePath = basePath + ".rewrite";
291 Json::String const rewriteActualPath = basePath + ".actual-rewrite";
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600292
293 Json::Value root;
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400294 exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
Jordan Bayles645cd042019-11-08 19:49:16 -0800295 opts.parseOnly, &root, use_legacy);
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600296 if (exitCode || opts.parseOnly) {
297 return exitCode;
298 }
Jordan Bayles645cd042019-11-08 19:49:16 -0800299
Billy Donahue1c2ed7a2019-01-17 16:35:29 -0500300 Json::String rewrite;
Christopher Dunn3682f602015-01-23 11:46:05 -0600301 exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600302 if (exitCode) {
303 return exitCode;
304 }
Jordan Bayles645cd042019-11-08 19:49:16 -0800305
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600306 Json::Value rewriteRoot;
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400307 exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
Jordan Bayles645cd042019-11-08 19:49:16 -0800308 opts.features, opts.parseOnly, &rewriteRoot,
309 use_legacy);
310
311 return exitCode;
Christopher Dunn79211e12015-01-23 11:27:19 -0600312}
Jordan Bayles645cd042019-11-08 19:49:16 -0800313
Aaron Jacobs11086dd2014-09-15 10:15:29 +1000314int main(int argc, const char* argv[]) {
Christopher Dunn79211e12015-01-23 11:27:19 -0600315 Options opts;
Gaurav6c145482015-09-22 13:53:19 +0530316 try {
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400317 int exitCode = parseCommandLine(argc, argv, &opts);
318 if (exitCode != 0) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800319 std::cerr << "Failed to parse command-line." << std::endl;
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400320 return exitCode;
321 }
Jordan Bayles645cd042019-11-08 19:49:16 -0800322
323 const int modern_return_code = runTest(opts, false);
324 if (modern_return_code) {
325 return modern_return_code;
326 }
327
328 const std::string filename =
329 opts.path.substr(opts.path.find_last_of("\\/") + 1);
330 const bool should_run_legacy = (filename.rfind("legacy_", 0) == 0);
331 if (should_run_legacy) {
332 return runTest(opts, true);
333 }
Billy Donahueb5e1fe82018-05-20 16:55:27 -0400334 } catch (const std::exception& e) {
Jordan Bayles645cd042019-11-08 19:49:16 -0800335 std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl;
Christopher Dunn58c31ac2015-01-23 11:36:55 -0600336 return 1;
Aaron Jacobs9fa4e842014-07-01 08:48:54 +1000337 }
Christopher Dunnf9864232007-06-14 21:01:26 +0000338}
Christopher Dunn90591c72017-08-28 08:38:29 -0500339
Marcel Raad240c85a2017-11-10 10:58:43 +0100340#if defined(__GNUC__)
Christopher Dunn90591c72017-08-28 08:38:29 -0500341#pragma GCC diagnostic pop
Marcel Raad240c85a2017-11-10 10:58:43 +0100342#endif