501 lines
10 KiB
C++
501 lines
10 KiB
C++
#include "stdafx.h"
|
|
#include "RuntimeJson.h"
|
|
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <sstream>
|
|
|
|
namespace
|
|
{
|
|
class JsonParser
|
|
{
|
|
public:
|
|
JsonParser(const std::string& text, std::string& error)
|
|
: mText(text), mError(error), mPosition(0)
|
|
{
|
|
}
|
|
|
|
bool parse(JsonValue& value)
|
|
{
|
|
skipWhitespace();
|
|
if (!parseValue(value))
|
|
return false;
|
|
|
|
skipWhitespace();
|
|
if (mPosition != mText.size())
|
|
{
|
|
setError("Unexpected trailing characters in JSON input.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool parseValue(JsonValue& value)
|
|
{
|
|
if (mPosition >= mText.size())
|
|
{
|
|
setError("Unexpected end of JSON input.");
|
|
return false;
|
|
}
|
|
|
|
char ch = mText[mPosition];
|
|
if (ch == '{')
|
|
return parseObject(value);
|
|
if (ch == '[')
|
|
return parseArray(value);
|
|
if (ch == '"')
|
|
{
|
|
std::string stringValue;
|
|
if (!parseString(stringValue))
|
|
return false;
|
|
value = JsonValue(stringValue);
|
|
return true;
|
|
}
|
|
if (ch == 't')
|
|
return parseLiteral("true", JsonValue(true), value);
|
|
if (ch == 'f')
|
|
return parseLiteral("false", JsonValue(false), value);
|
|
if (ch == 'n')
|
|
return parseLiteral("null", JsonValue(), value);
|
|
if (ch == '-' || std::isdigit(static_cast<unsigned char>(ch)))
|
|
return parseNumber(value);
|
|
|
|
setError("Unexpected token while parsing JSON.");
|
|
return false;
|
|
}
|
|
|
|
bool parseObject(JsonValue& value)
|
|
{
|
|
value = JsonValue::MakeObject();
|
|
++mPosition;
|
|
skipWhitespace();
|
|
if (consume('}'))
|
|
return true;
|
|
|
|
while (mPosition < mText.size())
|
|
{
|
|
std::string key;
|
|
if (!parseString(key))
|
|
return false;
|
|
|
|
skipWhitespace();
|
|
if (!consume(':'))
|
|
{
|
|
setError("Expected ':' after JSON object key.");
|
|
return false;
|
|
}
|
|
|
|
skipWhitespace();
|
|
JsonValue item;
|
|
if (!parseValue(item))
|
|
return false;
|
|
|
|
value.set(key, item);
|
|
|
|
skipWhitespace();
|
|
if (consume('}'))
|
|
return true;
|
|
if (!consume(','))
|
|
{
|
|
setError("Expected ',' or '}' in JSON object.");
|
|
return false;
|
|
}
|
|
skipWhitespace();
|
|
}
|
|
|
|
setError("Unexpected end of JSON object.");
|
|
return false;
|
|
}
|
|
|
|
bool parseArray(JsonValue& value)
|
|
{
|
|
value = JsonValue::MakeArray();
|
|
++mPosition;
|
|
skipWhitespace();
|
|
if (consume(']'))
|
|
return true;
|
|
|
|
while (mPosition < mText.size())
|
|
{
|
|
JsonValue item;
|
|
if (!parseValue(item))
|
|
return false;
|
|
|
|
value.pushBack(item);
|
|
|
|
skipWhitespace();
|
|
if (consume(']'))
|
|
return true;
|
|
if (!consume(','))
|
|
{
|
|
setError("Expected ',' or ']' in JSON array.");
|
|
return false;
|
|
}
|
|
skipWhitespace();
|
|
}
|
|
|
|
setError("Unexpected end of JSON array.");
|
|
return false;
|
|
}
|
|
|
|
bool parseString(std::string& value)
|
|
{
|
|
if (!consume('"'))
|
|
{
|
|
setError("Expected string literal.");
|
|
return false;
|
|
}
|
|
|
|
std::ostringstream result;
|
|
while (mPosition < mText.size())
|
|
{
|
|
char ch = mText[mPosition++];
|
|
if (ch == '"')
|
|
{
|
|
value = result.str();
|
|
return true;
|
|
}
|
|
|
|
if (ch == '\\')
|
|
{
|
|
if (mPosition >= mText.size())
|
|
{
|
|
setError("Unexpected end of escaped JSON string.");
|
|
return false;
|
|
}
|
|
|
|
char escaped = mText[mPosition++];
|
|
switch (escaped)
|
|
{
|
|
case '"': result << '"'; break;
|
|
case '\\': result << '\\'; break;
|
|
case '/': result << '/'; break;
|
|
case 'b': result << '\b'; break;
|
|
case 'f': result << '\f'; break;
|
|
case 'n': result << '\n'; break;
|
|
case 'r': result << '\r'; break;
|
|
case 't': result << '\t'; break;
|
|
case 'u':
|
|
setError("Unicode escape sequences are not supported in this JSON parser.");
|
|
return false;
|
|
default:
|
|
setError("Invalid escape sequence in JSON string.");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result << ch;
|
|
}
|
|
}
|
|
|
|
setError("Unexpected end of JSON string.");
|
|
return false;
|
|
}
|
|
|
|
bool parseNumber(JsonValue& value)
|
|
{
|
|
std::size_t start = mPosition;
|
|
|
|
if (mText[mPosition] == '-')
|
|
++mPosition;
|
|
|
|
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
|
++mPosition;
|
|
|
|
if (mPosition < mText.size() && mText[mPosition] == '.')
|
|
{
|
|
++mPosition;
|
|
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
|
++mPosition;
|
|
}
|
|
|
|
if (mPosition < mText.size() && (mText[mPosition] == 'e' || mText[mPosition] == 'E'))
|
|
{
|
|
++mPosition;
|
|
if (mPosition < mText.size() && (mText[mPosition] == '+' || mText[mPosition] == '-'))
|
|
++mPosition;
|
|
while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
|
|
++mPosition;
|
|
}
|
|
|
|
std::string token = mText.substr(start, mPosition - start);
|
|
char* endPtr = nullptr;
|
|
double parsed = strtod(token.c_str(), &endPtr);
|
|
if (endPtr == token.c_str() || *endPtr != '\0')
|
|
{
|
|
setError("Invalid JSON number.");
|
|
return false;
|
|
}
|
|
|
|
value = JsonValue(parsed);
|
|
return true;
|
|
}
|
|
|
|
bool parseLiteral(const char* literal, const JsonValue& literalValue, JsonValue& value)
|
|
{
|
|
std::size_t length = strlen(literal);
|
|
if (mText.compare(mPosition, length, literal) != 0)
|
|
{
|
|
setError("Invalid JSON literal.");
|
|
return false;
|
|
}
|
|
|
|
mPosition += length;
|
|
value = literalValue;
|
|
return true;
|
|
}
|
|
|
|
void skipWhitespace()
|
|
{
|
|
while (mPosition < mText.size() && std::isspace(static_cast<unsigned char>(mText[mPosition])))
|
|
++mPosition;
|
|
}
|
|
|
|
bool consume(char expected)
|
|
{
|
|
if (mPosition < mText.size() && mText[mPosition] == expected)
|
|
{
|
|
++mPosition;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void setError(const std::string& error)
|
|
{
|
|
if (mError.empty())
|
|
mError = error;
|
|
}
|
|
|
|
const std::string& mText;
|
|
std::string& mError;
|
|
std::size_t mPosition;
|
|
};
|
|
|
|
void SerializeJsonImpl(const JsonValue& value, std::ostringstream& output, bool pretty, int indentLevel)
|
|
{
|
|
auto indent = [&](int level) {
|
|
if (!pretty)
|
|
return;
|
|
for (int i = 0; i < level; ++i)
|
|
output << " ";
|
|
};
|
|
|
|
switch (value.type())
|
|
{
|
|
case JsonValue::Type::Null:
|
|
output << "null";
|
|
break;
|
|
case JsonValue::Type::Boolean:
|
|
output << (value.asBoolean() ? "true" : "false");
|
|
break;
|
|
case JsonValue::Type::Number:
|
|
{
|
|
double number = value.asNumber();
|
|
if (std::isfinite(number))
|
|
{
|
|
output << std::setprecision(15) << number;
|
|
}
|
|
else
|
|
{
|
|
output << "0";
|
|
}
|
|
break;
|
|
}
|
|
case JsonValue::Type::String:
|
|
{
|
|
output << '"';
|
|
for (char ch : value.asString())
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '"': output << "\\\""; break;
|
|
case '\\': output << "\\\\"; break;
|
|
case '\b': output << "\\b"; break;
|
|
case '\f': output << "\\f"; break;
|
|
case '\n': output << "\\n"; break;
|
|
case '\r': output << "\\r"; break;
|
|
case '\t': output << "\\t"; break;
|
|
default: output << ch; break;
|
|
}
|
|
}
|
|
output << '"';
|
|
break;
|
|
}
|
|
case JsonValue::Type::Array:
|
|
{
|
|
output << "[";
|
|
const std::vector<JsonValue>& array = value.asArray();
|
|
if (!array.empty())
|
|
{
|
|
if (pretty)
|
|
output << "\n";
|
|
for (std::size_t i = 0; i < array.size(); ++i)
|
|
{
|
|
indent(indentLevel + 1);
|
|
SerializeJsonImpl(array[i], output, pretty, indentLevel + 1);
|
|
if (i + 1 != array.size())
|
|
output << ",";
|
|
if (pretty)
|
|
output << "\n";
|
|
}
|
|
indent(indentLevel);
|
|
}
|
|
output << "]";
|
|
break;
|
|
}
|
|
case JsonValue::Type::Object:
|
|
{
|
|
output << "{";
|
|
const std::map<std::string, JsonValue>& object = value.asObject();
|
|
if (!object.empty())
|
|
{
|
|
if (pretty)
|
|
output << "\n";
|
|
std::size_t index = 0;
|
|
for (const auto& item : object)
|
|
{
|
|
indent(indentLevel + 1);
|
|
SerializeJsonImpl(JsonValue(item.first), output, pretty, indentLevel + 1);
|
|
output << (pretty ? ": " : ":");
|
|
SerializeJsonImpl(item.second, output, pretty, indentLevel + 1);
|
|
if (++index != object.size())
|
|
output << ",";
|
|
if (pretty)
|
|
output << "\n";
|
|
}
|
|
indent(indentLevel);
|
|
}
|
|
output << "}";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
JsonValue::JsonValue()
|
|
: mType(Type::Null), mBooleanValue(false), mNumberValue(0.0)
|
|
{
|
|
}
|
|
|
|
JsonValue::JsonValue(bool value)
|
|
: mType(Type::Boolean), mBooleanValue(value), mNumberValue(0.0)
|
|
{
|
|
}
|
|
|
|
JsonValue::JsonValue(double value)
|
|
: mType(Type::Number), mBooleanValue(false), mNumberValue(value)
|
|
{
|
|
}
|
|
|
|
JsonValue::JsonValue(const char* value)
|
|
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value ? value : "")
|
|
{
|
|
}
|
|
|
|
JsonValue::JsonValue(const std::string& value)
|
|
: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value)
|
|
{
|
|
}
|
|
|
|
JsonValue JsonValue::MakeArray()
|
|
{
|
|
JsonValue value;
|
|
value.mType = Type::Array;
|
|
return value;
|
|
}
|
|
|
|
JsonValue JsonValue::MakeObject()
|
|
{
|
|
JsonValue value;
|
|
value.mType = Type::Object;
|
|
return value;
|
|
}
|
|
|
|
bool JsonValue::asBoolean(bool fallback) const
|
|
{
|
|
return mType == Type::Boolean ? mBooleanValue : fallback;
|
|
}
|
|
|
|
double JsonValue::asNumber(double fallback) const
|
|
{
|
|
return mType == Type::Number ? mNumberValue : fallback;
|
|
}
|
|
|
|
const std::string& JsonValue::asString() const
|
|
{
|
|
static const std::string emptyString;
|
|
return mType == Type::String ? mStringValue : emptyString;
|
|
}
|
|
|
|
const std::vector<JsonValue>& JsonValue::asArray() const
|
|
{
|
|
static const std::vector<JsonValue> emptyArray;
|
|
return mType == Type::Array ? mArrayValue : emptyArray;
|
|
}
|
|
|
|
const std::map<std::string, JsonValue>& JsonValue::asObject() const
|
|
{
|
|
static const std::map<std::string, JsonValue> emptyObject;
|
|
return mType == Type::Object ? mObjectValue : emptyObject;
|
|
}
|
|
|
|
std::vector<JsonValue>& JsonValue::array()
|
|
{
|
|
if (mType != Type::Array)
|
|
{
|
|
mType = Type::Array;
|
|
mArrayValue.clear();
|
|
}
|
|
return mArrayValue;
|
|
}
|
|
|
|
std::map<std::string, JsonValue>& JsonValue::object()
|
|
{
|
|
if (mType != Type::Object)
|
|
{
|
|
mType = Type::Object;
|
|
mObjectValue.clear();
|
|
}
|
|
return mObjectValue;
|
|
}
|
|
|
|
void JsonValue::pushBack(const JsonValue& value)
|
|
{
|
|
array().push_back(value);
|
|
}
|
|
|
|
void JsonValue::set(const std::string& key, const JsonValue& value)
|
|
{
|
|
object()[key] = value;
|
|
}
|
|
|
|
const JsonValue* JsonValue::find(const std::string& key) const
|
|
{
|
|
if (mType != Type::Object)
|
|
return nullptr;
|
|
|
|
auto iterator = mObjectValue.find(key);
|
|
return iterator != mObjectValue.end() ? &iterator->second : nullptr;
|
|
}
|
|
|
|
bool ParseJson(const std::string& text, JsonValue& value, std::string& error)
|
|
{
|
|
error.clear();
|
|
JsonParser parser(text, error);
|
|
return parser.parse(value);
|
|
}
|
|
|
|
std::string SerializeJson(const JsonValue& value, bool pretty)
|
|
{
|
|
std::ostringstream output;
|
|
SerializeJsonImpl(value, output, pretty, 0);
|
|
return output.str();
|
|
}
|