/*
* Copyright (C) 2015 by Multi-Tech Systems
*
* This file is part of jsparser.
*
* jsparser is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* jsparser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with jsparser. If not, see .
*
*/
#include
#include
#include
#include
#include
#include "Version.h"
//OPTIONS
const uint32_t OPT_NONE = 0x00000000;
const uint32_t OPT_JSOBJ = 0x00000001;
const uint32_t OPT_COUNT = 0x00000002;
const uint32_t OPT_LIST = 0x00000004;
const uint32_t OPT_SEARCH = 0x00000008;
const uint32_t OPT_ISNULL = 0x00000010;
const uint32_t OPT_ISBOOL = 0x00000020;
const uint32_t OPT_ISSTRING = 0x00000040;
const uint32_t OPT_ISINT = 0x00000080;
const uint32_t OPT_ISUINT = 0x00000100;
const uint32_t OPT_ISFLOAT = 0x00000200;
const uint32_t OPT_ISARRAY = 0x00000400;
const uint32_t OPT_ISOBJECT = 0x00000800;
const uint32_t OPT_BASICOPTIONS = 0x0000000F;
const uint32_t OPT_ISOPTIONS = 0x00000FF0;
uint32_t g_iOptions = 0;
std::string g_sKey;
std::string g_sValue;
std::string get(const std::string& sPath, const Json::Value& jData);
std::string cleansePath(const std::string& path);
std::string parseOptions(int argc, char** argv);
std::string handleSearchOption(const Json::Value& data);
std::string handleIsOptions(const Json::Value& data);
int main(int argc, char** argv) {
Json::Value jInput;
std::string sPath(parseOptions(argc, argv));
#if defined(JSONCPP_VERSION_HEXA) && (JSONCPP_VERSION_HEXA > 0x010600) // > 1.6.0
Json::CharReaderBuilder builder;
builder.strictMode(&builder.settings_);
if (!Json::parseFromStream(builder, std::cin, &jInput, NULL)) {
fprintf(stderr, "bad json input\n");
return 1;
}
#else
std::string sInput;
std::stringstream ss;
std::string line;
while (std::getline(std::cin, line)) {
ss << line << std::endl;
}
sInput = ss.str();
//printf("INPUT:(%s)\n", g_sInput.c_str());
Json::Features features = Json::Features::strictMode();
Json::Reader reader(features);
if (!reader.parse(sInput, jInput)) {
fprintf(stderr, "bad json input\n");
return 1;
}
#endif
printf("%s", get(sPath, jInput).c_str());
return 0;
}
std::string cleansePath(const std::string& path) {
std::string clean(path);
while (clean.size() > 0 && clean[0] == '/') {
clean = clean.substr(1);
}
while (clean.size() > 0 && clean[clean.size() - 1] == '/') {
clean = clean.substr(0, clean.size() - 1);
}
return clean;
}
std::string get(const std::string& sPath, const Json::Value& jData) {
std::vector components = MTS::Text::split(sPath, '/');
const Json::Value* tmp = &jData;
for (uint32_t i = 0; i < components.size(); i++) {
if (tmp->isNull()) {
if (g_iOptions & OPT_ISNULL) {
printf("true");
} else {
fprintf(stderr, "null path [%s]. failed on [%s]\n", sPath.c_str(), components[i].c_str());
}
exit(1);
}
if (components[i].size() == 0) {
continue;
}
if (tmp->isArray()) {
uint32_t index;
if (MTS::Text::parse(index, components[i]) && tmp->isValidIndex(index)) {
tmp = &(*tmp)[index];
} else {
if (g_iOptions & OPT_ISNULL) {
printf("true");
} else {
fprintf(stderr, "array element is null. path [%s]. failed on [%s]\n", sPath.c_str(), components[i].c_str());
}
exit(1);
}
} else if (tmp->isObject() && tmp->isMember(components[i])) {
tmp = &(*tmp)[components[i]];
} else {
if (g_iOptions & OPT_ISNULL) {
printf("true");
} else {
if (g_iOptions & (OPT_ISOPTIONS)) {
printf("false");
} else {
fprintf(stderr, "not an object or array. null path [%s]. failed on [%s]\n", sPath.c_str(), components[i].c_str());
}
}
exit(1);
}
}
if (g_iOptions & OPT_SEARCH) {
return handleSearchOption(*tmp);
}
if (g_iOptions & OPT_ISOPTIONS) {
return handleIsOptions(*tmp);
}
if (g_iOptions & OPT_LIST) {
if (!tmp->isNull() && !tmp->isObject()) {
fprintf(stderr, "path must lead to an object to use --list option\n");
exit(1);
}
std::vector list = tmp->getMemberNames();
std::string csv;
for (uint32_t i = 0; i < list.size() - 1; i++) {
csv.append(list[i]);
csv.append(",");
}
csv.append(list[list.size() - 1]);
return csv;
}
if (g_iOptions & OPT_COUNT) {
std::stringstream ss;
ss << tmp->size();
return ss.str();
}
if (tmp->isString()) {
return tmp->asString();
}
if (tmp->isBool()) {
if (tmp->asBool()) {
return "true";
} else {
return "false";
}
}
if (tmp->isInt()) {
std::stringstream ss;
ss << tmp->asInt();
return ss.str();
}
if (tmp->isUInt()) {
std::stringstream ss;
ss << tmp->asUInt();
return ss.str();
}
if (tmp->isDouble()) {
std::stringstream ss;
ss << tmp->asDouble();
return ss.str();
}
if (g_iOptions & OPT_JSOBJ) {
//Returning Json Object
return tmp->toStyledString();
}
fprintf(stderr, "path ends at json object. increase depth or add --jsobj option\n");
exit(1);
}
std::string handleSearchOption(const Json::Value& data) {
if (!data.isArray()) {
fprintf(stderr, "path does not lead to array\n");
return "-1";
}
uint32_t index;
for (index = 0; index < data.size(); index++) {
if (data[index].isArray() || data[index].isNull()) {
continue;
}
Json::Value value;
if (data[index].isObject() && data[index].isMember(g_sKey)) {
value = data[index][g_sKey];
} else {
value = data[index];
}
if (value.isString()) {
if (g_sValue == value.asString()) {
break;
}
}
if (value.isInt()) {
int64_t iValue;
if (MTS::Text::parse(iValue, g_sValue) && iValue == value.asInt()) {
break;
}
}
if (value.isUInt()) {
uint64_t iValue;
if (MTS::Text::parse(iValue, g_sValue) && iValue == value.asUInt()) {
break;
}
}
if (value.isDouble()) {
double fValue;
if (MTS::Text::parse(fValue, g_sValue) && fValue == value.asDouble()) {
break;
}
}
if (value.isBool()) {
if (g_sValue == "true" && value.asBool()) {
break;
}
if (g_sValue == "false" && !value.asBool()) {
break;
}
}
}
if (index != data.size()) {
return MTS::Text::format(index);
} else {
return "-1";
}
}
std::string handleIsOptions(const Json::Value& data) {
uint32_t type = 0;
if (data.isString()) {
type |= OPT_ISSTRING;
}
if (data.isBool()) {
type |= OPT_ISBOOL;
}
if (data.isDouble()) {
type |= OPT_ISFLOAT;
}
if (data.isInt()) {
type |= OPT_ISINT;
}
if (data.isUInt()) {
type |= OPT_ISUINT;
}
if (data.isNull()) {
type |= OPT_ISNULL;
}
if (data.isArray()) {
type |= (OPT_ISARRAY);
}
if (data.isObject()) {
type |= (OPT_ISOBJECT);
}
if ((g_iOptions & OPT_ISOPTIONS) & type) {
return "true";
}
return "false";
}
std::string parseOptions(int argc, char** argv) {
int c;
std::string sPath;
while (1) {
static struct option long_options[] =
{ { "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' },
{ "jsobj", no_argument, 0, 'j' },
{ "count", no_argument, 0, 'c' },
{ "list", no_argument, 0, 'l' },
{ "isNull", no_argument, 0, 'n' },
{ "isBool", no_argument, 0, 'b' },
{ "isString", no_argument, 0, 's' },
{ "isInt", no_argument, 0, 'i' },
{ "isUInt", no_argument, 0, 'u' },
{ "isFloat", no_argument, 0, 'f' },
{ "isArray", no_argument, 0, 'a' },
{ "isObject", no_argument, 0, 'o' },
{ "path", required_argument, 0, 'p' },
{ "search", required_argument, 0, 'q' },
{ 0, 0, 0, 0 } };
/* getopt_long stores the option index here. */
int option_index = 0;
c = getopt_long(argc, argv, "hvjclnbsiufaop:q:", long_options, &option_index);
/* Detect the end of the options. */
if (c == -1)
break;
switch (c) {
case 'h':
printf("Usage: %s [OPTIONS] [--path ]\n", argv[0]);
printf("\tAccepts JSON through stdin.\n");
printf("\tReturns element at end of path parameter.\n");
printf("\tOPTIONS:\n");
printf("\t--jsobj (j) : returns JSON object at the end of path\n");
printf("\t--path (p) : Specify path to nested json element\n");
printf("\t--count (c) : returns number of elements in an array or object\n");
printf("\t--list (l) : returns comma-delimited list of keys within an object\n");
printf("\t--search (q) : returns index of array that contains search criteria\n");
printf("\t--isNull (n) : returns 'true' if NULL, 'false' otherwise\n");
printf("\t--isBool (b) : returns 'true' if Boolean, 'false' otherwise\n");
printf("\t--isString (s) : returns 'true' if String, 'false' otherwise\n");
printf("\t--isInt (i) : returns 'true' if Integer, 'false' otherwise\n");
printf("\t--isUInt (u) : returns 'true' if Unsigned Integer, 'false' otherwise\n");
printf("\t--isFloat (f) : returns 'true' if Float or Double, 'false' otherwise\n");
printf("\t--isArray (a) : returns 'true' if JSON Array, 'false' otherwise\n");
printf("\t--isObject (o) : returns 'true' if JSON Object, 'false' otherwise\n");
printf("\t--version (v) : show version\n");
printf("\t--help (h) : show this help\n");
printf("\tWrites to stderr if bad path or json data.\n");
printf("\tPath uses backslash characters to specify nested json element.\n");
printf("\tUse integers to index an element of an array\n");
printf("\tExamples: \n");
printf("\t\t#>echo { \\\"result\\\" : { \\\"enabled\\\" : true } } | %s -p /result/enabled\n\t\t#>true\n", argv[0]);
printf("\t\t#>echo { \\\"result\\\" : [ \\\"bears\\\", \\\"beets\\\", \\\"battlestar galactica\\\" ] } | %s -p /result/0\n\t\t#>bears\n", argv[0]);
exit(0);
break;
case 'v':
printf("Version: %s\n", Version::version.c_str());
exit(0);
break;
case 'p':
sPath = cleansePath(optarg);
break;
case 'j':
g_iOptions |= OPT_JSOBJ;
break;
case 'c':
g_iOptions |= OPT_COUNT;
break;
case 'l':
g_iOptions |= OPT_LIST;
break;
case 'q':
{
g_iOptions |= OPT_SEARCH;
std::vector kvp = MTS::Text::split(optarg, '=');
if (kvp.size() == 1) {
g_sValue = kvp[0];
} else if (kvp.size() == 2) {
g_sKey = kvp[0];
g_sValue = kvp[1];
} else {
fprintf(stderr, "search criteria must be a single value or key-value pair: value or key=value\n");
exit(1);
}
}
break;
case 'n':
g_iOptions |= OPT_ISNULL;
break;
case 's':
g_iOptions |= OPT_ISSTRING;
break;
case 'b':
g_iOptions |= OPT_ISBOOL;
break;
case 'i':
g_iOptions |= OPT_ISINT;
break;
case 'u':
g_iOptions |= OPT_ISUINT;
break;
case 'f':
g_iOptions |= OPT_ISFLOAT;
break;
case 'a':
g_iOptions |= (OPT_ISARRAY);
break;
case 'o':
g_iOptions |= (OPT_ISOBJECT);
break;
case '?':
/* getopt_long already printed an error message. */
break;
default:
printf("ABORTING!!\n");
abort();
break;
}
}
/* Print any remaining command line arguments (not options). */
if (optind < argc) {
fprintf(stderr, "non-option ARGV-elements: \n");
while (optind < argc)
fprintf(stderr, "%s ", argv[optind++]);
fprintf(stderr, "\n");
}
if ((g_iOptions & OPT_JSOBJ) && (g_iOptions & (~OPT_JSOBJ))) {
fprintf(stderr, "Option --jsobj can only be used with --path option\n");
exit(1);
}
if ((g_iOptions & OPT_COUNT) && (g_iOptions & (~OPT_COUNT))) {
fprintf(stderr, "Option --count can only be used with --path option\n");
exit(1);
}
if ((g_iOptions & OPT_LIST) && (g_iOptions & (~OPT_LIST))) {
fprintf(stderr, "Option --list can only be used with --path option\n");
exit(1);
}
if ((g_iOptions & OPT_SEARCH) && (g_iOptions & (~OPT_SEARCH))) {
fprintf(stderr, "Option --search can only be used with --path option\n");
exit(1);
}
if ((g_iOptions & OPT_ISOPTIONS) && (g_iOptions & (~OPT_ISOPTIONS))) {
fprintf(stderr, "Option \"--is*\" querries can only be used with --path option\n");
exit(1);
}
return sPath;
}