diff --git a/include/vsg/io/json.h b/include/vsg/io/json.h index 0d9687113d..1170f2e70e 100644 --- a/include/vsg/io/json.h +++ b/include/vsg/io/json.h @@ -23,6 +23,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include @@ -30,40 +31,97 @@ namespace vsg { /// JSON parser based on spec: https://www.json.org/json-en.html - struct JSONParser + struct VSG_DECLSPEC JSONParser { std::string buffer; std::size_t pos = 0; - vsg::mem_stream mstr; - vsg::indentation indent; + mem_stream mstr; JSONParser(); + /// Schema base class to provides a mechanism for customizing the json parsing to handle + /// mapping between json schema's and user data/scene graph objects + struct Schema + { + // array elements [ value, value.. ] + virtual void read_array(JSONParser& parser); + virtual void read_object(JSONParser& parser); + virtual void read_string(JSONParser& parser); + virtual void read_number(JSONParser& parser, std::istream& input); + virtual void read_bool(JSONParser& parser, bool value); + virtual void read_null(JSONParser& parser); + + // object properties { name, value; ... } + virtual void read_array(JSONParser& parser, const std::string_view& name); + virtual void read_object(JSONParser& parser, const std::string_view& name); + virtual void read_string(JSONParser& parser, const std::string_view& name); + virtual void read_number(JSONParser& parser, const std::string_view& name, std::istream& input); + virtual void read_bool(JSONParser& parser, const std::string_view& name, bool value); + virtual void read_null(JSONParser& parser, const std::string_view& name); + }; + + bool read_string(std::string& value); + void read_object(Schema& schema); + void read_array(Schema& schema); + + template + void warning(Args&&... args) + { + warn("Parsing error at pos = ", pos, ". ", std::forward(args)...); + } + inline bool white_space(char c) const { return (c == ' ' || c == '\t' || c == '\r' || c == '\n'); } - bool read_string(std::string& value); - - vsg::ref_ptr read_array(); - vsg::ref_ptr read_object(); }; VSG_type_name(vsg::JSONParser); + /// Default support for mapping standard JSON types directly to VSG. + /// JSON objects are mapped to Object metadata. + /// JSON arrays to Objects. + /// string, number and bool are mapped to stringValue, doubleValue and boolValue. + struct VSG_DECLSPEC JSONtoMetaDataSchema : public JSONParser::Schema + { + // object created when parsing JSON file + ref_ptr object; + ref_ptr objects; + + void addToArray(ref_ptr in_object); + void addToObject(const std::string_view& name, ref_ptr in_object); + + // array elements [ value, value.. ] + void read_array(JSONParser& parser) override; + void read_object(JSONParser& parser) override; + void read_string(JSONParser& parser) override; + void read_number(JSONParser& parser, std::istream& input) override; + void read_bool(JSONParser& parser, bool value) override; + void read_null(JSONParser& parser) override; + + // object properties { name, value; ... } + void read_array(JSONParser& parser, const std::string_view& name) override; + void read_object(JSONParser& parser, const std::string_view& name) override; + void read_string(JSONParser& parser, const std::string_view& name) override; + void read_number(JSONParser& parser, const std::string_view& name, std::istream& input) override; + void read_bool(JSONParser& parser, const std::string_view& name, bool value) override; + void read_null(JSONParser& parser, const std::string_view& name) override; + }; + VSG_type_name(vsg::JSONtoMetaDataSchema); + /// json ReaderWriter - class json : public vsg::Inherit + class json : public Inherit { public: json(); - vsg::ref_ptr read(const vsg::Path&, vsg::ref_ptr) const override; - vsg::ref_ptr read(std::istream&, vsg::ref_ptr) const override; - vsg::ref_ptr read(const uint8_t* ptr, size_t size, vsg::ref_ptr options = {}) const override; + ref_ptr read(const Path&, ref_ptr) const override; + ref_ptr read(std::istream&, ref_ptr) const override; + ref_ptr read(const uint8_t* ptr, size_t size, ref_ptr options = {}) const override; - vsg::ref_ptr _read(std::istream&, vsg::ref_ptr) const; + ref_ptr _read(std::istream&, ref_ptr) const; - bool supportedExtension(const vsg::Path& ext) const; + bool supportedExtension(const Path& ext) const; bool getFeatures(Features& features) const override; }; diff --git a/src/vsg/io/json.cpp b/src/vsg/io/json.cpp index 5885d6f20e..395f0c91dd 100644 --- a/src/vsg/io/json.cpp +++ b/src/vsg/io/json.cpp @@ -22,139 +22,200 @@ using namespace vsg; //////////////////////////////////////////////////////////////////////////////////////////////////// // -// json parser +// JSONParser::Schema // -JSONParser::JSONParser() : - mstr(nullptr, 0) +void JSONParser::Schema::read_array(JSONParser& ) { } -bool JSONParser::read_string(std::string& value) +void JSONParser::Schema::read_object(JSONParser& ) { - if (buffer[pos] != '"') return false; +} - // read string - auto end_of_value = buffer.find('"', pos + 1); - if (end_of_value == std::string::npos) return false; +void JSONParser::Schema::read_string(JSONParser& ) +{ +} - // could have escape characters. +void JSONParser::Schema::read_number(JSONParser&, std::istream&) +{ +} - value = buffer.substr(pos + 1, end_of_value - pos - 1); +void JSONParser::Schema::read_bool(JSONParser&, bool) +{ +} - pos = end_of_value + 1; +void JSONParser::Schema::read_null(JSONParser&) +{ +} - return true; +void JSONParser::Schema::read_array(JSONParser&, const std::string_view&) +{ } -vsg::ref_ptr JSONParser::read_array() +void JSONParser::Schema::read_object(JSONParser&, const std::string_view&) { - pos = buffer.find_first_not_of(" \t\r\n", pos); - if (pos == std::string::npos) return {}; - if (buffer[pos] != '[') - { - vsg::info(indent, "read_array() could not match opening ["); - return {}; - } +} - // buffer[pos] == '[' - // advance past open bracket - pos = buffer.find_first_not_of(" \t\r\n", pos + 1); - if (pos == std::string::npos) - { - vsg::info(indent, "read_array() contents after ["); - return {}; - } +void JSONParser::Schema::read_string(JSONParser&, const std::string_view&) +{ +} - indent += 4; +void JSONParser::Schema::read_number(JSONParser&, const std::string_view&, std::istream&) +{ +} - auto objects = vsg::Objects::create(); +void JSONParser::Schema::read_bool(JSONParser&, const std::string_view&, bool) +{ +} - while (pos != std::string::npos && pos < buffer.size() && buffer[pos] != ']') - { - // now look to pair with value after " : " - if (buffer[pos] == '{') - { - auto value = read_object(); - if (value) objects->children.push_back(value); - } - else if (buffer[pos] == '[') - { - auto value = read_array(); - if (value) objects->children.push_back(value); - } - else if (buffer[pos] == '"') - { - if (std::string value; read_string(value)) - { - objects->children.push_back(vsg::stringValue::create(value)); - } - } - else if (buffer[pos] == ',') - { - ++pos; - } - else - { - auto end_of_field = buffer.find_first_of(",}]", pos + 1); - if (end_of_field == std::string::npos) break; +void JSONParser::Schema::read_null(JSONParser&, const std::string_view&) +{ +} - auto end_of_value = end_of_field - 1; - while (end_of_value > 0 && white_space(buffer[end_of_value])) --end_of_value; +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// JSONtoMetaDataSchema +// - if (buffer.compare(pos, end_of_value - pos, "null") == 0) - { - objects->children.push_back(nullptr); - } - else if (buffer.compare(pos, end_of_value - pos, "true") == 0) - { - objects->children.push_back(vsg::boolValue::create(true)); - } - else if (buffer.compare(pos, end_of_value - pos, "false") == 0) - { - objects->children.push_back(vsg::boolValue::create(false)); - } - else - { - mstr.set(reinterpret_cast(&buffer.at(pos)), end_of_value - pos + 1); +void JSONtoMetaDataSchema::addToArray(ref_ptr in_object) +{ + if (!in_object) return; - double value; - mstr >> value; - objects->children.push_back(vsg::doubleValue::create(value)); - } + if (!objects) objects = Objects::create(); + objects->addChild(in_object); +} - // skip to end of field - pos = end_of_field; - } +void JSONtoMetaDataSchema::addToObject(const std::string_view& name, ref_ptr in_object) +{ + if (!in_object) return; - pos = buffer.find_first_not_of(" \t\r\n", pos); - } + if (!object) object = Object::create(); + object->setObject(std::string(name), in_object); +} - if (pos < buffer.size() && buffer[pos] == ']') - { - ++pos; - } +void JSONtoMetaDataSchema::read_array(JSONParser& parser) +{ + JSONtoMetaDataSchema nested; + parser.read_array(nested); + + addToArray(nested.objects); +} + +void JSONtoMetaDataSchema::read_object(JSONParser& parser) +{ + JSONtoMetaDataSchema nested; + parser.read_object(nested); + + addToArray(nested.object); +} + +void JSONtoMetaDataSchema::read_string(JSONParser& parser) +{ + std::string value; + parser.read_string(value); + + addToArray(stringValue::create(value)); +} + +void JSONtoMetaDataSchema::read_number(JSONParser&, std::istream& input) +{ + double value; + input >> value; + + addToArray(doubleValue::create(value)); +} + +void JSONtoMetaDataSchema::read_bool(JSONParser&, bool value) +{ + addToArray(boolValue::create(value)); +} + +void JSONtoMetaDataSchema::read_null(JSONParser&) +{ +} + +void JSONtoMetaDataSchema::read_array(JSONParser& parser, const std::string_view& name) +{ + JSONtoMetaDataSchema nested; + parser.read_array(nested); + + addToObject(name, nested.objects); +} + +void JSONtoMetaDataSchema::read_object(JSONParser& parser, const std::string_view& name) +{ + JSONtoMetaDataSchema nested; + parser.read_object(nested); + + addToObject(name, nested.object); +} + +void JSONtoMetaDataSchema::read_string(JSONParser& parser, const std::string_view& name) +{ + std::string value; + parser.read_string(value); + + addToObject(name, stringValue::create(value)); +} + +void JSONtoMetaDataSchema::read_number(JSONParser&, const std::string_view& name, std::istream& input) +{ + double value; + input >> value; + + addToObject(name, doubleValue::create(value)); +} + +void JSONtoMetaDataSchema::read_bool(JSONParser&, const std::string_view& name, bool value) +{ + addToObject(name, boolValue::create(value)); +} + +void JSONtoMetaDataSchema::read_null(JSONParser&, const std::string_view&) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// json parser +// +JSONParser::JSONParser() : + mstr(nullptr, 0) +{ +} + +bool JSONParser::read_string(std::string& value) +{ + if (buffer[pos] != '"') return false; + + // read string + auto end_of_value = buffer.find('"', pos + 1); + if (end_of_value == std::string::npos) return false; + + // could have escape characters. + + value = buffer.substr(pos + 1, end_of_value - pos - 1); - indent -= 4; + pos = end_of_value + 1; - return objects; + return true; } -vsg::ref_ptr JSONParser::read_object() + +void JSONParser::read_object(JSONParser::Schema& schema) { - if (pos == std::string::npos) return {}; - if (buffer[pos] != '{') return {}; + if (pos == std::string::npos) return; + if (buffer[pos] != '{') return; // buffer[pos] == '{' // advance past open bracket pos = buffer.find_first_not_of(" \t\r\n", pos + 1); - if (pos == std::string::npos) return {}; - - indent += 4; - - auto object = vsg::Object::create(); + if (pos == std::string::npos) return; while (pos != std::string::npos && pos < buffer.size() && buffer[pos] != '}') { + auto previous_position = pos; + if (buffer[pos] == '"') { auto end_of_string = buffer.find('"', pos + 1); @@ -166,14 +227,14 @@ vsg::ref_ptr JSONParser::read_object() pos = buffer.find_first_not_of(" \t\r\n", end_of_string + 1); if (pos == std::string::npos) { - vsg::info(indent, "read_object() deliminator error end of buffer."); + warning("read_object() deliminator error end of buffer."); break; } // make sure next charater is the {name : value} deliminator if (buffer[pos] != ':') { - vsg::info(indent, "read_object() deliminator error buffer[", pos, "] = ", buffer[pos]); + warning("read_object() deliminator error buffer[", pos, "] = ", buffer[pos]); break; } @@ -187,22 +248,15 @@ vsg::ref_ptr JSONParser::read_object() // now look to pair with value after " : " if (buffer[pos] == '{') { - auto value = read_object(); - - object->setObject(std::string(name), value); + schema.read_object(*this, name); } else if (buffer[pos] == '[') { - auto value = read_array(); - - object->setObject(std::string(name), value); + schema.read_array(*this, name); } else if (buffer[pos] == '"') { - if (std::string value; read_string(value)) - { - object->setValue(std::string(name), value); - } + schema.read_string(*this, name); } else { @@ -214,24 +268,20 @@ vsg::ref_ptr JSONParser::read_object() if (buffer.compare(pos, end_of_value - pos, "null") == 0) { - // non op? - object->setObject(std::string(name), nullptr); + schema.read_null(*this, name); } else if (buffer.compare(pos, end_of_value - pos, "true") == 0) { - object->setValue(std::string(name), true); + schema.read_bool(*this, name, true); } else if (buffer.compare(pos, end_of_value - pos, "false") == 0) { - object->setValue(std::string(name), false); + schema.read_bool(*this, name, false); } else { mstr.set(reinterpret_cast(&buffer.at(pos)), end_of_value - pos + 1); - - double value; - mstr >> value; - object->setValue(std::string(name), value); + schema.read_number(*this, name, mstr); } // skip to end of field @@ -244,22 +294,114 @@ vsg::ref_ptr JSONParser::read_object() } else { - vsg::info(indent, "read_object() buffer[", pos, "] = ", buffer[pos]); + warning("read_object() buffer[", pos, "] = ", buffer[pos]); } pos = buffer.find_first_not_of(" \t\r\n", pos); + + if (pos <= previous_position) + { + warning("Parser stuck when reading object."); + break; + } } if (pos < buffer.size() && buffer[pos] == '}') { ++pos; } +} - indent -= 4; +void JSONParser::read_array(JSONParser::Schema& schema) +{ + pos = buffer.find_first_not_of(" \t\r\n", pos); + if (pos == std::string::npos) return; + if (buffer[pos] != '[') + { + warning("read_array() could not match opening ["); + return; + } - return object; + // buffer[pos] == '[' + // advance past open bracket + pos = buffer.find_first_not_of(" \t\r\n", pos + 1); + if (pos == std::string::npos) + { + warning("read_array() contents after ["); + return; + } + + while (pos != std::string::npos && pos < buffer.size() && buffer[pos] != ']') + { + auto previous_position = pos; + + // now look to pair with value after " : " + if (buffer[pos] == '{') + { + schema.read_object(*this); + } + else if (buffer[pos] == '[') + { + schema.read_array(*this); + } + else if (buffer[pos] == '"') + { + if (std::string value; read_string(value)) + { + schema.read_string(*this); + } + } + else if (buffer[pos] == ',') + { + ++pos; + } + else + { + auto end_of_field = buffer.find_first_of(",}]", pos + 1); + if (end_of_field == std::string::npos) break; + + auto end_of_value = end_of_field - 1; + while (end_of_value > 0 && white_space(buffer[end_of_value])) --end_of_value; + + if (buffer.compare(pos, end_of_value - pos, "null") == 0) + { + schema.read_null(*this); + } + else if (buffer.compare(pos, end_of_value - pos, "true") == 0) + { + schema.read_bool(*this, true); + } + else if (buffer.compare(pos, end_of_value - pos, "false") == 0) + { + schema.read_bool(*this, false); + } + else + { + mstr.set(reinterpret_cast(&buffer.at(pos)), end_of_value - pos + 1); + + schema.read_number(*this, mstr); + } + + // skip to end of field + pos = end_of_field; + } + + pos = buffer.find_first_not_of(" \t\r\n", pos); + + if (pos <= previous_position) + { + warning("Parser stuck when reading array."); + break; + } + } + + if (pos < buffer.size() && buffer[pos] == ']') + { + ++pos; + } } + //////////////////////////////////////////////////////////////////////////////////////////////////// // // json ReaderWriter @@ -268,12 +410,12 @@ json::json() { } -bool json::supportedExtension(const vsg::Path& ext) const +bool json::supportedExtension(const Path& ext) const { return ext == ".json"; } -vsg::ref_ptr json::_read(std::istream& fin, vsg::ref_ptr) const +ref_ptr json::_read(std::istream& fin, ref_ptr) const { fin.seekg(0, fin.end); size_t fileSize = fin.tellg(); @@ -287,7 +429,7 @@ vsg::ref_ptr json::_read(std::istream& fin, vsg::ref_ptr(parser.buffer.data()), fileSize); - vsg::ref_ptr result; + ref_ptr result; // skip white space parser.pos = parser.buffer.find_first_not_of(" \t\r\n", 0); @@ -295,33 +437,41 @@ vsg::ref_ptr json::_read(std::istream& fin, vsg::ref_ptr json::read(const vsg::Path& filename, vsg::ref_ptr options) const +ref_ptr json::read(const Path& filename, ref_ptr options) const { - vsg::Path ext = (options && options->extensionHint) ? options->extensionHint : vsg::lowerCaseFileExtension(filename); + Path ext = (options && options->extensionHint) ? options->extensionHint : lowerCaseFileExtension(filename); if (!supportedExtension(ext)) return {}; - vsg::Path filenameToUse = vsg::findFile(filename, options); + Path filenameToUse = findFile(filename, options); if (!filenameToUse) return {}; std::ifstream fin(filenameToUse, std::ios::ate | std::ios::binary); return _read(fin, options); } -vsg::ref_ptr json::read(std::istream& fin, vsg::ref_ptr options) const +ref_ptr json::read(std::istream& fin, ref_ptr options) const { if (!options || !options->extensionHint) return {}; if (!supportedExtension(options->extensionHint)) return {}; @@ -329,18 +479,18 @@ vsg::ref_ptr json::read(std::istream& fin, vsg::ref_ptr json::read(const uint8_t* ptr, size_t size, vsg::ref_ptr options) const +ref_ptr json::read(const uint8_t* ptr, size_t size, ref_ptr options) const { if (!options || !options->extensionHint) return {}; if (!supportedExtension(options->extensionHint)) return {}; - vsg::mem_stream fin(ptr, size); + mem_stream fin(ptr, size); return _read(fin, options); } bool json::getFeatures(Features& features) const { - vsg::ReaderWriter::FeatureMask supported_features = static_cast(vsg::ReaderWriter::READ_FILENAME | vsg::ReaderWriter::READ_ISTREAM | vsg::ReaderWriter::READ_MEMORY); + ReaderWriter::FeatureMask supported_features = static_cast(ReaderWriter::READ_FILENAME | ReaderWriter::READ_ISTREAM | ReaderWriter::READ_MEMORY); features.extensionFeatureMap[".json"] = supported_features; return true;