2023-06-20 04:33:09 +00:00
|
|
|
#include "StarFormattedJson.hpp"
|
|
|
|
#include "StarJsonBuilder.hpp"
|
|
|
|
#include "StarLexicalCast.hpp"
|
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
class FormattedJsonBuilderStream : public JsonStream {
|
|
|
|
public:
|
|
|
|
virtual void beginObject();
|
|
|
|
virtual void objectKey(String::Char const* s, size_t len);
|
|
|
|
virtual void endObject();
|
|
|
|
|
|
|
|
virtual void beginArray();
|
|
|
|
virtual void endArray();
|
|
|
|
|
|
|
|
virtual void putString(String::Char const* s, size_t len);
|
|
|
|
virtual void putDouble(String::Char const* s, size_t len);
|
|
|
|
virtual void putInteger(String::Char const* s, size_t len);
|
|
|
|
virtual void putBoolean(bool b);
|
|
|
|
virtual void putNull();
|
|
|
|
|
|
|
|
virtual void putWhitespace(String::Char const* s, size_t len);
|
|
|
|
virtual void putComma();
|
|
|
|
virtual void putColon();
|
|
|
|
|
|
|
|
FormattedJson takeTop();
|
|
|
|
|
|
|
|
private:
|
|
|
|
void push(FormattedJson const& v);
|
|
|
|
FormattedJson pop();
|
|
|
|
FormattedJson& current();
|
|
|
|
void putValue(Json const& value, Maybe<String> formatting = {});
|
|
|
|
|
|
|
|
Maybe<FormattedJson> m_root;
|
|
|
|
List<FormattedJson> m_stack;
|
|
|
|
};
|
|
|
|
|
|
|
|
template <>
|
|
|
|
class JsonStreamer<FormattedJson> {
|
|
|
|
public:
|
|
|
|
static void toJsonStream(FormattedJson const& val, JsonStream& stream, bool sort);
|
|
|
|
};
|
|
|
|
|
|
|
|
ValueElement::ValueElement(FormattedJson const& json) : value(make_shared<FormattedJson>(json)) {}
|
|
|
|
|
|
|
|
bool ValueElement::operator==(ValueElement const& v) const {
|
|
|
|
return *value == *v.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ObjectKeyElement::operator==(ObjectKeyElement const& v) const {
|
|
|
|
return key == v.key;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WhitespaceElement::operator==(WhitespaceElement const& v) const {
|
|
|
|
return whitespace == v.whitespace;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ColonElement::operator==(ColonElement const&) const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CommaElement::operator==(CommaElement const&) const {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::parse(String const& string) {
|
|
|
|
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
|
2024-04-21 20:07:59 +00:00
|
|
|
string.begin(), string.end(), JsonParseType::Value);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::parseJson(String const& string) {
|
|
|
|
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
|
2024-04-21 20:07:59 +00:00
|
|
|
string.begin(), string.end(), JsonParseType::Top);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::ofType(Json::Type type) {
|
|
|
|
FormattedJson json;
|
|
|
|
json.m_jsonValue = Json::ofType(type);
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson::FormattedJson() : FormattedJson(Json()) {}
|
|
|
|
|
|
|
|
FormattedJson::FormattedJson(Json const& json)
|
|
|
|
: m_jsonValue(Json::ofType(json.type())),
|
|
|
|
m_elements(),
|
|
|
|
m_formatting(),
|
|
|
|
m_lastKey(),
|
|
|
|
m_objectEntryLocations(),
|
|
|
|
m_arrayElementLocations() {
|
|
|
|
if (json.type() == Json::Type::Object || json.type() == Json::Type::Array) {
|
|
|
|
FormattedJsonBuilderStream stream;
|
|
|
|
JsonStreamer<Json>::toJsonStream(json, stream, false);
|
|
|
|
FormattedJson parsed = stream.takeTop();
|
|
|
|
for (JsonElement const& elem : parsed.elements()) {
|
|
|
|
appendElement(elem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m_jsonValue = json;
|
|
|
|
}
|
|
|
|
|
|
|
|
Json const& FormattedJson::toJson() const {
|
|
|
|
return m_jsonValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::get(String const& key) const {
|
|
|
|
if (type() != Json::Type::Object)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call get with key on FormattedJson type {}, must be Object type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
Maybe<pair<ElementLocation, ElementLocation>> entry = m_objectEntryLocations.maybe(key);
|
|
|
|
if (entry.isNothing())
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("No such key in FormattedJson::get(\"{}\")", key);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
return getFormattedJson(entry->second);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::get(size_t index) const {
|
|
|
|
if (type() != Json::Type::Array)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call get with index on FormattedJson type {}, must be Array type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (index >= m_arrayElementLocations.size())
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("FormattedJson::get({}) out of range", index);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
ElementLocation loc = m_arrayElementLocations.at(index);
|
|
|
|
return getFormattedJson(loc);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct WhitespaceStyle {
|
|
|
|
String beforeKey;
|
|
|
|
String beforeColon;
|
|
|
|
String beforeValue;
|
|
|
|
String beforeComma;
|
|
|
|
};
|
|
|
|
|
|
|
|
template <class ElementType>
|
|
|
|
FormattedJson::ElementLocation indexOf(FormattedJson::ElementList const& elements, FormattedJson::ElementLocation pos) {
|
|
|
|
for (; pos < elements.size(); ++pos) {
|
|
|
|
if (elements[pos].is<ElementType>())
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
return NPos;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class ElementType>
|
|
|
|
FormattedJson::ElementLocation lastIndexOf(
|
|
|
|
FormattedJson::ElementList const& elements, FormattedJson::ElementLocation pos) {
|
|
|
|
while (pos > 0) {
|
|
|
|
--pos;
|
|
|
|
if (elements[pos].is<ElementType>())
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
return NPos;
|
|
|
|
}
|
|
|
|
|
|
|
|
String concatWhitespace(FormattedJson::ElementList const& elements, FormattedJson::ElementLocation from,
|
|
|
|
FormattedJson::ElementLocation to) {
|
|
|
|
String whitespace;
|
|
|
|
for (JsonElement const& elem : elements.slice(from, to)) {
|
|
|
|
if (elem.is<WhitespaceElement>())
|
|
|
|
whitespace += elem.get<WhitespaceElement>().whitespace;
|
|
|
|
}
|
|
|
|
return whitespace;
|
|
|
|
}
|
|
|
|
|
|
|
|
WhitespaceStyle detectWhitespace(FormattedJson::ElementList const& elements,
|
|
|
|
FormattedJson::ElementLocation insertLoc, bool array) {
|
|
|
|
WhitespaceStyle style;
|
|
|
|
|
|
|
|
// Find a nearby value as a reference location to learn whitespace from.
|
|
|
|
FormattedJson::ElementLocation valueLoc = lastIndexOf<ValueElement>(elements, insertLoc);
|
|
|
|
if (valueLoc == NPos)
|
|
|
|
valueLoc = indexOf<ValueElement>(elements, insertLoc);
|
|
|
|
|
|
|
|
if (valueLoc == NPos) {
|
|
|
|
// This object/array is empty. Pre-key/value whitespace will be the total of
|
|
|
|
// the whitespace already present, plus some guessed indentation if it
|
|
|
|
// contained a newline.
|
|
|
|
String beforeValue = concatWhitespace(elements, 0, elements.size());
|
|
|
|
if (beforeValue.find('\n') != NPos)
|
|
|
|
beforeValue += " ";
|
|
|
|
if (array)
|
|
|
|
return WhitespaceStyle{"", "", beforeValue, ""};
|
|
|
|
return WhitespaceStyle{beforeValue, "", "", ""};
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson::ElementLocation commaLoc = indexOf<CommaElement>(elements, valueLoc);
|
|
|
|
if (commaLoc != NPos) {
|
|
|
|
style.beforeComma = concatWhitespace(elements, valueLoc + 1, commaLoc);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson::ElementLocation colonLoc = lastIndexOf<ColonElement>(elements, valueLoc);
|
|
|
|
starAssert((colonLoc == NPos) == array);
|
|
|
|
if (colonLoc != NPos) {
|
|
|
|
style.beforeValue = concatWhitespace(elements, colonLoc + 1, valueLoc);
|
|
|
|
|
|
|
|
FormattedJson::ElementLocation keyLoc = lastIndexOf<ObjectKeyElement>(elements, colonLoc);
|
|
|
|
starAssert(keyLoc != NPos);
|
|
|
|
style.beforeColon = concatWhitespace(elements, keyLoc + 1, colonLoc);
|
|
|
|
|
|
|
|
FormattedJson::ElementLocation prevValueLoc = lastIndexOf<ValueElement>(elements, keyLoc);
|
|
|
|
if (prevValueLoc == NPos)
|
|
|
|
prevValueLoc = 0;
|
|
|
|
style.beforeKey = concatWhitespace(elements, prevValueLoc, keyLoc);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
FormattedJson::ElementLocation prevValueLoc = lastIndexOf<ValueElement>(elements, valueLoc);
|
|
|
|
if (prevValueLoc == NPos)
|
|
|
|
prevValueLoc = 0;
|
|
|
|
style.beforeValue = concatWhitespace(elements, prevValueLoc, valueLoc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return style;
|
|
|
|
}
|
|
|
|
|
|
|
|
void insertWhitespace(FormattedJson::ElementList& destination, FormattedJson::ElementLocation& at, String const& whitespace) {
|
|
|
|
if (whitespace == "")
|
|
|
|
return;
|
|
|
|
destination.insertAt(at++, WhitespaceElement{whitespace});
|
|
|
|
}
|
|
|
|
|
|
|
|
void insertWithWhitespace(FormattedJson::ElementList& destination, WhitespaceStyle const& style,
|
|
|
|
FormattedJson::ElementLocation& at, JsonElement const& element) {
|
|
|
|
if (element.is<ValueElement>())
|
|
|
|
insertWhitespace(destination, at, style.beforeValue);
|
|
|
|
if (element.is<ObjectKeyElement>())
|
|
|
|
insertWhitespace(destination, at, style.beforeKey);
|
|
|
|
if (element.is<ColonElement>())
|
|
|
|
insertWhitespace(destination, at, style.beforeColon);
|
|
|
|
if (element.is<CommaElement>())
|
|
|
|
insertWhitespace(destination, at, style.beforeComma);
|
|
|
|
destination.insertAt(at++, element);
|
|
|
|
}
|
|
|
|
|
|
|
|
void insertWithCommaAndFormatting(FormattedJson::ElementList& destination, FormattedJson::ElementLocation at,
|
|
|
|
bool array, FormattedJson::ElementList const& elements) {
|
|
|
|
// Find the previous value we're inserting after, if any.
|
|
|
|
at = lastIndexOf<ValueElement>(destination, at);
|
|
|
|
if (at == NPos)
|
|
|
|
at = 0;
|
|
|
|
else
|
|
|
|
at += 1;
|
|
|
|
bool empty = lastIndexOf<ValueElement>(destination, destination.size()) == NPos;
|
|
|
|
bool appendComma = at == 0 && !empty;
|
|
|
|
bool prependComma = !appendComma && !empty;
|
|
|
|
|
|
|
|
WhitespaceStyle style = detectWhitespace(destination, at, array);
|
|
|
|
|
|
|
|
if (prependComma) {
|
|
|
|
// Inserting into a non-empty object/array. Prepend a comma
|
|
|
|
insertWithWhitespace(destination, style, at, CommaElement{});
|
|
|
|
}
|
|
|
|
for (JsonElement const& elem : elements) {
|
|
|
|
insertWithWhitespace(destination, style, at, elem);
|
|
|
|
}
|
|
|
|
if (appendComma) {
|
|
|
|
insertWithWhitespace(destination, style, at, CommaElement{});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::prepend(String const& key, FormattedJson const& value) const {
|
|
|
|
return objectInsert(key, value, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::insertBefore(String const& key, FormattedJson const& value, String const& beforeKey) const {
|
|
|
|
if (!m_objectEntryLocations.contains(beforeKey))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot insert before key \"{}\", which does not exist", beforeKey);
|
2023-06-20 04:33:09 +00:00
|
|
|
ElementLocation loc = m_objectEntryLocations.get(beforeKey).first;
|
|
|
|
return objectInsert(key, value, loc);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::insertAfter(String const& key, FormattedJson const& value, String const& afterKey) const {
|
|
|
|
if (!m_objectEntryLocations.contains(afterKey))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot insert after key \"{}\", which does not exist", afterKey);
|
2023-06-20 04:33:09 +00:00
|
|
|
ElementLocation loc = m_objectEntryLocations.get(afterKey).second;
|
|
|
|
return objectInsert(key, value, loc + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::append(String const& key, FormattedJson const& value) const {
|
|
|
|
return objectInsert(key, value, m_elements.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::set(String const& key, FormattedJson const& value) const {
|
|
|
|
return objectInsert(key, value, m_elements.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
void removeValueFromArray(List<JsonElement>& elements, size_t loc) {
|
|
|
|
// Remove the value itself, the comma following and the whitespace up to the
|
|
|
|
// next value.
|
|
|
|
// If it's the last value, it removes the value, and the preceding whitespace
|
|
|
|
// and comma.
|
|
|
|
size_t commaLoc = elements.indexOf(CommaElement{}, loc);
|
|
|
|
if (commaLoc != NPos) {
|
|
|
|
elements.eraseAt(loc, commaLoc + 1);
|
|
|
|
while (loc < elements.size() && elements.at(loc).is<WhitespaceElement>())
|
|
|
|
elements.eraseAt(loc);
|
|
|
|
} else {
|
|
|
|
commaLoc = elements.lastIndexOf(CommaElement{}, loc);
|
|
|
|
if (commaLoc == NPos)
|
|
|
|
commaLoc = 0;
|
|
|
|
elements.eraseAt(commaLoc, loc + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::eraseKey(String const& key) const {
|
|
|
|
if (type() != Json::Type::Object)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call erase with key on FormattedJson type {}, must be Object type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
Maybe<pair<ElementLocation, ElementLocation>> maybeEntry = m_objectEntryLocations.maybe(key);
|
|
|
|
if (maybeEntry.isNothing())
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
ElementLocation loc = maybeEntry->first;
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
elements.eraseAt(loc, maybeEntry->second); // Remove key, colon and whitespace up to the value
|
|
|
|
removeValueFromArray(elements, loc);
|
|
|
|
return object(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::insert(size_t index, FormattedJson const& value) const {
|
|
|
|
if (type() != Json::Type::Array)
|
|
|
|
throw JsonException::format(
|
2023-06-27 10:23:44 +00:00
|
|
|
"Cannot call insert with index on FormattedJson type {}, must be Array type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (index > m_arrayElementLocations.size())
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("FormattedJson::insert({}) out of range", index);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
ElementLocation insertPosition = elements.size();
|
|
|
|
if (index < m_arrayElementLocations.size())
|
|
|
|
insertPosition = m_arrayElementLocations.at(index);
|
|
|
|
|
|
|
|
insertWithCommaAndFormatting(elements, insertPosition, true, {ValueElement{value}});
|
|
|
|
return array(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::append(FormattedJson const& value) const {
|
|
|
|
if (type() != Json::Type::Array)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call append on FormattedJson type {}, must be Array type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
insertWithCommaAndFormatting(elements, elements.size(), true, {ValueElement{value}});
|
|
|
|
return array(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::set(size_t index, FormattedJson const& value) const {
|
|
|
|
if (type() != Json::Type::Array)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call set with index on FormattedJson type {}, must be Array type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (index >= m_arrayElementLocations.size())
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("FormattedJson::set({}) out of range", index);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
ElementLocation loc = m_arrayElementLocations.at(index);
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
elements.at(loc) = ValueElement{value};
|
|
|
|
return array(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::eraseIndex(size_t index) const {
|
|
|
|
if (type() != Json::Type::Array)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call set with index on FormattedJson type {}, must be Array type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
if (index >= m_arrayElementLocations.size())
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("FormattedJson::eraseIndex({}) out of range", index);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
ElementLocation loc = m_arrayElementLocations.at(index);
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
removeValueFromArray(elements, loc);
|
|
|
|
return array(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FormattedJson::size() const {
|
|
|
|
return m_jsonValue.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormattedJson::contains(String const& key) const {
|
|
|
|
return m_jsonValue.contains(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
Json::Type FormattedJson::type() const {
|
|
|
|
return m_jsonValue.type();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormattedJson::isType(Json::Type type) const {
|
|
|
|
return m_jsonValue.isType(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
String FormattedJson::typeName() const {
|
|
|
|
return m_jsonValue.typeName();
|
|
|
|
}
|
|
|
|
|
|
|
|
String FormattedJson::toFormattedDouble() const {
|
|
|
|
if (!isType(Json::Type::Float))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call toFormattedDouble on Json type {}, must be Float", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
if (m_formatting.isValid())
|
|
|
|
return *m_formatting;
|
|
|
|
return toJson().repr();
|
|
|
|
}
|
|
|
|
|
|
|
|
String FormattedJson::toFormattedInt() const {
|
|
|
|
if (!isType(Json::Type::Int))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call toFormattedInt on Json type {}, must be Int", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
if (m_formatting.isValid())
|
|
|
|
return *m_formatting;
|
|
|
|
return toJson().repr();
|
|
|
|
}
|
|
|
|
|
|
|
|
String FormattedJson::repr() const {
|
|
|
|
if (m_formatting.isValid())
|
|
|
|
return *m_formatting;
|
|
|
|
String result;
|
|
|
|
outputUtf32Json<std::back_insert_iterator<String>, FormattedJson>(*this, std::back_inserter(result), 0, false);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
String FormattedJson::printJson() const {
|
|
|
|
if (type() != Json::Type::Object && type() != Json::Type::Array)
|
|
|
|
throw JsonException("printJson called on non-top-level JSON type");
|
|
|
|
return repr();
|
|
|
|
}
|
|
|
|
|
|
|
|
Json elemToJson(JsonElement const& elem) {
|
|
|
|
return elem.get<ValueElement>().value->toJson();
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson::ElementList const& FormattedJson::elements() const {
|
|
|
|
return m_elements;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormattedJson::operator==(FormattedJson const& v) const {
|
|
|
|
return m_jsonValue == v.m_jsonValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FormattedJson::operator!=(FormattedJson const& v) const {
|
|
|
|
return !(*this == v);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::object(ElementList const& elements) {
|
|
|
|
FormattedJson json = ofType(Json::Type::Object);
|
|
|
|
for (JsonElement const& elem : elements) {
|
|
|
|
json.appendElement(elem);
|
|
|
|
}
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::array(ElementList const& elements) {
|
|
|
|
FormattedJson json = ofType(Json::Type::Array);
|
|
|
|
for (JsonElement const& elem : elements) {
|
|
|
|
if (elem.is<ColonElement>() || elem.is<ObjectKeyElement>())
|
|
|
|
throw JsonException("Invalid FormattedJson element in Json array");
|
|
|
|
json.appendElement(elem);
|
|
|
|
}
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::objectInsert(String const& key, FormattedJson const& value, ElementLocation loc) const {
|
|
|
|
if (type() != Json::Type::Object)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw JsonException::format("Cannot call set with key on FormattedJson type {}, must be Object type", typeName());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
Maybe<pair<ElementLocation, ElementLocation>> maybeEntry = m_objectEntryLocations.maybe(key);
|
|
|
|
if (maybeEntry.isValid()) {
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
elements.at(maybeEntry->second) = ValueElement{value};
|
|
|
|
return object(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
ElementList elements = m_elements;
|
|
|
|
insertWithCommaAndFormatting(elements, loc, false, {ObjectKeyElement{key}, ColonElement{}, ValueElement{value}});
|
|
|
|
return object(elements);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJson::appendElement(JsonElement const& elem) {
|
|
|
|
ElementLocation loc = m_elements.size();
|
|
|
|
m_elements.append(elem);
|
|
|
|
|
|
|
|
if (elem.is<ObjectKeyElement>()) {
|
|
|
|
starAssert(isType(Json::Type::Object));
|
|
|
|
m_lastKey = loc;
|
|
|
|
|
|
|
|
} else if (elem.is<ValueElement>()) {
|
|
|
|
m_lastValue = loc;
|
|
|
|
|
|
|
|
if (m_lastKey.isValid()) {
|
|
|
|
starAssert(isType(Json::Type::Object));
|
|
|
|
String key = m_elements[*m_lastKey].get<ObjectKeyElement>().key;
|
|
|
|
|
|
|
|
m_objectEntryLocations[key] = make_pair(*m_lastKey, loc);
|
|
|
|
m_jsonValue = m_jsonValue.set(key, elemToJson(elem));
|
|
|
|
|
|
|
|
m_lastKey = {};
|
|
|
|
} else {
|
|
|
|
starAssert(isType(Json::Type::Array));
|
|
|
|
m_arrayElementLocations.append(loc);
|
|
|
|
|
|
|
|
m_jsonValue = m_jsonValue.append(elemToJson(elem));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson const& FormattedJson::getFormattedJson(ElementLocation loc) const {
|
|
|
|
return *m_elements[loc].get<ValueElement>().value;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJson::formattedAs(String const& formatting) const {
|
|
|
|
starAssert(Json::parse(formatting) == toJson());
|
|
|
|
FormattedJson json = *this;
|
|
|
|
json.m_formatting = formatting;
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::beginObject() {
|
|
|
|
FormattedJson value = FormattedJson::ofType(Json::Type::Object);
|
|
|
|
push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::objectKey(String::Char const* s, size_t len) {
|
|
|
|
current().appendElement(ObjectKeyElement{String(s, len)});
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::endObject() {
|
|
|
|
FormattedJson value = pop();
|
|
|
|
if (m_stack.size() > 0)
|
|
|
|
current().appendElement(ValueElement{value});
|
|
|
|
else
|
|
|
|
m_root = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::beginArray() {
|
|
|
|
FormattedJson value = FormattedJson::ofType(Json::Type::Array);
|
|
|
|
push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::endArray() {
|
|
|
|
FormattedJson value = pop();
|
|
|
|
if (m_stack.size() > 0)
|
|
|
|
current().appendElement(ValueElement{value});
|
|
|
|
else
|
|
|
|
m_root = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putString(String::Char const* s, size_t len) {
|
|
|
|
putValue(String(s, len));
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putDouble(String::Char const* s, size_t len) {
|
|
|
|
String formatted(s, len);
|
|
|
|
double d = lexicalCast<double>(formatted);
|
|
|
|
putValue(d, formatted);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putInteger(String::Char const* s, size_t len) {
|
|
|
|
String formatted(s, len);
|
|
|
|
long long d = lexicalCast<long long>(formatted);
|
|
|
|
putValue(d, formatted);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putBoolean(bool b) {
|
|
|
|
putValue(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putNull() {
|
|
|
|
putValue(Json::ofType(Json::Type::Null));
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putWhitespace(String::Char const* s, size_t len) {
|
|
|
|
if (m_stack.size() > 0)
|
|
|
|
current().appendElement(WhitespaceElement{String(s, len)});
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putColon() {
|
|
|
|
current().appendElement(ColonElement{});
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putComma() {
|
|
|
|
current().appendElement(CommaElement{});
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJsonBuilderStream::takeTop() {
|
|
|
|
return m_root.take();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::push(FormattedJson const& v) {
|
|
|
|
m_stack.push_back(v);
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson FormattedJsonBuilderStream::pop() {
|
|
|
|
FormattedJson result = m_stack.back();
|
|
|
|
m_stack.pop_back();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
FormattedJson& FormattedJsonBuilderStream::current() {
|
|
|
|
return m_stack.back();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FormattedJsonBuilderStream::putValue(Json const& value, Maybe<String> formatting) {
|
|
|
|
FormattedJson formattedValue = value;
|
|
|
|
if (formatting.isValid())
|
|
|
|
formattedValue = formattedValue.formattedAs(*formatting);
|
|
|
|
|
|
|
|
if (m_stack.size() > 0)
|
|
|
|
current().appendElement(ValueElement{formattedValue});
|
|
|
|
else {
|
|
|
|
m_root = formattedValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JsonStreamer<FormattedJson>::toJsonStream(FormattedJson const& val, JsonStream& stream, bool sort) {
|
|
|
|
if (val.isType(Json::Type::Object))
|
|
|
|
stream.beginObject();
|
|
|
|
|
|
|
|
else if (val.isType(Json::Type::Array))
|
|
|
|
stream.beginArray();
|
|
|
|
|
|
|
|
else if (val.isType(Json::Type::Float)) {
|
|
|
|
// Float and Int are to be formatted the same way they were parsed to
|
|
|
|
// preserve, e.g. negative zeroes and trailing 0 digits on decimals.
|
|
|
|
auto ws = val.toFormattedDouble().wideString();
|
|
|
|
stream.putDouble(ws.c_str(), ws.length());
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else if (val.isType(Json::Type::Int)) {
|
|
|
|
auto ws = val.toFormattedInt().wideString();
|
|
|
|
stream.putInteger(ws.c_str(), ws.length());
|
|
|
|
return;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// If val is not an object, array or number, it has no formatting and no
|
|
|
|
// elements. Stream the wrapped Json value the usual way.
|
|
|
|
JsonStreamer<Json>::toJsonStream(val.toJson(), stream, sort);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (JsonElement elem : val.elements()) {
|
|
|
|
if (elem.is<ObjectKeyElement>()) {
|
|
|
|
String::WideString key = elem.get<ObjectKeyElement>().key.wideString();
|
|
|
|
stream.objectKey(key.c_str(), key.length());
|
|
|
|
} else if (elem.is<WhitespaceElement>()) {
|
|
|
|
String::WideString white = elem.get<WhitespaceElement>().whitespace.wideString();
|
|
|
|
stream.putWhitespace(white.c_str(), white.length());
|
|
|
|
} else if (elem.is<ColonElement>()) {
|
|
|
|
stream.putColon();
|
|
|
|
} else if (elem.is<CommaElement>()) {
|
|
|
|
stream.putComma();
|
|
|
|
} else {
|
|
|
|
toJsonStream(*elem.get<ValueElement>().value, stream, sort);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val.isType(Json::Type::Object))
|
|
|
|
stream.endObject();
|
|
|
|
if (val.isType(Json::Type::Array))
|
|
|
|
stream.endArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, JsonElement const& elem) {
|
|
|
|
if (elem.is<ValueElement>())
|
|
|
|
return os << "ValueElement{" << elem.get<ValueElement>().value << "}";
|
|
|
|
if (elem.is<ObjectKeyElement>())
|
|
|
|
return os << "ObjectKeyElement{" << elem.get<ObjectKeyElement>().key << "}";
|
|
|
|
if (elem.is<WhitespaceElement>())
|
|
|
|
return os << "WhitespaceElement{" << elem.get<WhitespaceElement>().whitespace << "}";
|
|
|
|
if (elem.is<ColonElement>())
|
|
|
|
return os << "ColonElement{}";
|
|
|
|
if (elem.is<CommaElement>())
|
|
|
|
return os << "CommaElement{}";
|
|
|
|
starAssert(false);
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, FormattedJson const& json) {
|
|
|
|
return os << json.repr();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|