• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
2 // 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 
6 #if defined(__GNUC__)
7 #pragma GCC diagnostic push
8 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
9 #elif defined(_MSC_VER)
10 #pragma warning(disable : 4996)
11 #endif
12 
13 /* This executable is used for testing parser/writer using real JSON files.
14  */
15 
16 #include <algorithm> // sort
17 #include <cstdio>
18 #include <iostream>
19 #include <json/json.h>
20 #include <memory>
21 #include <sstream>
22 
23 struct Options {
24   Json::String path;
25   Json::Features features;
26   bool parseOnly;
27   using writeFuncType = Json::String (*)(Json::Value const&);
28   writeFuncType write;
29 };
30 
normalizeFloatingPointStr(double value)31 static Json::String normalizeFloatingPointStr(double value) {
32   char buffer[32];
33   jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value);
34   buffer[sizeof(buffer) - 1] = 0;
35   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 =
39         (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0;
40     Json::String::size_type exponentStartIndex = index + 1 + hasSign;
41     Json::String normalized = s.substr(0, exponentStartIndex);
42     Json::String::size_type indexDigit =
43         s.find_first_not_of('0', exponentStartIndex);
44     Json::String exponent = "0";
45     if (indexDigit != Json::String::npos) // There is an exponent different
46                                           // from 0
47     {
48       exponent = s.substr(indexDigit);
49     }
50     return normalized + exponent;
51   }
52   return s;
53 }
54 
readInputTestFile(const char * path)55 static Json::String readInputTestFile(const char* path) {
56   FILE* file = fopen(path, "rb");
57   if (!file)
58     return "";
59   fseek(file, 0, SEEK_END);
60   auto const size = ftell(file);
61   auto const usize = static_cast<size_t>(size);
62   fseek(file, 0, SEEK_SET);
63   auto buffer = new char[size + 1];
64   buffer[size] = 0;
65   Json::String text;
66   if (fread(buffer, 1, usize, file) == usize)
67     text = buffer;
68   fclose(file);
69   delete[] buffer;
70   return text;
71 }
72 
printValueTree(FILE * fout,Json::Value & value,const Json::String & path=".")73 static void printValueTree(FILE* fout, Json::Value& value,
74                            const Json::String& path = ".") {
75   if (value.hasComment(Json::commentBefore)) {
76     fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str());
77   }
78   switch (value.type()) {
79   case Json::nullValue:
80     fprintf(fout, "%s=null\n", path.c_str());
81     break;
82   case Json::intValue:
83     fprintf(fout, "%s=%s\n", path.c_str(),
84             Json::valueToString(value.asLargestInt()).c_str());
85     break;
86   case Json::uintValue:
87     fprintf(fout, "%s=%s\n", path.c_str(),
88             Json::valueToString(value.asLargestUInt()).c_str());
89     break;
90   case Json::realValue:
91     fprintf(fout, "%s=%s\n", path.c_str(),
92             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());
102     Json::ArrayIndex size = value.size();
103     for (Json::ArrayIndex index = 0; index < size; ++index) {
104       static char buffer[16];
105       jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index);
106       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());
113     Json::String suffix = *(path.end() - 1) == '.' ? "" : ".";
114     for (const auto& name : members) {
115       printValueTree(fout, value[name], path + suffix + name);
116     }
117   } break;
118   default:
119     break;
120   }
121 
122   if (value.hasComment(Json::commentAfter)) {
123     fprintf(fout, "%s\n", value.getComment(Json::commentAfter).c_str());
124   }
125 }
126 
parseAndSaveValueTree(const Json::String & input,const Json::String & actual,const Json::String & kind,const Json::Features & features,bool parseOnly,Json::Value * root,bool use_legacy)127 static int parseAndSaveValueTree(const Json::String& input,
128                                  const Json::String& actual,
129                                  const Json::String& kind,
130                                  const Json::Features& features, bool parseOnly,
131                                  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     }
163   }
164 
165   if (!parseOnly) {
166     FILE* factual = fopen(actual.c_str(), "wt");
167     if (!factual) {
168       std::cerr << "Failed to create '" << kind << "' actual file."
169                 << std::endl;
170       return 2;
171     }
172     printValueTree(factual, *root);
173     fclose(factual);
174   }
175   return 0;
176 }
177 // static Json::String useFastWriter(Json::Value const& root) {
178 //   Json::FastWriter writer;
179 //   writer.enableYAMLCompatibility();
180 //   return writer.write(root);
181 // }
useStyledWriter(Json::Value const & root)182 static Json::String useStyledWriter(Json::Value const& root) {
183   Json::StyledWriter writer;
184   return writer.write(root);
185 }
useStyledStreamWriter(Json::Value const & root)186 static Json::String useStyledStreamWriter(Json::Value const& root) {
187   Json::StyledStreamWriter writer;
188   Json::OStringStream sout;
189   writer.write(sout, root);
190   return sout.str();
191 }
useBuiltStyledStreamWriter(Json::Value const & root)192 static Json::String useBuiltStyledStreamWriter(Json::Value const& root) {
193   Json::StreamWriterBuilder builder;
194   return Json::writeString(builder, root);
195 }
rewriteValueTree(const Json::String & rewritePath,const Json::Value & root,Options::writeFuncType write,Json::String * rewrite)196 static int rewriteValueTree(const Json::String& rewritePath,
197                             const Json::Value& root,
198                             Options::writeFuncType write,
199                             Json::String* rewrite) {
200   *rewrite = write(root);
201   FILE* fout = fopen(rewritePath.c_str(), "wt");
202   if (!fout) {
203     std::cerr << "Failed to create rewrite file: " << rewritePath << std::endl;
204     return 2;
205   }
206   fprintf(fout, "%s\n", rewrite->c_str());
207   fclose(fout);
208   return 0;
209 }
210 
removeSuffix(const Json::String & path,const Json::String & extension)211 static Json::String removeSuffix(const Json::String& path,
212                                  const Json::String& extension) {
213   if (extension.length() >= path.length())
214     return Json::String("");
215   Json::String suffix = path.substr(path.length() - extension.length());
216   if (suffix != extension)
217     return Json::String("");
218   return path.substr(0, path.length() - extension.length());
219 }
220 
printConfig()221 static void printConfig() {
222 // Print the configuration used to compile JsonCpp
223 #if defined(JSON_NO_INT64)
224   std::cout << "JSON_NO_INT64=1" << std::endl;
225 #else
226   std::cout << "JSON_NO_INT64=0" << std::endl;
227 #endif
228 }
229 
printUsage(const char * argv[])230 static int printUsage(const char* argv[]) {
231   std::cout << "Usage: " << argv[0] << " [--strict] input-json-file"
232             << std::endl;
233   return 3;
234 }
235 
parseCommandLine(int argc,const char * argv[],Options * opts)236 static int parseCommandLine(int argc, const char* argv[], Options* opts) {
237   opts->parseOnly = false;
238   opts->write = &useStyledWriter;
239   if (argc < 2) {
240     return printUsage(argv);
241   }
242   int index = 1;
243   if (Json::String(argv[index]) == "--json-checker") {
244     opts->features = Json::Features::strictMode();
245     opts->parseOnly = true;
246     ++index;
247   }
248   if (Json::String(argv[index]) == "--json-config") {
249     printConfig();
250     return 3;
251   }
252   if (Json::String(argv[index]) == "--json-writer") {
253     ++index;
254     Json::String const writerName(argv[index++]);
255     if (writerName == "StyledWriter") {
256       opts->write = &useStyledWriter;
257     } else if (writerName == "StyledStreamWriter") {
258       opts->write = &useStyledStreamWriter;
259     } else if (writerName == "BuiltStyledStreamWriter") {
260       opts->write = &useBuiltStyledStreamWriter;
261     } else {
262       std::cerr << "Unknown '--json-writer' " << writerName << std::endl;
263       return 4;
264     }
265   }
266   if (index == argc || index + 1 < argc) {
267     return printUsage(argv);
268   }
269   opts->path = argv[index];
270   return 0;
271 }
272 
runTest(Options const & opts,bool use_legacy)273 static int runTest(Options const& opts, bool use_legacy) {
274   int exitCode = 0;
275 
276   Json::String input = readInputTestFile(opts.path.c_str());
277   if (input.empty()) {
278     std::cerr << "Invalid input file: " << opts.path << std::endl;
279     return 3;
280   }
281 
282   Json::String basePath = removeSuffix(opts.path, ".json");
283   if (!opts.parseOnly && basePath.empty()) {
284     std::cerr << "Bad input path '" << opts.path
285               << "'. Must end with '.expected'" << std::endl;
286     return 3;
287   }
288 
289   Json::String const actualPath = basePath + ".actual";
290   Json::String const rewritePath = basePath + ".rewrite";
291   Json::String const rewriteActualPath = basePath + ".actual-rewrite";
292 
293   Json::Value root;
294   exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
295                                    opts.parseOnly, &root, use_legacy);
296   if (exitCode || opts.parseOnly) {
297     return exitCode;
298   }
299 
300   Json::String rewrite;
301   exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
302   if (exitCode) {
303     return exitCode;
304   }
305 
306   Json::Value rewriteRoot;
307   exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
308                                    opts.features, opts.parseOnly, &rewriteRoot,
309                                    use_legacy);
310 
311   return exitCode;
312 }
313 
main(int argc,const char * argv[])314 int main(int argc, const char* argv[]) {
315   Options opts;
316   try {
317     int exitCode = parseCommandLine(argc, argv, &opts);
318     if (exitCode != 0) {
319       std::cerr << "Failed to parse command-line." << std::endl;
320       return exitCode;
321     }
322 
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     }
334   } catch (const std::exception& e) {
335     std::cerr << "Unhandled exception:" << std::endl << e.what() << std::endl;
336     return 1;
337   }
338   return 0;
339 }
340 
341 #if defined(__GNUC__)
342 #pragma GCC diagnostic pop
343 #endif
344