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 <json/json.h>
19 #include <sstream>
20
21 struct Options {
22 Json::String path;
23 Json::Features features;
24 bool parseOnly;
25 using writeFuncType = Json::String (*)(Json::Value const&);
26 writeFuncType write;
27 };
28
normalizeFloatingPointStr(double value)29 static Json::String normalizeFloatingPointStr(double value) {
30 char buffer[32];
31 jsoncpp_snprintf(buffer, sizeof(buffer), "%.16g", value);
32 buffer[sizeof(buffer) - 1] = 0;
33 Json::String s(buffer);
34 Json::String::size_type index = s.find_last_of("eE");
35 if (index != Json::String::npos) {
36 Json::String::size_type hasSign =
37 (s[index + 1] == '+' || s[index + 1] == '-') ? 1 : 0;
38 Json::String::size_type exponentStartIndex = index + 1 + hasSign;
39 Json::String normalized = s.substr(0, exponentStartIndex);
40 Json::String::size_type indexDigit =
41 s.find_first_not_of('0', exponentStartIndex);
42 Json::String exponent = "0";
43 if (indexDigit != Json::String::npos) // There is an exponent different
44 // from 0
45 {
46 exponent = s.substr(indexDigit);
47 }
48 return normalized + exponent;
49 }
50 return s;
51 }
52
readInputTestFile(const char * path)53 static Json::String readInputTestFile(const char* path) {
54 FILE* file = fopen(path, "rb");
55 if (!file)
56 return "";
57 fseek(file, 0, SEEK_END);
58 long const size = ftell(file);
59 size_t const usize = static_cast<unsigned long>(size);
60 fseek(file, 0, SEEK_SET);
61 char* buffer = new char[size + 1];
62 buffer[size] = 0;
63 Json::String text;
64 if (fread(buffer, 1, usize, file) == usize)
65 text = buffer;
66 fclose(file);
67 delete[] buffer;
68 return text;
69 }
70
71 static void
printValueTree(FILE * fout,Json::Value & value,const Json::String & path=".")72 printValueTree(FILE* fout, Json::Value& value, const Json::String& path = ".") {
73 if (value.hasComment(Json::commentBefore)) {
74 fprintf(fout, "%s\n", value.getComment(Json::commentBefore).c_str());
75 }
76 switch (value.type()) {
77 case Json::nullValue:
78 fprintf(fout, "%s=null\n", path.c_str());
79 break;
80 case Json::intValue:
81 fprintf(fout, "%s=%s\n", path.c_str(),
82 Json::valueToString(value.asLargestInt()).c_str());
83 break;
84 case Json::uintValue:
85 fprintf(fout, "%s=%s\n", path.c_str(),
86 Json::valueToString(value.asLargestUInt()).c_str());
87 break;
88 case Json::realValue:
89 fprintf(fout, "%s=%s\n", path.c_str(),
90 normalizeFloatingPointStr(value.asDouble()).c_str());
91 break;
92 case Json::stringValue:
93 fprintf(fout, "%s=\"%s\"\n", path.c_str(), value.asString().c_str());
94 break;
95 case Json::booleanValue:
96 fprintf(fout, "%s=%s\n", path.c_str(), value.asBool() ? "true" : "false");
97 break;
98 case Json::arrayValue: {
99 fprintf(fout, "%s=[]\n", path.c_str());
100 Json::ArrayIndex size = value.size();
101 for (Json::ArrayIndex index = 0; index < size; ++index) {
102 static char buffer[16];
103 jsoncpp_snprintf(buffer, sizeof(buffer), "[%u]", index);
104 printValueTree(fout, value[index], path + buffer);
105 }
106 } break;
107 case Json::objectValue: {
108 fprintf(fout, "%s={}\n", path.c_str());
109 Json::Value::Members members(value.getMemberNames());
110 std::sort(members.begin(), members.end());
111 Json::String suffix = *(path.end() - 1) == '.' ? "" : ".";
112 for (auto name : members) {
113 printValueTree(fout, value[name], path + suffix + name);
114 }
115 } break;
116 default:
117 break;
118 }
119
120 if (value.hasComment(Json::commentAfter)) {
121 fprintf(fout, "%s\n", value.getComment(Json::commentAfter).c_str());
122 }
123 }
124
parseAndSaveValueTree(const Json::String & input,const Json::String & actual,const Json::String & kind,const Json::Features & features,bool parseOnly,Json::Value * root)125 static int parseAndSaveValueTree(const Json::String& input,
126 const Json::String& actual,
127 const Json::String& kind,
128 const Json::Features& features,
129 bool parseOnly,
130 Json::Value* root) {
131 Json::Reader reader(features);
132 bool parsingSuccessful =
133 reader.parse(input.data(), input.data() + input.size(), *root);
134 if (!parsingSuccessful) {
135 printf("Failed to parse %s file: \n%s\n", kind.c_str(),
136 reader.getFormattedErrorMessages().c_str());
137 return 1;
138 }
139 if (!parseOnly) {
140 FILE* factual = fopen(actual.c_str(), "wt");
141 if (!factual) {
142 printf("Failed to create %s actual file.\n", kind.c_str());
143 return 2;
144 }
145 printValueTree(factual, *root);
146 fclose(factual);
147 }
148 return 0;
149 }
150 // static Json::String useFastWriter(Json::Value const& root) {
151 // Json::FastWriter writer;
152 // writer.enableYAMLCompatibility();
153 // return writer.write(root);
154 // }
useStyledWriter(Json::Value const & root)155 static Json::String useStyledWriter(Json::Value const& root) {
156 Json::StyledWriter writer;
157 return writer.write(root);
158 }
useStyledStreamWriter(Json::Value const & root)159 static Json::String useStyledStreamWriter(Json::Value const& root) {
160 Json::StyledStreamWriter writer;
161 Json::OStringStream sout;
162 writer.write(sout, root);
163 return sout.str();
164 }
useBuiltStyledStreamWriter(Json::Value const & root)165 static Json::String useBuiltStyledStreamWriter(Json::Value const& root) {
166 Json::StreamWriterBuilder builder;
167 return Json::writeString(builder, root);
168 }
rewriteValueTree(const Json::String & rewritePath,const Json::Value & root,Options::writeFuncType write,Json::String * rewrite)169 static int rewriteValueTree(const Json::String& rewritePath,
170 const Json::Value& root,
171 Options::writeFuncType write,
172 Json::String* rewrite) {
173 *rewrite = write(root);
174 FILE* fout = fopen(rewritePath.c_str(), "wt");
175 if (!fout) {
176 printf("Failed to create rewrite file: %s\n", rewritePath.c_str());
177 return 2;
178 }
179 fprintf(fout, "%s\n", rewrite->c_str());
180 fclose(fout);
181 return 0;
182 }
183
removeSuffix(const Json::String & path,const Json::String & extension)184 static Json::String removeSuffix(const Json::String& path,
185 const Json::String& extension) {
186 if (extension.length() >= path.length())
187 return Json::String("");
188 Json::String suffix = path.substr(path.length() - extension.length());
189 if (suffix != extension)
190 return Json::String("");
191 return path.substr(0, path.length() - extension.length());
192 }
193
printConfig()194 static void printConfig() {
195 // Print the configuration used to compile JsonCpp
196 #if defined(JSON_NO_INT64)
197 printf("JSON_NO_INT64=1\n");
198 #else
199 printf("JSON_NO_INT64=0\n");
200 #endif
201 }
202
printUsage(const char * argv[])203 static int printUsage(const char* argv[]) {
204 printf("Usage: %s [--strict] input-json-file", argv[0]);
205 return 3;
206 }
207
parseCommandLine(int argc,const char * argv[],Options * opts)208 static int parseCommandLine(int argc, const char* argv[], Options* opts) {
209 opts->parseOnly = false;
210 opts->write = &useStyledWriter;
211 if (argc < 2) {
212 return printUsage(argv);
213 }
214 int index = 1;
215 if (Json::String(argv[index]) == "--json-checker") {
216 opts->features = Json::Features::strictMode();
217 opts->parseOnly = true;
218 ++index;
219 }
220 if (Json::String(argv[index]) == "--json-config") {
221 printConfig();
222 return 3;
223 }
224 if (Json::String(argv[index]) == "--json-writer") {
225 ++index;
226 Json::String const writerName(argv[index++]);
227 if (writerName == "StyledWriter") {
228 opts->write = &useStyledWriter;
229 } else if (writerName == "StyledStreamWriter") {
230 opts->write = &useStyledStreamWriter;
231 } else if (writerName == "BuiltStyledStreamWriter") {
232 opts->write = &useBuiltStyledStreamWriter;
233 } else {
234 printf("Unknown '--json-writer %s'\n", writerName.c_str());
235 return 4;
236 }
237 }
238 if (index == argc || index + 1 < argc) {
239 return printUsage(argv);
240 }
241 opts->path = argv[index];
242 return 0;
243 }
runTest(Options const & opts)244 static int runTest(Options const& opts) {
245 int exitCode = 0;
246
247 Json::String input = readInputTestFile(opts.path.c_str());
248 if (input.empty()) {
249 printf("Failed to read input or empty input: %s\n", opts.path.c_str());
250 return 3;
251 }
252
253 Json::String basePath = removeSuffix(opts.path, ".json");
254 if (!opts.parseOnly && basePath.empty()) {
255 printf("Bad input path. Path does not end with '.expected':\n%s\n",
256 opts.path.c_str());
257 return 3;
258 }
259
260 Json::String const actualPath = basePath + ".actual";
261 Json::String const rewritePath = basePath + ".rewrite";
262 Json::String const rewriteActualPath = basePath + ".actual-rewrite";
263
264 Json::Value root;
265 exitCode = parseAndSaveValueTree(input, actualPath, "input", opts.features,
266 opts.parseOnly, &root);
267 if (exitCode || opts.parseOnly) {
268 return exitCode;
269 }
270 Json::String rewrite;
271 exitCode = rewriteValueTree(rewritePath, root, opts.write, &rewrite);
272 if (exitCode) {
273 return exitCode;
274 }
275 Json::Value rewriteRoot;
276 exitCode = parseAndSaveValueTree(rewrite, rewriteActualPath, "rewrite",
277 opts.features, opts.parseOnly, &rewriteRoot);
278 if (exitCode) {
279 return exitCode;
280 }
281 return 0;
282 }
main(int argc,const char * argv[])283 int main(int argc, const char* argv[]) {
284 Options opts;
285 try {
286 int exitCode = parseCommandLine(argc, argv, &opts);
287 if (exitCode != 0) {
288 printf("Failed to parse command-line.");
289 return exitCode;
290 }
291 return runTest(opts);
292 } catch (const std::exception& e) {
293 printf("Unhandled exception:\n%s\n", e.what());
294 return 1;
295 }
296 }
297
298 #if defined(__GNUC__)
299 #pragma GCC diagnostic pop
300 #endif
301