aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/json/writer
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/json/writer
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/json/writer')
-rw-r--r--library/cpp/json/writer/README23
-rw-r--r--library/cpp/json/writer/json.cpp517
-rw-r--r--library/cpp/json/writer/json.h289
-rw-r--r--library/cpp/json/writer/json_ut.cpp307
-rw-r--r--library/cpp/json/writer/json_value.cpp1105
-rw-r--r--library/cpp/json/writer/json_value.h294
-rw-r--r--library/cpp/json/writer/json_value_ut.cpp650
-rw-r--r--library/cpp/json/writer/ut/ya.make16
-rw-r--r--library/cpp/json/writer/ya.make20
9 files changed, 3221 insertions, 0 deletions
diff --git a/library/cpp/json/writer/README b/library/cpp/json/writer/README
new file mode 100644
index 0000000000..a20489f32e
--- /dev/null
+++ b/library/cpp/json/writer/README
@@ -0,0 +1,23 @@
+JSON writer with no external dependencies, producing output
+where HTML special characters are always escaped.
+
+Use it like this:
+
+ #include <library/cpp/json/writer/json.h>
+ ...
+
+ NJsonWriter::TBuf json;
+ json.BeginList()
+ .WriteString("<script>")
+ .EndList();
+ Cout << json.Str(); // output: ["\u003Cscript\u003E"]
+
+For compatibility with legacy formats where object keys
+are not quoted, use CompatWriteKeyWithoutQuotes:
+
+ NJsonWriter::TBuf json;
+ json.BeginObject()
+ .CompatWriteKeyWithoutQuotes("r").WriteInt(1)
+ .CompatWriteKeyWithoutQuotes("n").WriteInt(0)
+ .EndObject();
+ Cout << json.Str(); // output: {r:1,n:0}
diff --git a/library/cpp/json/writer/json.cpp b/library/cpp/json/writer/json.cpp
new file mode 100644
index 0000000000..02370c2d79
--- /dev/null
+++ b/library/cpp/json/writer/json.cpp
@@ -0,0 +1,517 @@
+#include "json.h"
+
+#include <library/cpp/json/json_value.h>
+
+#include <util/string/cast.h>
+#include <util/string/strspn.h>
+#include <util/generic/algorithm.h>
+#include <util/generic/ymath.h>
+#include <util/generic/singleton.h>
+
+namespace NJsonWriter {
+ TBuf::TBuf(EHtmlEscapeMode mode, IOutputStream* stream)
+ : Stream(stream)
+ , NeedComma(false)
+ , NeedNewline(false)
+ , EscapeMode(mode)
+ , IndentSpaces(0)
+ , WriteNanAsString(false)
+ {
+ Y_ASSERT(mode == HEM_DONT_ESCAPE_HTML ||
+ mode == HEM_ESCAPE_HTML ||
+ mode == HEM_RELAXED ||
+ mode == HEM_UNSAFE);
+ if (!Stream) {
+ StringStream.Reset(new TStringStream);
+ Stream = StringStream.Get();
+ }
+
+ Stack.reserve(64); // should be enough for most cases
+ StackPush(JE_OUTER_SPACE);
+ }
+
+ static const char* EntityToStr(EJsonEntity e) {
+ switch (e) {
+ case JE_OUTER_SPACE:
+ return "JE_OUTER_SPACE";
+ case JE_LIST:
+ return "JE_LIST";
+ case JE_OBJECT:
+ return "JE_OBJECT";
+ case JE_PAIR:
+ return "JE_PAIR";
+ default:
+ return "JE_unknown";
+ }
+ }
+
+ inline void TBuf::StackPush(EJsonEntity e) {
+ Stack.push_back(e);
+ }
+
+ inline EJsonEntity TBuf::StackTop() const {
+ return Stack.back();
+ }
+
+ inline void TBuf::StackPop() {
+ Y_ASSERT(!Stack.empty());
+ const EJsonEntity current = StackTop();
+ Stack.pop_back();
+ switch (current) {
+ case JE_OUTER_SPACE:
+ ythrow TError() << "JSON writer: stack empty";
+ case JE_LIST:
+ PrintIndentation(true);
+ RawWriteChar(']');
+ break;
+ case JE_OBJECT:
+ PrintIndentation(true);
+ RawWriteChar('}');
+ break;
+ case JE_PAIR:
+ break;
+ }
+ NeedComma = true;
+ NeedNewline = true;
+ }
+
+ inline void TBuf::CheckAndPop(EJsonEntity e) {
+ if (Y_UNLIKELY(StackTop() != e)) {
+ ythrow TError() << "JSON writer: unexpected value "
+ << EntityToStr(StackTop()) << " on the stack";
+ }
+ StackPop();
+ }
+
+ void TBuf::PrintIndentation(bool closing) {
+ if (!IndentSpaces)
+ return;
+ const int indentation = IndentSpaces * (Stack.size() - 1);
+ if (!indentation && !closing)
+ return;
+
+ PrintWhitespaces(Max(0, indentation), true);
+ }
+
+ void TBuf::PrintWhitespaces(size_t count, bool prependWithNewLine) {
+ static constexpr TStringBuf whitespacesTemplate = "\n ";
+ static_assert(whitespacesTemplate[0] == '\n');
+ static_assert(whitespacesTemplate[1] == ' ');
+
+ count += (prependWithNewLine);
+ do {
+ const TStringBuf buffer = whitespacesTemplate.SubString(prependWithNewLine ? 0 : 1, count);
+ count -= buffer.size();
+ UnsafeWriteRawBytes(buffer);
+ prependWithNewLine = false; // skip '\n' in subsequent writes
+ } while (count > 0);
+ }
+
+ inline void TBuf::WriteComma() {
+ if (NeedComma) {
+ RawWriteChar(',');
+ }
+ NeedComma = true;
+
+ if (NeedNewline) {
+ PrintIndentation(false);
+ }
+ NeedNewline = true;
+ }
+
+ inline void TBuf::BeginValue() {
+ if (Y_UNLIKELY(KeyExpected())) {
+ ythrow TError() << "JSON writer: value written, "
+ "but expected a key:value pair";
+ }
+ WriteComma();
+ }
+
+ inline void TBuf::BeginKey() {
+ if (Y_UNLIKELY(!KeyExpected())) {
+ ythrow TError() << "JSON writer: key written outside of an object";
+ }
+ WriteComma();
+ StackPush(JE_PAIR);
+ NeedComma = false;
+ NeedNewline = false;
+ }
+
+ inline void TBuf::EndValue() {
+ if (StackTop() == JE_PAIR) {
+ StackPop();
+ }
+ }
+
+ TValueContext TBuf::BeginList() {
+ NeedNewline = true;
+ BeginValue();
+ RawWriteChar('[');
+ StackPush(JE_LIST);
+ NeedComma = false;
+ return TValueContext(*this);
+ }
+
+ TPairContext TBuf::BeginObject() {
+ NeedNewline = true;
+ BeginValue();
+ RawWriteChar('{');
+ StackPush(JE_OBJECT);
+ NeedComma = false;
+ return TPairContext(*this);
+ }
+
+ TAfterColonContext TBuf::UnsafeWriteKey(const TStringBuf& s) {
+ BeginKey();
+ RawWriteChar('"');
+ UnsafeWriteRawBytes(s);
+ UnsafeWriteRawBytes("\":", 2);
+ return TAfterColonContext(*this);
+ }
+
+ TAfterColonContext TBuf::WriteKey(const TStringBuf& s) {
+ // use the default escaping mode for this object
+ return WriteKey(s, EscapeMode);
+ }
+
+ TAfterColonContext TBuf::WriteKey(const TStringBuf& s, EHtmlEscapeMode hem) {
+ BeginKey();
+ WriteBareString(s, hem);
+ RawWriteChar(':');
+ return TAfterColonContext(*this);
+ }
+
+ TAfterColonContext TBuf::CompatWriteKeyWithoutQuotes(const TStringBuf& s) {
+ BeginKey();
+ Y_ASSERT(AllOf(s, [](char x) { return 'a' <= x && x <= 'z'; }));
+ UnsafeWriteRawBytes(s);
+ RawWriteChar(':');
+ return TAfterColonContext(*this);
+ }
+
+ TBuf& TBuf::EndList() {
+ CheckAndPop(JE_LIST);
+ EndValue();
+ return *this;
+ }
+
+ TBuf& TBuf::EndObject() {
+ CheckAndPop(JE_OBJECT);
+ EndValue();
+ return *this;
+ }
+
+ TValueContext TBuf::WriteString(const TStringBuf& s) {
+ // use the default escaping mode for this object
+ return WriteString(s, EscapeMode);
+ }
+
+ TValueContext TBuf::WriteString(const TStringBuf& s, EHtmlEscapeMode hem) {
+ BeginValue();
+ WriteBareString(s, hem);
+ EndValue();
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteNull() {
+ UnsafeWriteValue(TStringBuf("null"));
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteBool(bool b) {
+ constexpr TStringBuf trueVal = "true";
+ constexpr TStringBuf falseVal = "false";
+ UnsafeWriteValue(b ? trueVal : falseVal);
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteInt(int i) {
+ char buf[22]; // enough to hold any 64-bit number
+ size_t len = ToString(i, buf, sizeof(buf));
+ UnsafeWriteValue(buf, len);
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteLongLong(long long i) {
+ static_assert(sizeof(long long) <= 8, "expect sizeof(long long) <= 8");
+ char buf[22]; // enough to hold any 64-bit number
+ size_t len = ToString(i, buf, sizeof(buf));
+ UnsafeWriteValue(buf, len);
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteULongLong(unsigned long long i) {
+ char buf[22]; // enough to hold any 64-bit number
+ size_t len = ToString(i, buf, sizeof(buf));
+ UnsafeWriteValue(buf, len);
+ return TValueContext(*this);
+ }
+
+ template <class TFloat>
+ TValueContext TBuf::WriteFloatImpl(TFloat f, EFloatToStringMode mode, int ndigits) {
+ char buf[512]; // enough to hold most floats, the same buffer is used in FloatToString implementation
+ if (Y_UNLIKELY(!IsValidFloat(f))) {
+ if (WriteNanAsString) {
+ const size_t size = FloatToString(f, buf, Y_ARRAY_SIZE(buf));
+ WriteString(TStringBuf(buf, size));
+ return TValueContext(*this);
+ } else {
+ ythrow TError() << "JSON writer: invalid float value: " << FloatToString(f);
+ }
+ }
+ size_t len = FloatToString(f, buf, Y_ARRAY_SIZE(buf), mode, ndigits);
+ UnsafeWriteValue(buf, len);
+ return TValueContext(*this);
+ }
+
+ TValueContext TBuf::WriteFloat(float f, EFloatToStringMode mode, int ndigits) {
+ return WriteFloatImpl(f, mode, ndigits);
+ }
+
+ TValueContext TBuf::WriteDouble(double f, EFloatToStringMode mode, int ndigits) {
+ return WriteFloatImpl(f, mode, ndigits);
+ }
+
+ namespace {
+ struct TFinder: public TCompactStrSpn {
+ inline TFinder()
+ : TCompactStrSpn("\xe2\\\"\b\n\f\r\t<>&\'/")
+ {
+ for (ui8 ch = 0; ch < 0x20; ++ch) {
+ Set(ch);
+ }
+ }
+ };
+ }
+
+ inline void TBuf::WriteBareString(const TStringBuf s, EHtmlEscapeMode hem) {
+ RawWriteChar('"');
+ const auto& specialChars = *Singleton<TFinder>();
+ const char* b = s.begin();
+ const char* e = s.end();
+ const char* i = b;
+ while ((i = specialChars.FindFirstOf(i, e)) != e) {
+ // U+2028 (line separator) and U+2029 (paragraph separator) are valid string
+ // contents in JSON, but are treated as line breaks in JavaScript, breaking JSONP.
+ // In UTF-8, U+2028 is "\xe2\x80\xa8" and U+2029 is "\xe2\x80\xa9".
+ if (Y_UNLIKELY(e - i >= 3 && i[0] == '\xe2' && i[1] == '\x80' && (i[2] | 1) == '\xa9')) {
+ UnsafeWriteRawBytes(b, i - b);
+ UnsafeWriteRawBytes(i[2] == '\xa9' ? "\\u2029" : "\\u2028", 6);
+ b = i = i + 3;
+ } else if (EscapedWriteChar(b, i, hem)) {
+ b = ++i;
+ } else {
+ ++i;
+ }
+ }
+ UnsafeWriteRawBytes(b, e - b);
+ RawWriteChar('"');
+ }
+
+ inline void TBuf::RawWriteChar(char c) {
+ Stream->Write(c);
+ }
+
+ void TBuf::WriteHexEscape(unsigned char c) {
+ Y_ASSERT(c < 0x80);
+ UnsafeWriteRawBytes("\\u00", 4);
+ static const char hexDigits[] = "0123456789ABCDEF";
+ RawWriteChar(hexDigits[(c & 0xf0) >> 4]);
+ RawWriteChar(hexDigits[(c & 0x0f)]);
+ }
+
+#define MATCH(sym, string) \
+ case sym: \
+ UnsafeWriteRawBytes(beg, cur - beg); \
+ UnsafeWriteRawBytes(TStringBuf(string)); \
+ return true
+
+ inline bool TBuf::EscapedWriteChar(const char* beg, const char* cur, EHtmlEscapeMode hem) {
+ unsigned char c = *cur;
+ if (hem == HEM_ESCAPE_HTML) {
+ switch (c) {
+ MATCH('"', "&quot;");
+ MATCH('\'', "&#39;");
+ MATCH('<', "&lt;");
+ MATCH('>', "&gt;");
+ MATCH('&', "&amp;");
+ }
+ //for other characters, we fall through to the non-HTML-escaped part
+ }
+
+ if (hem == HEM_RELAXED && c == '/')
+ return false;
+
+ if (hem != HEM_UNSAFE) {
+ switch (c) {
+ case '/':
+ UnsafeWriteRawBytes(beg, cur - beg);
+ UnsafeWriteRawBytes("\\/", 2);
+ return true;
+ case '<':
+ case '>':
+ case '\'':
+ UnsafeWriteRawBytes(beg, cur - beg);
+ WriteHexEscape(c);
+ return true;
+ }
+ // for other characters, fall through to the non-escaped part
+ }
+
+ switch (c) {
+ MATCH('"', "\\\"");
+ MATCH('\\', "\\\\");
+ MATCH('\b', "\\b");
+ MATCH('\f', "\\f");
+ MATCH('\n', "\\n");
+ MATCH('\r', "\\r");
+ MATCH('\t', "\\t");
+ }
+ if (c < 0x20) {
+ UnsafeWriteRawBytes(beg, cur - beg);
+ WriteHexEscape(c);
+ return true;
+ }
+
+ return false;
+ }
+
+#undef MATCH
+
+ static bool LessStrPtr(const TString* a, const TString* b) {
+ return *a < *b;
+ }
+
+ TValueContext TBuf::WriteJsonValue(const NJson::TJsonValue* v, bool sortKeys, EFloatToStringMode mode, int ndigits) {
+ using namespace NJson;
+ switch (v->GetType()) {
+ default:
+ case JSON_NULL:
+ WriteNull();
+ break;
+ case JSON_BOOLEAN:
+ WriteBool(v->GetBoolean());
+ break;
+ case JSON_DOUBLE:
+ WriteDouble(v->GetDouble(), mode, ndigits);
+ break;
+ case JSON_INTEGER:
+ WriteLongLong(v->GetInteger());
+ break;
+ case JSON_UINTEGER:
+ WriteULongLong(v->GetUInteger());
+ break;
+ case JSON_STRING:
+ WriteString(v->GetString());
+ break;
+ case JSON_ARRAY: {
+ BeginList();
+ const TJsonValue::TArray& arr = v->GetArray();
+ for (const auto& it : arr)
+ WriteJsonValue(&it, sortKeys, mode, ndigits);
+ EndList();
+ break;
+ }
+ case JSON_MAP: {
+ BeginObject();
+ const TJsonValue::TMapType& map = v->GetMap();
+ if (sortKeys) {
+ const size_t oldsz = Keys.size();
+ Keys.reserve(map.size() + oldsz);
+ for (const auto& it : map) {
+ Keys.push_back(&(it.first));
+ }
+ Sort(Keys.begin() + oldsz, Keys.end(), LessStrPtr);
+ for (size_t i = oldsz, sz = Keys.size(); i < sz; ++i) {
+ TJsonValue::TMapType::const_iterator kv = map.find(*Keys[i]);
+ WriteKey(kv->first);
+ WriteJsonValue(&kv->second, sortKeys, mode, ndigits);
+ }
+ Keys.resize(oldsz);
+ } else {
+ for (const auto& it : map) {
+ WriteKey(it.first);
+ WriteJsonValue(&it.second, sortKeys, mode, ndigits);
+ }
+ }
+ EndObject();
+ break;
+ }
+ }
+ return TValueContext(*this);
+ }
+
+ TPairContext TBuf::UnsafeWritePair(const TStringBuf& s) {
+ if (Y_UNLIKELY(StackTop() != JE_OBJECT)) {
+ ythrow TError() << "JSON writer: key:value pair written outside of an object";
+ }
+ WriteComma();
+ UnsafeWriteRawBytes(s);
+ return TPairContext(*this);
+ }
+
+ void TBuf::UnsafeWriteValue(const TStringBuf& s) {
+ BeginValue();
+ UnsafeWriteRawBytes(s);
+ EndValue();
+ }
+
+ void TBuf::UnsafeWriteValue(const char* s, size_t len) {
+ BeginValue();
+ UnsafeWriteRawBytes(s, len);
+ EndValue();
+ }
+
+ void TBuf::UnsafeWriteRawBytes(const char* src, size_t len) {
+ Stream->Write(src, len);
+ }
+
+ void TBuf::UnsafeWriteRawBytes(const TStringBuf& s) {
+ UnsafeWriteRawBytes(s.data(), s.size());
+ }
+
+ const TString& TBuf::Str() const {
+ if (!StringStream) {
+ ythrow TError() << "JSON writer: Str() called "
+ "but writing to an external stream";
+ }
+ if (!(Stack.size() == 1 && StackTop() == JE_OUTER_SPACE)) {
+ ythrow TError() << "JSON writer: incomplete object converted to string";
+ }
+ return StringStream->Str();
+ }
+
+ void TBuf::FlushTo(IOutputStream* stream) {
+ if (!StringStream) {
+ ythrow TError() << "JSON writer: FlushTo() called "
+ "but writing to an external stream";
+ }
+ stream->Write(StringStream->Str());
+ StringStream->Clear();
+ }
+
+ TString WrapJsonToCallback(const TBuf& buf, TStringBuf callback) {
+ if (!callback) {
+ return buf.Str();
+ } else {
+ return TString::Join(callback, "(", buf.Str(), ")");
+ }
+ }
+
+ TBufState TBuf::State() const {
+ return TBufState{NeedComma, NeedNewline, Stack};
+ }
+
+ void TBuf::Reset(const TBufState& from) {
+ NeedComma = from.NeedComma;
+ NeedNewline = from.NeedNewline;
+ Stack = from.Stack;
+ }
+
+ void TBuf::Reset(TBufState&& from) {
+ NeedComma = from.NeedComma;
+ NeedNewline = from.NeedNewline;
+ Stack.swap(from.Stack);
+ }
+
+}
diff --git a/library/cpp/json/writer/json.h b/library/cpp/json/writer/json.h
new file mode 100644
index 0000000000..0aae2531b9
--- /dev/null
+++ b/library/cpp/json/writer/json.h
@@ -0,0 +1,289 @@
+#pragma once
+
+#include <util/generic/noncopyable.h>
+#include <util/generic/ptr.h>
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/generic/yexception.h>
+#include <util/stream/str.h>
+#include <util/string/cast.h>
+
+namespace NJson {
+ class TJsonValue;
+}
+
+namespace NJsonWriter {
+ enum EJsonEntity : ui8 {
+ JE_OUTER_SPACE = 1,
+ JE_LIST,
+ JE_OBJECT,
+ JE_PAIR,
+ };
+
+ enum EHtmlEscapeMode {
+ HEM_ESCAPE_HTML = 1, // Use HTML escaping: &lt; &gt; &amp; \/
+ HEM_DONT_ESCAPE_HTML, // Use JSON escaping: \u003C \u003E \u0026 \/
+ HEM_RELAXED, // Use JSON escaping: \u003C \u003E \u0026 /
+ HEM_UNSAFE, // Turn escaping off: < > & /
+ };
+
+ class TError: public yexception {};
+
+ class TValueContext;
+ class TPairContext;
+ class TAfterColonContext;
+
+ struct TBufState {
+ bool NeedComma;
+ bool NeedNewline;
+ TVector<EJsonEntity> Stack;
+ };
+
+ class TBuf : TNonCopyable {
+ public:
+ TBuf(EHtmlEscapeMode mode = HEM_DONT_ESCAPE_HTML, IOutputStream* stream = nullptr);
+
+ TValueContext WriteString(const TStringBuf& s, EHtmlEscapeMode hem);
+ TValueContext WriteString(const TStringBuf& s);
+ TValueContext WriteInt(int i);
+ TValueContext WriteLongLong(long long i);
+ TValueContext WriteULongLong(unsigned long long i);
+ TValueContext WriteFloat(float f, EFloatToStringMode mode = PREC_NDIGITS, int ndigits = 6);
+ TValueContext WriteDouble(double f, EFloatToStringMode mode = PREC_NDIGITS, int ndigits = 10);
+ TValueContext WriteBool(bool b);
+ TValueContext WriteNull();
+ TValueContext WriteJsonValue(const NJson::TJsonValue* value, bool sortKeys = false, EFloatToStringMode mode = PREC_NDIGITS, int ndigits = 10);
+
+ TValueContext BeginList();
+ TBuf& EndList();
+
+ TPairContext BeginObject();
+ TAfterColonContext WriteKey(const TStringBuf& key, EHtmlEscapeMode hem);
+ TAfterColonContext WriteKey(const TStringBuf& key);
+ TAfterColonContext UnsafeWriteKey(const TStringBuf& key);
+ bool KeyExpected() const {
+ return Stack.back() == JE_OBJECT;
+ }
+
+ //! deprecated, do not use in new code
+ TAfterColonContext CompatWriteKeyWithoutQuotes(const TStringBuf& key);
+
+ TBuf& EndObject();
+
+ /*** Indent the resulting JSON with spaces.
+ * By default (spaces==0) no formatting is done. */
+ TBuf& SetIndentSpaces(int spaces) {
+ IndentSpaces = spaces;
+ return *this;
+ }
+
+ /*** NaN and Inf are not valid json values,
+ * so if WriteNanAsString is set, writer would write string
+ * intead of throwing exception (default case) */
+ TBuf& SetWriteNanAsString(bool writeNanAsString = true) {
+ WriteNanAsString = writeNanAsString;
+ return *this;
+ }
+
+ /*** Return the string formed in the internal TStringStream.
+ * You may only call it if the `stream' parameter was NULL
+ * at construction time. */
+ const TString& Str() const;
+
+ /*** Dump and forget the string constructed so far.
+ * You may only call it if the `stream' parameter was NULL
+ * at construction time. */
+ void FlushTo(IOutputStream* stream);
+
+ /*** Write a literal string that represents a JSON value
+ * (string, number, object, array, bool, or null).
+ *
+ * Example:
+ * j.UnsafeWriteValue("[1, 2, 3, \"o'clock\", 4, \"o'clock rock\"]");
+ *
+ * As in all of the Unsafe* functions, no escaping is done. */
+ void UnsafeWriteValue(const TStringBuf& s);
+ void UnsafeWriteValue(const char* s, size_t len);
+
+ /*** When in the context of an object, write a literal string
+ * that represents a key:value pair (or several pairs).
+ *
+ * Example:
+ * j.BeginObject();
+ * j.UnsafeWritePair("\"adam\": \"male\", \"eve\": \"female\"");
+ * j.EndObject();
+ *
+ * As in all of the Unsafe* functions, no escaping is done. */
+ TPairContext UnsafeWritePair(const TStringBuf& s);
+
+ /*** Copy the supplied string directly into the output stream. */
+ void UnsafeWriteRawBytes(const TStringBuf& s);
+ void UnsafeWriteRawBytes(const char* c, size_t len);
+
+ TBufState State() const;
+ void Reset(const TBufState& from);
+ void Reset(TBufState&& from);
+
+ private:
+ void BeginValue();
+ void EndValue();
+ void BeginKey();
+ void RawWriteChar(char c);
+ bool EscapedWriteChar(const char* b, const char* c, EHtmlEscapeMode hem);
+ void WriteBareString(const TStringBuf s, EHtmlEscapeMode hem);
+ void WriteComma();
+ void PrintIndentation(bool closing);
+ void PrintWhitespaces(size_t count, bool prependWithNewLine);
+ void WriteHexEscape(unsigned char c);
+
+ void StackPush(EJsonEntity e);
+ void StackPop();
+ void CheckAndPop(EJsonEntity e);
+ EJsonEntity StackTop() const;
+
+ template <class TFloat>
+ TValueContext WriteFloatImpl(TFloat f, EFloatToStringMode mode, int ndigits);
+
+ private:
+ IOutputStream* Stream;
+ THolder<TStringStream> StringStream;
+ typedef TVector<const TString*> TKeys;
+ TKeys Keys;
+
+ TVector<EJsonEntity> Stack;
+ bool NeedComma;
+ bool NeedNewline;
+ const EHtmlEscapeMode EscapeMode;
+ int IndentSpaces;
+ bool WriteNanAsString;
+ };
+
+ // Please don't try to instantiate the classes declared below this point.
+
+ template <typename TOutContext>
+ class TValueWriter {
+ public:
+ TOutContext WriteNull();
+ TOutContext WriteString(const TStringBuf&);
+ TOutContext WriteString(const TStringBuf& s, EHtmlEscapeMode hem);
+ TOutContext WriteInt(int);
+ TOutContext WriteLongLong(long long);
+ TOutContext WriteULongLong(unsigned long long);
+ TOutContext WriteBool(bool);
+ TOutContext WriteFloat(float);
+ TOutContext WriteFloat(float, EFloatToStringMode, int ndigits);
+ TOutContext WriteDouble(double);
+ TOutContext WriteDouble(double, EFloatToStringMode, int ndigits);
+ TOutContext WriteJsonValue(const NJson::TJsonValue* value, bool sortKeys = false);
+ TOutContext UnsafeWriteValue(const TStringBuf&);
+
+ TValueContext BeginList();
+ TPairContext BeginObject();
+
+ protected:
+ TValueWriter(TBuf& buf)
+ : Buf(buf)
+ {
+ }
+ friend class TBuf;
+
+ protected:
+ TBuf& Buf;
+ };
+
+ class TValueContext: public TValueWriter<TValueContext> {
+ public:
+ TBuf& EndList() {
+ return Buf.EndList();
+ }
+ TString Str() const {
+ return Buf.Str();
+ }
+
+ private:
+ TValueContext(TBuf& buf)
+ : TValueWriter<TValueContext>(buf)
+ {
+ }
+ friend class TBuf;
+ friend class TValueWriter<TValueContext>;
+ };
+
+ class TAfterColonContext: public TValueWriter<TPairContext> {
+ private:
+ TAfterColonContext(TBuf& iBuf)
+ : TValueWriter<TPairContext>(iBuf)
+ {
+ }
+ friend class TBuf;
+ friend class TPairContext;
+ };
+
+ class TPairContext {
+ public:
+ TAfterColonContext WriteKey(const TStringBuf& s, EHtmlEscapeMode hem) {
+ return Buf.WriteKey(s, hem);
+ }
+ TAfterColonContext WriteKey(const TStringBuf& s) {
+ return Buf.WriteKey(s);
+ }
+ TAfterColonContext UnsafeWriteKey(const TStringBuf& s) {
+ return Buf.UnsafeWriteKey(s);
+ }
+ TAfterColonContext CompatWriteKeyWithoutQuotes(const TStringBuf& s) {
+ return Buf.CompatWriteKeyWithoutQuotes(s);
+ }
+ TPairContext UnsafeWritePair(const TStringBuf& s) {
+ return Buf.UnsafeWritePair(s);
+ }
+ TBuf& EndObject() {
+ return Buf.EndObject();
+ }
+
+ private:
+ TPairContext(TBuf& buf)
+ : Buf(buf)
+ {
+ }
+
+ friend class TBuf;
+ friend class TValueWriter<TPairContext>;
+
+ private:
+ TBuf& Buf;
+ };
+
+#define JSON_VALUE_WRITER_WRAP(function, params, args) \
+ template <typename TOutContext> \
+ TOutContext TValueWriter<TOutContext>::function params { \
+ Buf.function args; \
+ return TOutContext(Buf); \
+ }
+
+ JSON_VALUE_WRITER_WRAP(WriteNull, (), ())
+ JSON_VALUE_WRITER_WRAP(WriteString, (const TStringBuf& arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteString, (const TStringBuf& s, EHtmlEscapeMode hem), (s, hem))
+ JSON_VALUE_WRITER_WRAP(WriteInt, (int arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteLongLong, (long long arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteULongLong, (unsigned long long arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteBool, (bool arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteFloat, (float arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteFloat, (float arg, EFloatToStringMode mode, int ndigits), (arg, mode, ndigits))
+ JSON_VALUE_WRITER_WRAP(WriteDouble, (double arg), (arg))
+ JSON_VALUE_WRITER_WRAP(WriteDouble, (double arg, EFloatToStringMode mode, int ndigits), (arg, mode, ndigits))
+ JSON_VALUE_WRITER_WRAP(WriteJsonValue, (const NJson::TJsonValue* value, bool sortKeys), (value, sortKeys))
+ JSON_VALUE_WRITER_WRAP(UnsafeWriteValue, (const TStringBuf& arg), (arg))
+#undef JSON_VALUE_WRITER_WRAP
+
+ template <typename TOutContext>
+ TValueContext TValueWriter<TOutContext>::BeginList() {
+ return Buf.BeginList();
+ }
+
+ template <typename TOutContext>
+ TPairContext TValueWriter<TOutContext>::BeginObject() {
+ return Buf.BeginObject();
+ }
+
+ TString WrapJsonToCallback(const TBuf& buf, TStringBuf callback);
+}
diff --git a/library/cpp/json/writer/json_ut.cpp b/library/cpp/json/writer/json_ut.cpp
new file mode 100644
index 0000000000..9980555683
--- /dev/null
+++ b/library/cpp/json/writer/json_ut.cpp
@@ -0,0 +1,307 @@
+#include <library/cpp/testing/unittest/registar.h>
+#include <util/system/sanitizers.h>
+
+#include "json.h"
+#include <library/cpp/json/json_value.h>
+
+#include <limits>
+
+Y_UNIT_TEST_SUITE(JsonWriter) {
+ Y_UNIT_TEST(Struct) {
+ NJsonWriter::TBuf w;
+ w.BeginList();
+ w.BeginObject()
+ .WriteKey("key")
+ .WriteString("value")
+ .UnsafeWritePair("\"xk\":13")
+ .WriteKey("key2")
+ .BeginList()
+ .BeginObject()
+ .EndObject()
+ .BeginObject()
+ .EndObject()
+ .EndList()
+ .EndObject();
+ w.WriteInt(43);
+ w.UnsafeWriteValue("\"x\"");
+ w.WriteString("...");
+ w.EndList();
+ const char* exp = "[{\"key\":\"value\",\"xk\":13,\"key2\":[{},{}]},43,\"x\",\"...\"]";
+ UNIT_ASSERT_EQUAL(w.Str(), exp);
+ }
+ Y_UNIT_TEST(EscapedString) {
+ NJsonWriter::TBuf w(NJsonWriter::HEM_ESCAPE_HTML);
+ w.WriteString(" \n \r \t \007 \b \f ' <tag> &ent; \"txt\" ");
+ TString ws = w.Str();
+ const char* exp = "\" \\n \\r \\t \\u0007 \\b \\f &#39; &lt;tag&gt; &amp;ent; &quot;txt&quot; \"";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(UnescapedString) {
+ NJsonWriter::TBuf w;
+ w.WriteString(" \n \r \t \b \f '; -- <tag> &ent; \"txt\"", NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ TString ws = w.Str();
+ const char* exp = "\" \\n \\r \\t \\b \\f \\u0027; -- \\u003Ctag\\u003E &ent; \\\"txt\\\"\"";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(UnescapedChaining) {
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ w.UnsafeWriteRawBytes("(", 1);
+ w.BeginList().WriteString("<>&'\\").BeginList();
+ w.EndList().EndList();
+ TString ws = w.Str();
+ const char* exp = "([\"\\u003C\\u003E&\\u0027\\\\\",[]]";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(Utf8) {
+ TString ws = NJsonWriter::TBuf().WriteString("яЯ σΣ ש א").Str();
+ const char* exp = "\"яЯ σΣ ש א\"";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(WrongObject) {
+ NJsonWriter::TBuf w;
+ w.BeginObject();
+ UNIT_ASSERT_EXCEPTION(w.WriteString("hehe"), NJsonWriter::TError);
+ }
+ Y_UNIT_TEST(WrongList) {
+ NJsonWriter::TBuf w;
+ w.BeginList();
+ UNIT_ASSERT_EXCEPTION(w.WriteKey("hehe"), NJsonWriter::TError);
+ }
+ Y_UNIT_TEST(Incomplete) {
+ NJsonWriter::TBuf w;
+ w.BeginList();
+ UNIT_ASSERT_EXCEPTION(w.Str(), NJsonWriter::TError);
+ }
+ Y_UNIT_TEST(BareKey) {
+ NJsonWriter::TBuf w;
+ w.BeginObject()
+ .CompatWriteKeyWithoutQuotes("p")
+ .WriteInt(1)
+ .CompatWriteKeyWithoutQuotes("n")
+ .WriteInt(0)
+ .EndObject();
+ TString ws = w.Str();
+ const char* exp = "{p:1,n:0}";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(UnescapedStringInObject) {
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ w.BeginObject().WriteKey("key").WriteString("</&>'").EndObject();
+ TString ws = w.Str();
+ const char* exp = "{\"key\":\"\\u003C\\/&\\u003E\\u0027\"}";
+ UNIT_ASSERT_STRINGS_EQUAL(ws.c_str(), exp);
+ }
+ Y_UNIT_TEST(ForeignStreamStr) {
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML, &Cerr);
+ UNIT_ASSERT_EXCEPTION(w.Str(), NJsonWriter::TError);
+ }
+ Y_UNIT_TEST(ForeignStreamValue) {
+ TStringStream ss;
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML, &ss);
+ w.WriteInt(1543);
+ UNIT_ASSERT_STRINGS_EQUAL(ss.Str(), "1543");
+ }
+ Y_UNIT_TEST(Indentation) {
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ w.SetIndentSpaces(2);
+ w.BeginList()
+ .WriteInt(1)
+ .WriteString("hello")
+ .BeginObject()
+ .WriteKey("abc")
+ .WriteInt(3)
+ .WriteKey("def")
+ .WriteInt(4)
+ .EndObject()
+ .EndList();
+ const char* exp = "[\n"
+ " 1,\n"
+ " \"hello\",\n"
+ " {\n"
+ " \"abc\":3,\n"
+ " \"def\":4\n"
+ " }\n"
+ "]";
+ UNIT_ASSERT_STRINGS_EQUAL(exp, w.Str());
+ }
+ Y_UNIT_TEST(WriteJsonValue) {
+ using namespace NJson;
+ TJsonValue val;
+ val.AppendValue(1);
+ val.AppendValue("2");
+ val.AppendValue(3.5);
+ TJsonValue obj;
+ obj.InsertValue("key", TJsonValue("value"));
+
+ val.AppendValue(obj);
+ val.AppendValue(TJsonValue(JSON_NULL));
+
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ w.WriteJsonValue(&val);
+
+ const char exp[] = "[1,\"2\",3.5,{\"key\":\"value\"},null]";
+ UNIT_ASSERT_STRINGS_EQUAL(exp, w.Str());
+ }
+ Y_UNIT_TEST(WriteJsonValueSorted) {
+ using namespace NJson;
+ TJsonValue val;
+ val.InsertValue("1", TJsonValue(1));
+ val.InsertValue("2", TJsonValue(2));
+
+ TJsonValue obj;
+ obj.InsertValue("zero", TJsonValue(0));
+ obj.InsertValue("succ", TJsonValue(1));
+ val.InsertValue("0", obj);
+
+ NJsonWriter::TBuf w(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ w.WriteJsonValue(&val, true);
+
+ const char exp[] = "{\"0\":{\"succ\":1,\"zero\":0},\"1\":1,\"2\":2}";
+ UNIT_ASSERT_STRINGS_EQUAL(exp, w.Str());
+ }
+ Y_UNIT_TEST(Unescaped) {
+ NJsonWriter::TBuf buf(NJsonWriter::HEM_UNSAFE);
+ buf.WriteString("</security>'");
+ UNIT_ASSERT_STRINGS_EQUAL("\"</security>'\"", buf.Str());
+ }
+ Y_UNIT_TEST(LittleBobbyJsonp) {
+ NJsonWriter::TBuf buf;
+ buf.WriteString("hello\xe2\x80\xa8\xe2\x80\xa9stranger");
+ UNIT_ASSERT_STRINGS_EQUAL("\"hello\\u2028\\u2029stranger\"", buf.Str());
+ }
+ Y_UNIT_TEST(LittleBobbyInvalid) {
+ NJsonWriter::TBuf buf;
+ TStringBuf incomplete("\xe2\x80\xa8", 2);
+ buf.WriteString(incomplete);
+ // garbage in - garbage out
+ UNIT_ASSERT_STRINGS_EQUAL("\"\xe2\x80\"", buf.Str());
+ }
+ Y_UNIT_TEST(OverlyZealous) {
+ NJsonWriter::TBuf buf;
+ buf.WriteString("—");
+ UNIT_ASSERT_STRINGS_EQUAL("\"—\"", buf.Str());
+ }
+ Y_UNIT_TEST(RelaxedEscaping) {
+ NJsonWriter::TBuf buf(NJsonWriter::HEM_RELAXED);
+ buf.WriteString("</>");
+ UNIT_ASSERT_STRINGS_EQUAL("\"\\u003C/\\u003E\"", buf.Str());
+ }
+
+ Y_UNIT_TEST(FloatFormatting) {
+ NJsonWriter::TBuf buf(NJsonWriter::HEM_DONT_ESCAPE_HTML);
+ buf.BeginList()
+ .WriteFloat(0.12345678987654321f)
+ .WriteDouble(0.12345678987654321)
+ .WriteFloat(0.315501, PREC_NDIGITS, 3)
+ .WriteFloat(244.13854, PREC_NDIGITS, 4)
+ .WriteFloat(10385.8324, PREC_POINT_DIGITS, 2)
+ .BeginObject()
+ .WriteKey("1")
+ .WriteDouble(1111.71, PREC_POINT_DIGITS, 0)
+ .WriteKey("2")
+ .WriteDouble(1111.71, PREC_NDIGITS, 1)
+ .EndObject()
+ .EndList();
+ const char exp[] = "[0.123457,0.1234567899,0.316,244.1,10385.83,{\"1\":1112,\"2\":1e+03}]";
+ UNIT_ASSERT_STRINGS_EQUAL(exp, buf.Str());
+ }
+
+ Y_UNIT_TEST(NanFormatting) {
+ {
+ NJsonWriter::TBuf buf;
+ buf.BeginObject();
+ buf.WriteKey("nanvalue");
+ UNIT_ASSERT_EXCEPTION(buf.WriteFloat(std::numeric_limits<double>::quiet_NaN()), yexception);
+ }
+
+ {
+ NJsonWriter::TBuf buf;
+ buf.BeginObject();
+ buf.WriteKey("infvalue");
+ UNIT_ASSERT_EXCEPTION(buf.WriteFloat(std::numeric_limits<double>::infinity()), yexception);
+ }
+
+ {
+ NJsonWriter::TBuf buf;
+ buf.BeginList();
+ UNIT_ASSERT_EXCEPTION(buf.WriteFloat(std::numeric_limits<double>::quiet_NaN()), yexception);
+ }
+
+ {
+ NJsonWriter::TBuf buf;
+ buf.BeginList();
+ UNIT_ASSERT_EXCEPTION(buf.WriteFloat(std::numeric_limits<double>::infinity()), yexception);
+ }
+
+ {
+ NJsonWriter::TBuf buf;
+ buf.SetWriteNanAsString();
+
+ buf.BeginObject()
+ .WriteKey("nanvalue")
+ .WriteFloat(std::numeric_limits<double>::quiet_NaN())
+ .WriteKey("infvalue")
+ .WriteFloat(std::numeric_limits<double>::infinity())
+ .WriteKey("minus_infvalue")
+ .WriteFloat(-std::numeric_limits<float>::infinity())
+ .WriteKey("l")
+ .BeginList()
+ .WriteFloat(std::numeric_limits<float>::quiet_NaN())
+ .EndList()
+ .EndObject();
+
+ UNIT_ASSERT_STRINGS_EQUAL(buf.Str(), R"raw_json({"nanvalue":"nan","infvalue":"inf","minus_infvalue":"-inf","l":["nan"]})raw_json");
+ }
+
+ {
+ NJsonWriter::TBuf buf;
+ buf.BeginObject()
+ .WriteKey("<>&")
+ .WriteString("Ololo")
+ .UnsafeWriteKey("<>&")
+ .WriteString("Ololo2")
+ .EndObject();
+
+ UNIT_ASSERT_STRINGS_EQUAL(buf.Str(), R"({"\u003C\u003E&":"Ololo","<>&":"Ololo2"})");
+ }
+ }
+
+ Y_UNIT_TEST(WriteUninitializedBoolDoesntCrashProgram) {
+ // makes sense only in release build w/ address sanitizer
+ //
+ // passing uninitialized boolean into WriteBool can make cleverly optimized code which is emitted by compiler crash program
+ // https://stackoverflow.com/questions/54120862/does-the-c-standard-allow-for-an-uninitialized-bool-to-crash-a-program
+
+ // looks like compiler can detect UB at compile time in simple cases, but not in this one
+ class TSensorConf {
+ public:
+ class TAggrRuleItem {
+ public:
+ TVector<TString> Cond;
+ TVector<TString> Target;
+ };
+
+ TString ToString() const {
+ NJson::TJsonValue jsonValue;
+ NJsonWriter::TBuf jsonOutput;
+ jsonOutput.BeginObject()
+ .WriteKey("rawDataMemOnly").WriteBool(RawDataMemOnly)
+ .WriteKey("aggrRules").BeginList();
+
+ jsonOutput.EndList()
+ .EndObject();
+
+ return jsonOutput.Str();
+ }
+
+ TVector<TAggrRuleItem> AggrRules;
+ bool RawDataMemOnly;
+ };
+
+ TSensorConf s;
+ NSan::Unpoison(&s.RawDataMemOnly, sizeof(s.RawDataMemOnly));
+ auto p = s.ToString();
+ // doesn't really matter
+ UNIT_ASSERT(!p.empty());
+ }
+}
diff --git a/library/cpp/json/writer/json_value.cpp b/library/cpp/json/writer/json_value.cpp
new file mode 100644
index 0000000000..c61e8d1dc4
--- /dev/null
+++ b/library/cpp/json/writer/json_value.cpp
@@ -0,0 +1,1105 @@
+#include "json_value.h"
+#include "json.h"
+
+#include <util/generic/ymath.h>
+#include <util/generic/ylimits.h>
+#include <util/generic/utility.h>
+#include <util/generic/singleton.h>
+#include <util/stream/str.h>
+#include <util/stream/output.h>
+#include <util/string/cast.h>
+#include <util/string/type.h>
+#include <util/string/vector.h>
+#include <util/system/yassert.h>
+#include <util/ysaveload.h>
+#include <util/generic/bt_exception.h>
+
+static bool
+AreJsonMapsEqual(const NJson::TJsonValue& lhs, const NJson::TJsonValue& rhs) {
+ using namespace NJson;
+
+ Y_VERIFY(lhs.GetType() == JSON_MAP, "lhs has not a JSON_MAP type.");
+
+ if (rhs.GetType() != JSON_MAP)
+ return false;
+
+ typedef TJsonValue::TMapType TMapType;
+ const TMapType& lhsMap = lhs.GetMap();
+ const TMapType& rhsMap = rhs.GetMap();
+
+ if (lhsMap.size() != rhsMap.size())
+ return false;
+
+ for (const auto& lhsIt : lhsMap) {
+ TMapType::const_iterator rhsIt = rhsMap.find(lhsIt.first);
+ if (rhsIt == rhsMap.end())
+ return false;
+
+ if (lhsIt.second != rhsIt->second)
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+AreJsonArraysEqual(const NJson::TJsonValue& lhs, const NJson::TJsonValue& rhs) {
+ using namespace NJson;
+
+ Y_VERIFY(lhs.GetType() == JSON_ARRAY, "lhs has not a JSON_ARRAY type.");
+
+ if (rhs.GetType() != JSON_ARRAY)
+ return false;
+
+ typedef TJsonValue::TArray TArray;
+ const TArray& lhsArray = lhs.GetArray();
+ const TArray& rhsArray = rhs.GetArray();
+
+ if (lhsArray.size() != rhsArray.size())
+ return false;
+
+ for (TArray::const_iterator lhsIt = lhsArray.begin(), rhsIt = rhsArray.begin();
+ lhsIt != lhsArray.end(); ++lhsIt, ++rhsIt) {
+ if (*lhsIt != *rhsIt)
+ return false;
+ }
+
+ return true;
+}
+
+namespace NJson {
+ const TJsonValue TJsonValue::UNDEFINED{};
+
+ TJsonValue::TJsonValue(const EJsonValueType type) {
+ SetType(type);
+ }
+
+ TJsonValue::TJsonValue(TJsonValue&& vval) noexcept
+ : Type(JSON_UNDEFINED)
+ {
+ vval.SwapWithUndefined(*this);
+ Zero(vval.Value);
+ }
+
+ TJsonValue::TJsonValue(const TJsonValue& val)
+ : Type(val.Type)
+ {
+ switch (Type) {
+ case JSON_STRING:
+ new (&Value.String) TString(val.GetString());
+ break;
+ case JSON_MAP:
+ Value.Map = new TMapType(val.GetMap());
+ break;
+ case JSON_ARRAY:
+ Value.Array = new TArray(val.GetArray());
+ break;
+ case JSON_UNDEFINED:
+ case JSON_NULL:
+ case JSON_BOOLEAN:
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ case JSON_DOUBLE:
+ std::memcpy(&Value, &val.Value, sizeof(Value));
+ break;
+ }
+ }
+
+ TJsonValue& TJsonValue::operator=(const TJsonValue& val) {
+ if (this == &val)
+ return *this;
+ TJsonValue tmp(val);
+ tmp.Swap(*this);
+ return *this;
+ }
+
+ TJsonValue& TJsonValue::operator=(TJsonValue&& val) noexcept {
+ if (this == &val)
+ return *this;
+ TJsonValue tmp(std::move(val));
+ tmp.Swap(*this);
+ return *this;
+ }
+
+ TJsonValue::TJsonValue(const bool value) noexcept {
+ SetType(JSON_BOOLEAN);
+ Value.Boolean = value;
+ }
+
+ TJsonValue::TJsonValue(const long long value) noexcept {
+ SetType(JSON_INTEGER);
+ Value.Integer = value;
+ }
+
+ TJsonValue::TJsonValue(const unsigned long long value) noexcept {
+ SetType(JSON_UINTEGER);
+ Value.UInteger = value;
+ }
+
+ TJsonValue::TJsonValue(const int value) noexcept {
+ SetType(JSON_INTEGER);
+ Value.Integer = value;
+ }
+
+ TJsonValue::TJsonValue(const unsigned int value) noexcept {
+ SetType(JSON_UINTEGER);
+ Value.UInteger = value;
+ }
+
+ TJsonValue::TJsonValue(const long value) noexcept {
+ SetType(JSON_INTEGER);
+ Value.Integer = value;
+ }
+
+ TJsonValue::TJsonValue(const unsigned long value) noexcept {
+ SetType(JSON_UINTEGER);
+ Value.UInteger = value;
+ }
+
+ TJsonValue::TJsonValue(const double value) noexcept {
+ SetType(JSON_DOUBLE);
+ Value.Double = value;
+ }
+
+ TJsonValue::TJsonValue(TString value) {
+ SetType(JSON_STRING);
+ Value.String = std::move(value);
+ }
+
+ TJsonValue::TJsonValue(const TStringBuf value) {
+ SetType(JSON_STRING);
+ Value.String = value;
+ }
+
+ TJsonValue::TJsonValue(const char* value) {
+ SetType(JSON_STRING);
+ Value.String = value;
+ }
+
+ EJsonValueType TJsonValue::GetType() const noexcept {
+ return Type;
+ }
+
+ TJsonValue& TJsonValue::SetType(const EJsonValueType type) {
+ if (Type == type)
+ return *this;
+
+ Clear();
+ Type = type;
+
+ switch (Type) {
+ case JSON_STRING:
+ new (&Value.String) TString();
+ break;
+ case JSON_MAP:
+ Value.Map = new TMapType();
+ break;
+ case JSON_ARRAY:
+ Value.Array = new TArray();
+ break;
+ case JSON_UNDEFINED:
+ case JSON_NULL:
+ case JSON_BOOLEAN:
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ case JSON_DOUBLE:
+ break;
+ }
+
+ return *this;
+ }
+
+ TJsonValue& TJsonValue::SetValue(const TJsonValue& value) {
+ return *this = value;
+ }
+
+ TJsonValue& TJsonValue::SetValue(TJsonValue&& value) {
+ *this = std::move(value);
+ return *this;
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const TString& key, const TJsonValue& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = value;
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const TStringBuf key, const TJsonValue& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = value;
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const char* key, const TJsonValue& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = value;
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const TString& key, TJsonValue&& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = std::move(value);
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const TStringBuf key, TJsonValue&& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = std::move(value);
+ }
+
+ TJsonValue& TJsonValue::InsertValue(const char* key, TJsonValue&& value) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key] = std::move(value);
+ }
+
+ TJsonValue& TJsonValue::Back() {
+ BackChecks();
+ return Value.Array->back();
+ }
+
+ const TJsonValue& TJsonValue::Back() const {
+ BackChecks();
+ return Value.Array->back();
+ }
+
+ TJsonValue& TJsonValue::AppendValue(const TJsonValue& value) {
+ SetType(JSON_ARRAY);
+ Value.Array->push_back(value);
+ return Value.Array->back();
+ }
+
+ TJsonValue& TJsonValue::AppendValue(TJsonValue&& value) {
+ SetType(JSON_ARRAY);
+ Value.Array->push_back(std::move(value));
+ return Value.Array->back();
+ }
+
+ void TJsonValue::EraseValue(const TStringBuf key) {
+ if (IsMap()) {
+ TMapType::iterator it = Value.Map->find(key);
+ if (it != Value.Map->end())
+ Value.Map->erase(it);
+ }
+ }
+
+ void TJsonValue::EraseValue(const size_t index) {
+ if (IsArray()) {
+ if (index >= Value.Array->size()) {
+ return;
+ }
+ TArray::iterator it = Value.Array->begin() + index;
+ Value.Array->erase(it);
+ }
+ }
+
+ void TJsonValue::Clear() noexcept {
+ switch (Type) {
+ case JSON_STRING:
+ Value.String.~TString();
+ break;
+ case JSON_MAP:
+ delete Value.Map;
+ break;
+ case JSON_ARRAY:
+ delete Value.Array;
+ break;
+ case JSON_UNDEFINED:
+ case JSON_NULL:
+ case JSON_BOOLEAN:
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ case JSON_DOUBLE:
+ break;
+ }
+ Zero(Value);
+ Type = JSON_UNDEFINED;
+ }
+
+ TJsonValue& TJsonValue::operator[](const size_t idx) {
+ SetType(JSON_ARRAY);
+ if (Value.Array->size() <= idx)
+ Value.Array->resize(idx + 1);
+ return (*Value.Array)[idx];
+ }
+
+ TJsonValue& TJsonValue::operator[](const TStringBuf& key) {
+ SetType(JSON_MAP);
+ return (*Value.Map)[key];
+ }
+
+ namespace {
+ struct TDefaultsHolder {
+ const TString String{};
+ const TJsonValue::TMapType Map{};
+ const TJsonValue::TArray Array{};
+ const TJsonValue Value{};
+ };
+ }
+
+ const TJsonValue& TJsonValue::operator[](const size_t idx) const noexcept {
+ const TJsonValue* ret = nullptr;
+ if (GetValuePointer(idx, &ret))
+ return *ret;
+
+ return Singleton<TDefaultsHolder>()->Value;
+ }
+
+ const TJsonValue& TJsonValue::operator[](const TStringBuf& key) const noexcept {
+ const TJsonValue* ret = nullptr;
+ if (GetValuePointer(key, &ret))
+ return *ret;
+
+ return Singleton<TDefaultsHolder>()->Value;
+ }
+
+ bool TJsonValue::GetBoolean() const {
+ return Type != JSON_BOOLEAN ? false : Value.Boolean;
+ }
+
+ long long TJsonValue::GetInteger() const {
+ if (!IsInteger())
+ return 0;
+
+ switch (Type) {
+ case JSON_INTEGER:
+ return Value.Integer;
+
+ case JSON_UINTEGER:
+ return Value.UInteger;
+
+ case JSON_DOUBLE:
+ return Value.Double;
+
+ default:
+ Y_ASSERT(false && "Unexpected type.");
+ return 0;
+ }
+ }
+
+ unsigned long long TJsonValue::GetUInteger() const {
+ if (!IsUInteger())
+ return 0;
+
+ switch (Type) {
+ case JSON_UINTEGER:
+ return Value.UInteger;
+
+ case JSON_INTEGER:
+ return Value.Integer;
+
+ case JSON_DOUBLE:
+ return Value.Double;
+
+ default:
+ Y_ASSERT(false && "Unexpected type.");
+ return 0;
+ }
+ }
+
+ double TJsonValue::GetDouble() const {
+ if (!IsDouble())
+ return 0.0;
+
+ switch (Type) {
+ case JSON_DOUBLE:
+ return Value.Double;
+
+ case JSON_INTEGER:
+ return Value.Integer;
+
+ case JSON_UINTEGER:
+ return Value.UInteger;
+
+ default:
+ Y_ASSERT(false && "Unexpected type.");
+ return 0.0;
+ }
+ }
+
+ const TString& TJsonValue::GetString() const {
+ return Type != JSON_STRING ? Singleton<TDefaultsHolder>()->String : Value.String;
+ }
+
+ const TJsonValue::TMapType& TJsonValue::GetMap() const {
+ return Type != JSON_MAP ? Singleton<TDefaultsHolder>()->Map : *Value.Map;
+ }
+
+ const TJsonValue::TArray& TJsonValue::GetArray() const {
+ return (Type != JSON_ARRAY) ? Singleton<TDefaultsHolder>()->Array : *Value.Array;
+ }
+
+ bool TJsonValue::GetBooleanSafe() const {
+ if (Type != JSON_BOOLEAN)
+ ythrow TJsonException() << "Not a boolean";
+
+ return Value.Boolean;
+ }
+
+ long long TJsonValue::GetIntegerSafe() const {
+ if (!IsInteger())
+ ythrow TJsonException() << "Not an integer";
+
+ return GetInteger();
+ }
+
+ unsigned long long TJsonValue::GetUIntegerSafe() const {
+ if (!IsUInteger())
+ ythrow TJsonException() << "Not an unsigned integer";
+
+ return GetUInteger();
+ }
+
+ double TJsonValue::GetDoubleSafe() const {
+ if (!IsDouble())
+ ythrow TJsonException() << "Not a double";
+
+ return GetDouble();
+ }
+
+ const TString& TJsonValue::GetStringSafe() const {
+ if (Type != JSON_STRING)
+ ythrow TJsonException() << "Not a string";
+
+ return Value.String;
+ }
+
+ bool TJsonValue::GetBooleanSafe(const bool defaultValue) const {
+ if (Type == JSON_UNDEFINED)
+ return defaultValue;
+
+ return GetBooleanSafe();
+ }
+
+ long long TJsonValue::GetIntegerSafe(const long long defaultValue) const {
+ if (Type == JSON_UNDEFINED)
+ return defaultValue;
+
+ return GetIntegerSafe();
+ }
+
+ unsigned long long TJsonValue::GetUIntegerSafe(const unsigned long long defaultValue) const {
+ if (Type == JSON_UNDEFINED)
+ return defaultValue;
+
+ return GetUIntegerSafe();
+ }
+
+ double TJsonValue::GetDoubleSafe(const double defaultValue) const {
+ if (Type == JSON_UNDEFINED)
+ return defaultValue;
+
+ return GetDoubleSafe();
+ }
+
+ TString TJsonValue::GetStringSafe(const TString& defaultValue) const {
+ if (Type == JSON_UNDEFINED)
+ return defaultValue;
+
+ return GetStringSafe();
+ }
+
+ const TJsonValue::TMapType& TJsonValue::GetMapSafe() const {
+ if (Type != JSON_MAP)
+ ythrow TJsonException() << "Not a map";
+
+ return *Value.Map;
+ }
+
+ TJsonValue::TMapType& TJsonValue::GetMapSafe() {
+ return const_cast<TJsonValue::TMapType&>(const_cast<const TJsonValue*>(this)->GetMapSafe());
+ }
+
+ const TJsonValue::TArray& TJsonValue::GetArraySafe() const {
+ if (Type != JSON_ARRAY)
+ ythrow TJsonException() << "Not an array";
+
+ return *Value.Array;
+ }
+
+ TJsonValue::TArray& TJsonValue::GetArraySafe() {
+ return const_cast<TJsonValue::TArray&>(const_cast<const TJsonValue*>(this)->GetArraySafe());
+ }
+
+ bool TJsonValue::GetBooleanRobust() const noexcept {
+ switch (Type) {
+ case JSON_ARRAY:
+ return !Value.Array->empty();
+ case JSON_MAP:
+ return !Value.Map->empty();
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ case JSON_DOUBLE:
+ return GetIntegerRobust();
+ case JSON_STRING:
+ return GetIntegerRobust() || IsTrue(Value.String);
+ case JSON_NULL:
+ case JSON_UNDEFINED:
+ default:
+ return false;
+ case JSON_BOOLEAN:
+ return Value.Boolean;
+ }
+ }
+
+ long long TJsonValue::GetIntegerRobust() const noexcept {
+ switch (Type) {
+ case JSON_ARRAY:
+ return Value.Array->size();
+ case JSON_MAP:
+ return Value.Map->size();
+ case JSON_BOOLEAN:
+ return Value.Boolean;
+ case JSON_DOUBLE:
+ return GetDoubleRobust();
+ case JSON_STRING:
+ try {
+ i64 res = 0;
+ if (Value.String && TryFromString(Value.String, res)) {
+ return res;
+ }
+ } catch (const yexception&) {
+ }
+ return 0;
+ case JSON_NULL:
+ case JSON_UNDEFINED:
+ default:
+ return 0;
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ return Value.Integer;
+ }
+ }
+
+ unsigned long long TJsonValue::GetUIntegerRobust() const noexcept {
+ switch (Type) {
+ case JSON_ARRAY:
+ return Value.Array->size();
+ case JSON_MAP:
+ return Value.Map->size();
+ case JSON_BOOLEAN:
+ return Value.Boolean;
+ case JSON_DOUBLE:
+ return GetDoubleRobust();
+ case JSON_STRING:
+ try {
+ ui64 res = 0;
+ if (Value.String && TryFromString(Value.String, res)) {
+ return res;
+ }
+ } catch (const yexception&) {
+ }
+ return 0;
+ case JSON_NULL:
+ case JSON_UNDEFINED:
+ default:
+ return 0;
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ return Value.UInteger;
+ }
+ }
+
+ double TJsonValue::GetDoubleRobust() const noexcept {
+ switch (Type) {
+ case JSON_ARRAY:
+ return Value.Array->size();
+ case JSON_MAP:
+ return Value.Map->size();
+ case JSON_BOOLEAN:
+ return Value.Boolean;
+ case JSON_INTEGER:
+ return Value.Integer;
+ case JSON_UINTEGER:
+ return Value.UInteger;
+ case JSON_STRING:
+ try {
+ double res = 0;
+ if (Value.String && TryFromString(Value.String, res)) {
+ return res;
+ }
+ } catch (const yexception&) {
+ }
+ return 0;
+ case JSON_NULL:
+ case JSON_UNDEFINED:
+ default:
+ return 0;
+ case JSON_DOUBLE:
+ return Value.Double;
+ }
+ }
+
+ TString TJsonValue::GetStringRobust() const {
+ switch (Type) {
+ case JSON_ARRAY:
+ case JSON_MAP:
+ case JSON_BOOLEAN:
+ case JSON_DOUBLE:
+ case JSON_INTEGER:
+ case JSON_UINTEGER:
+ case JSON_NULL:
+ case JSON_UNDEFINED:
+ default: {
+ NJsonWriter::TBuf sout;
+ sout.WriteJsonValue(this);
+ return sout.Str();
+ }
+ case JSON_STRING:
+ return Value.String;
+ }
+ }
+
+ bool TJsonValue::GetBoolean(bool* value) const noexcept {
+ if (Type != JSON_BOOLEAN)
+ return false;
+
+ *value = Value.Boolean;
+ return true;
+ }
+
+ bool TJsonValue::GetInteger(long long* value) const noexcept {
+ if (!IsInteger())
+ return false;
+
+ *value = GetInteger();
+ return true;
+ }
+
+ bool TJsonValue::GetUInteger(unsigned long long* value) const noexcept {
+ if (!IsUInteger())
+ return false;
+
+ *value = GetUInteger();
+ return true;
+ }
+
+ bool TJsonValue::GetDouble(double* value) const noexcept {
+ if (!IsDouble())
+ return false;
+
+ *value = GetDouble();
+ return true;
+ }
+
+ bool TJsonValue::GetString(TString* value) const {
+ if (Type != JSON_STRING)
+ return false;
+
+ *value = Value.String;
+ return true;
+ }
+
+ bool TJsonValue::GetMap(TJsonValue::TMapType* value) const {
+ if (Type != JSON_MAP)
+ return false;
+
+ *value = *Value.Map;
+ return true;
+ }
+
+ bool TJsonValue::GetArray(TJsonValue::TArray* value) const {
+ if (Type != JSON_ARRAY)
+ return false;
+
+ *value = *Value.Array;
+ return true;
+ }
+
+ bool TJsonValue::GetMapPointer(const TJsonValue::TMapType** value) const noexcept {
+ if (Type != JSON_MAP)
+ return false;
+
+ *value = Value.Map;
+ return true;
+ }
+
+ bool TJsonValue::GetArrayPointer(const TJsonValue::TArray** value) const noexcept {
+ if (Type != JSON_ARRAY)
+ return false;
+
+ *value = Value.Array;
+ return true;
+ }
+
+ bool TJsonValue::GetValue(const size_t index, TJsonValue* value) const {
+ const TJsonValue* tmp = nullptr;
+ if (GetValuePointer(index, &tmp)) {
+ *value = *tmp;
+ return true;
+ }
+ return false;
+ }
+
+ bool TJsonValue::GetValue(const TStringBuf key, TJsonValue* value) const {
+ const TJsonValue* tmp = nullptr;
+ if (GetValuePointer(key, &tmp)) {
+ *value = *tmp;
+ return true;
+ }
+ return false;
+ }
+
+ bool TJsonValue::GetValuePointer(const size_t index, const TJsonValue** value) const noexcept {
+ if (Type == JSON_ARRAY && index < Value.Array->size()) {
+ *value = &(*Value.Array)[index];
+ return true;
+ }
+ return false;
+ }
+
+ bool TJsonValue::GetValuePointer(const TStringBuf key, const TJsonValue** value) const noexcept {
+ if (Type == JSON_MAP) {
+ const TMapType::const_iterator it = Value.Map->find(key);
+ if (it != Value.Map->end()) {
+ *value = &(it->second);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool TJsonValue::GetValuePointer(const TStringBuf key, TJsonValue** value) noexcept {
+ return static_cast<const TJsonValue*>(this)->GetValuePointer(key, const_cast<const TJsonValue**>(value));
+ }
+
+ bool TJsonValue::IsNull() const noexcept {
+ return Type == JSON_NULL;
+ }
+
+ bool TJsonValue::IsBoolean() const noexcept {
+ return Type == JSON_BOOLEAN;
+ }
+
+ bool TJsonValue::IsInteger() const noexcept {
+ switch (Type) {
+ case JSON_INTEGER:
+ return true;
+
+ case JSON_UINTEGER:
+ return (Value.UInteger <= static_cast<unsigned long long>(Max<long long>()));
+
+ case JSON_DOUBLE:
+ return ((long long)Value.Double == Value.Double);
+
+ default:
+ return false;
+ }
+ }
+
+ bool TJsonValue::IsUInteger() const noexcept {
+ switch (Type) {
+ case JSON_UINTEGER:
+ return true;
+
+ case JSON_INTEGER:
+ return (Value.Integer >= 0);
+
+ case JSON_DOUBLE:
+ return ((unsigned long long)Value.Double == Value.Double);
+
+ default:
+ return false;
+ }
+ }
+
+ bool TJsonValue::IsDouble() const noexcept {
+ // Check whether we can convert integer to floating-point
+ // without precision loss.
+ switch (Type) {
+ case JSON_DOUBLE:
+ return true;
+
+ case JSON_INTEGER:
+ return (1ll << std::numeric_limits<double>::digits) >= Abs(Value.Integer);
+
+ case JSON_UINTEGER:
+ return (1ull << std::numeric_limits<double>::digits) >= Value.UInteger;
+
+ default:
+ return false;
+ }
+ }
+
+ namespace {
+ template <class TPtr, class T>
+ TPtr* CreateOrNullptr(TPtr* p, T key, std::true_type /*create*/) {
+ return &(*p)[key];
+ }
+
+ template <class TPtr, class T>
+ TPtr* CreateOrNullptr(const TPtr* p, T key, std::false_type /*create*/) noexcept {
+ const TPtr* const next = &(*p)[key];
+ return next->IsDefined() ? const_cast<TPtr*>(next) : nullptr;
+ }
+
+ template <bool Create, class TJsonPtr>
+ TJsonPtr GetValuePtrByPath(TJsonPtr currentJson, TStringBuf path, char delimiter) noexcept(!Create) {
+ static_assert(
+ !(Create && std::is_const<std::remove_pointer_t<TJsonPtr>>::value),
+ "TJsonPtr must be a `TJsonValue*` if `Create` is true");
+ constexpr std::integral_constant<bool, Create> create_tag{};
+
+ while (!path.empty()) {
+ size_t index = 0;
+ const TStringBuf step = path.NextTok(delimiter);
+ if (step.size() > 2 && *step.begin() == '[' && step.back() == ']' && TryFromString(step.substr(1, step.size() - 2), index)) {
+ currentJson = CreateOrNullptr(currentJson, index, create_tag);
+ } else {
+ currentJson = CreateOrNullptr(currentJson, step, create_tag);
+ }
+
+ if (!currentJson) {
+ return nullptr;
+ }
+ }
+
+ return currentJson;
+ }
+ } // anonymous namespace
+
+ bool TJsonValue::GetValueByPath(const TStringBuf path, TJsonValue& result, char delimiter) const {
+ const TJsonValue* const ptr = GetValuePtrByPath<false>(this, path, delimiter);
+ if (ptr) {
+ result = *ptr;
+ return true;
+ }
+ return false;
+ }
+
+ bool TJsonValue::SetValueByPath(const TStringBuf path, const TJsonValue& value, char delimiter) {
+ TJsonValue* const ptr = GetValuePtrByPath<true>(this, path, delimiter);
+ if (ptr) {
+ *ptr = value;
+ return true;
+ }
+ return false;
+ }
+
+ bool TJsonValue::SetValueByPath(const TStringBuf path, TJsonValue&& value, char delimiter) {
+ TJsonValue* const ptr = GetValuePtrByPath<true>(this, path, delimiter);
+ if (ptr) {
+ *ptr = std::move(value);
+ return true;
+ }
+ return false;
+ }
+
+ const TJsonValue* TJsonValue::GetValueByPath(const TStringBuf key, char delim) const noexcept {
+ return GetValuePtrByPath<false>(this, key, delim);
+ }
+
+ TJsonValue* TJsonValue::GetValueByPath(const TStringBuf key, char delim) noexcept {
+ return GetValuePtrByPath<false>(this, key, delim);
+ }
+
+ void TJsonValue::DoScan(const TString& path, TJsonValue* parent, IScanCallback& callback) {
+ if (!callback.Do(path, parent, *this)) {
+ return;
+ }
+
+ if (Type == JSON_MAP) {
+ for (auto&& i : *Value.Map) {
+ i.second.DoScan(!!path ? TString::Join(path, ".", i.first) : i.first, this, callback);
+ }
+ } else if (Type == JSON_ARRAY) {
+ for (ui32 i = 0; i < Value.Array->size(); ++i) {
+ (*Value.Array)[i].DoScan(TString::Join(path, "[", ToString(i), "]"), this, callback);
+ }
+ }
+ }
+
+ void TJsonValue::Scan(IScanCallback& callback) {
+ DoScan("", nullptr, callback);
+ }
+
+ bool TJsonValue::IsString() const noexcept {
+ return Type == JSON_STRING;
+ }
+
+ bool TJsonValue::IsMap() const noexcept {
+ return Type == JSON_MAP;
+ }
+
+ bool TJsonValue::IsArray() const noexcept {
+ return Type == JSON_ARRAY;
+ }
+
+ bool TJsonValue::Has(const TStringBuf& key) const noexcept {
+ return Type == JSON_MAP && Value.Map->contains(key);
+ }
+
+ bool TJsonValue::Has(size_t key) const noexcept {
+ return Type == JSON_ARRAY && Value.Array->size() > key;
+ }
+
+ bool TJsonValue::operator==(const TJsonValue& rhs) const {
+ switch (Type) {
+ case JSON_UNDEFINED: {
+ return (rhs.GetType() == JSON_UNDEFINED);
+ }
+
+ case JSON_NULL: {
+ return rhs.IsNull();
+ }
+
+ case JSON_BOOLEAN: {
+ return (rhs.IsBoolean() && Value.Boolean == rhs.Value.Boolean);
+ }
+
+ case JSON_INTEGER: {
+ return (rhs.IsInteger() && GetInteger() == rhs.GetInteger());
+ }
+
+ case JSON_UINTEGER: {
+ return (rhs.IsUInteger() && GetUInteger() == rhs.GetUInteger());
+ }
+
+ case JSON_STRING: {
+ return (rhs.IsString() && Value.String == rhs.Value.String);
+ }
+
+ case JSON_DOUBLE: {
+ return (rhs.IsDouble() && fabs(GetDouble() - rhs.GetDouble()) <= FLT_EPSILON);
+ }
+
+ case JSON_MAP:
+ return AreJsonMapsEqual(*this, rhs);
+
+ case JSON_ARRAY:
+ return AreJsonArraysEqual(*this, rhs);
+
+ default:
+ Y_ASSERT(false && "Unknown type.");
+ return false;
+ }
+ }
+
+ void TJsonValue::SwapWithUndefined(TJsonValue& output) noexcept {
+ if (Type == JSON_STRING) {
+ static_assert(std::is_nothrow_move_constructible<TString>::value, "noexcept violation! Add some try {} catch (...) logic");
+ new (&output.Value.String) TString(std::move(Value.String));
+ Value.String.~TString();
+ } else {
+ std::memcpy(&output.Value, &Value, sizeof(Value));
+ }
+
+ output.Type = Type;
+ Type = JSON_UNDEFINED;
+ }
+
+ void TJsonValue::Swap(TJsonValue& rhs) noexcept {
+ TJsonValue tmp(std::move(*this));
+ rhs.SwapWithUndefined(*this);
+ tmp.SwapWithUndefined(rhs);
+ }
+
+ void TJsonValue::Save(IOutputStream* s) const {
+ ::Save(s, static_cast<ui8>(Type));
+ switch (Type) {
+ case JSON_UNDEFINED:break;
+ case JSON_NULL:break;
+ case JSON_BOOLEAN:
+ ::Save(s, Value.Boolean);
+ break;
+ case JSON_INTEGER:
+ ::Save(s, Value.Integer);
+ break;
+ case JSON_UINTEGER:
+ ::Save(s, Value.UInteger);
+ break;
+ case JSON_DOUBLE:
+ ::Save(s, Value.Double);
+ break;
+ case JSON_STRING:
+ ::Save(s, Value.String);
+ break;
+ case JSON_MAP:
+ ::Save(s, *Value.Map);
+ break;
+ case JSON_ARRAY:
+ ::Save(s, *Value.Array);
+ break;
+ }
+ }
+
+ void TJsonValue::Load(IInputStream* s) {
+ {
+ ui8 loadedType = {};
+ ::Load(s, loadedType);
+ SetType(static_cast<EJsonValueType>(loadedType));
+ }
+ switch (Type) {
+ case JSON_UNDEFINED:break;
+ case JSON_NULL:break;
+ case JSON_BOOLEAN:
+ ::Load(s, Value.Boolean);
+ break;
+ case JSON_INTEGER:
+ ::Load(s, Value.Integer);
+ break;
+ case JSON_UINTEGER:
+ ::Load(s, Value.UInteger);
+ break;
+ case JSON_DOUBLE:
+ ::Load(s, Value.Double);
+ break;
+ case JSON_STRING:
+ ::Load(s, Value.String);
+ break;
+ case JSON_MAP:
+ ::Load(s, *Value.Map);
+ break;
+ case JSON_ARRAY:
+ ::Load(s, *Value.Array);
+ break;
+ }
+ }
+
+ //****************************************************************
+
+ bool GetMapPointer(const TJsonValue& jv, const size_t index, const TJsonValue::TMapType** value) {
+ const TJsonValue* v;
+ if (!jv.GetValuePointer(index, &v) || !v->IsMap())
+ return false;
+
+ *value = &v->GetMap();
+ return true;
+ }
+
+ bool GetArrayPointer(const TJsonValue& jv, const size_t index, const TJsonValue::TArray** value) {
+ const TJsonValue* v;
+ if (!jv.GetValuePointer(index, &v) || !v->IsArray())
+ return false;
+
+ *value = &v->GetArray();
+ return true;
+ }
+
+ bool GetMapPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TMapType** value) {
+ const TJsonValue* v;
+ if (!jv.GetValuePointer(key, &v) || !v->IsMap())
+ return false;
+
+ *value = &v->GetMap();
+ return true;
+ }
+
+ bool GetArrayPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TArray** value) {
+ const TJsonValue* v;
+ if (!jv.GetValuePointer(key, &v) || !v->IsArray())
+ return false;
+
+ *value = &v->GetArray();
+ return true;
+ }
+
+ void TJsonValue::BackChecks() const {
+ if (Type != JSON_ARRAY)
+ ythrow TJsonException() << "Not an array";
+
+ if (Value.Array->empty())
+ ythrow TJsonException() << "Get back on empty array";
+ }
+}
+
+template <>
+void Out<NJson::TJsonValue>(IOutputStream& out, const NJson::TJsonValue& v) {
+ NJsonWriter::TBuf buf(NJsonWriter::HEM_DONT_ESCAPE_HTML, &out);
+ buf.WriteJsonValue(&v);
+}
diff --git a/library/cpp/json/writer/json_value.h b/library/cpp/json/writer/json_value.h
new file mode 100644
index 0000000000..3f0f50bc4c
--- /dev/null
+++ b/library/cpp/json/writer/json_value.h
@@ -0,0 +1,294 @@
+#pragma once
+
+#include <library/cpp/json/common/defs.h>
+
+#include <util/generic/string.h>
+#include <util/generic/hash.h>
+#include <util/generic/vector.h>
+#include <util/generic/deque.h>
+#include <util/generic/utility.h>
+#include <util/generic/yexception.h>
+
+namespace NJson {
+ enum EJsonValueType {
+ JSON_UNDEFINED /* "Undefined" */,
+ JSON_NULL /* "Null" */,
+ JSON_BOOLEAN /* "Boolean" */,
+ JSON_INTEGER /* "Integer" */,
+ JSON_DOUBLE /* "Double" */,
+ JSON_STRING /* "String" */,
+ JSON_MAP /* "Map" */,
+ JSON_ARRAY /* "Array" */,
+ JSON_UINTEGER /* "UInteger" */
+ };
+
+ class TJsonValue;
+
+ class IScanCallback {
+ public:
+ virtual ~IScanCallback() = default;
+
+ virtual bool Do(const TString& path, TJsonValue* parent, TJsonValue& value) = 0;
+ };
+
+ class TJsonValue {
+ void Clear() noexcept;
+
+ public:
+ typedef THashMap<TString, TJsonValue> TMapType;
+ typedef TDeque<TJsonValue> TArray;
+
+ TJsonValue() noexcept = default;
+ TJsonValue(EJsonValueType type);
+ TJsonValue(bool value) noexcept;
+ TJsonValue(int value) noexcept;
+ TJsonValue(unsigned int value) noexcept;
+ TJsonValue(long value) noexcept;
+ TJsonValue(unsigned long value) noexcept;
+ TJsonValue(long long value) noexcept;
+ TJsonValue(unsigned long long value) noexcept;
+ TJsonValue(double value) noexcept;
+ TJsonValue(TString value);
+ TJsonValue(const char* value);
+ template <class T>
+ TJsonValue(const T*) = delete;
+ TJsonValue(TStringBuf value);
+
+ TJsonValue(const std::string& s)
+ : TJsonValue(TStringBuf(s))
+ {
+ }
+
+ TJsonValue(const TJsonValue& vval);
+ TJsonValue(TJsonValue&& vval) noexcept;
+
+ TJsonValue& operator=(const TJsonValue& val);
+ TJsonValue& operator=(TJsonValue&& val) noexcept;
+
+ ~TJsonValue() {
+ Clear();
+ }
+
+ EJsonValueType GetType() const noexcept;
+ TJsonValue& SetType(EJsonValueType type);
+
+ TJsonValue& SetValue(const TJsonValue& value);
+ TJsonValue& SetValue(TJsonValue&& value);
+
+ // for Map
+ TJsonValue& InsertValue(const TString& key, const TJsonValue& value);
+ TJsonValue& InsertValue(TStringBuf key, const TJsonValue& value);
+ TJsonValue& InsertValue(const char* key, const TJsonValue& value);
+ TJsonValue& InsertValue(const TString& key, TJsonValue&& value);
+ TJsonValue& InsertValue(TStringBuf key, TJsonValue&& value);
+ TJsonValue& InsertValue(const char* key, TJsonValue&& value);
+
+ // for Array
+ TJsonValue& AppendValue(const TJsonValue& value);
+ TJsonValue& AppendValue(TJsonValue&& value);
+ TJsonValue& Back();
+ const TJsonValue& Back() const;
+
+ bool GetValueByPath(TStringBuf path, TJsonValue& result, char delimiter = '.') const;
+ bool SetValueByPath(TStringBuf path, const TJsonValue& value, char delimiter = '.');
+ bool SetValueByPath(TStringBuf path, TJsonValue&& value, char delimiter = '.');
+
+ // returns NULL on failure
+ const TJsonValue* GetValueByPath(TStringBuf path, char delimiter = '.') const noexcept;
+ TJsonValue* GetValueByPath(TStringBuf path, char delimiter = '.') noexcept;
+
+ void EraseValue(TStringBuf key);
+ void EraseValue(size_t index);
+
+ TJsonValue& operator[](size_t idx);
+ TJsonValue& operator[](const TStringBuf& key);
+ const TJsonValue& operator[](size_t idx) const noexcept;
+ const TJsonValue& operator[](const TStringBuf& key) const noexcept;
+
+ bool GetBoolean() const;
+ long long GetInteger() const;
+ unsigned long long GetUInteger() const;
+ double GetDouble() const;
+ const TString& GetString() const;
+ const TMapType& GetMap() const;
+ const TArray& GetArray() const;
+
+ //throwing TJsonException possible
+ bool GetBooleanSafe() const;
+ long long GetIntegerSafe() const;
+ unsigned long long GetUIntegerSafe() const;
+ double GetDoubleSafe() const;
+ const TString& GetStringSafe() const;
+ const TMapType& GetMapSafe() const;
+ TMapType& GetMapSafe();
+ const TArray& GetArraySafe() const;
+ TArray& GetArraySafe();
+
+ bool GetBooleanSafe(bool defaultValue) const;
+ long long GetIntegerSafe(long long defaultValue) const;
+ unsigned long long GetUIntegerSafe(unsigned long long defaultValue) const;
+ double GetDoubleSafe(double defaultValue) const;
+ TString GetStringSafe(const TString& defaultValue) const;
+
+ bool GetBooleanRobust() const noexcept;
+ long long GetIntegerRobust() const noexcept;
+ unsigned long long GetUIntegerRobust() const noexcept;
+ double GetDoubleRobust() const noexcept;
+ TString GetStringRobust() const;
+
+ // Exception-free accessors
+ bool GetBoolean(bool* value) const noexcept;
+ bool GetInteger(long long* value) const noexcept;
+ bool GetUInteger(unsigned long long* value) const noexcept;
+ bool GetDouble(double* value) const noexcept;
+ bool GetMapPointer(const TMapType** value) const noexcept;
+ bool GetArrayPointer(const TArray** value) const noexcept;
+
+ bool GetString(TString* value) const;
+ bool GetMap(TMapType* value) const;
+ bool GetArray(TArray* value) const;
+ bool GetValue(size_t index, TJsonValue* value) const;
+ bool GetValue(TStringBuf key, TJsonValue* value) const;
+ bool GetValuePointer(size_t index, const TJsonValue** value) const noexcept;
+ bool GetValuePointer(TStringBuf key, const TJsonValue** value) const noexcept;
+ bool GetValuePointer(TStringBuf key, TJsonValue** value) noexcept;
+
+ // Checking for defined non-null value
+ bool IsDefined() const noexcept {
+ return Type != JSON_UNDEFINED && Type != JSON_NULL;
+ }
+
+ bool IsNull() const noexcept;
+ bool IsBoolean() const noexcept;
+ bool IsDouble() const noexcept;
+ bool IsString() const noexcept;
+ bool IsMap() const noexcept;
+ bool IsArray() const noexcept;
+
+ /// @return true if JSON_INTEGER or (JSON_UINTEGER and Value <= Max<long long>)
+ bool IsInteger() const noexcept;
+
+ /// @return true if JSON_UINTEGER or (JSON_INTEGER and Value >= 0)
+ bool IsUInteger() const noexcept;
+
+ bool Has(const TStringBuf& key) const noexcept;
+ bool Has(size_t key) const noexcept;
+
+ void Scan(IScanCallback& callback);
+
+ /// Non-robust comparison.
+ bool operator==(const TJsonValue& rhs) const;
+
+ bool operator!=(const TJsonValue& rhs) const {
+ return !(*this == rhs);
+ }
+
+ void Swap(TJsonValue& rhs) noexcept;
+
+ // save using util/ysaveload.h serialization (not to JSON stream)
+ void Save(IOutputStream* s) const;
+
+ // load using util/ysaveload.h serialization (not as JSON stream)
+ void Load(IInputStream* s);
+
+ static const TJsonValue UNDEFINED;
+
+ private:
+ EJsonValueType Type = JSON_UNDEFINED;
+ union TValueUnion {
+ bool Boolean;
+ long long Integer;
+ unsigned long long UInteger;
+ double Double;
+ TString String;
+ TMapType* Map;
+ TArray* Array;
+
+ TValueUnion() noexcept {
+ Zero(*this);
+ }
+ ~TValueUnion() noexcept {
+ }
+ };
+ TValueUnion Value;
+ void DoScan(const TString& path, TJsonValue* parent, IScanCallback& callback);
+ void SwapWithUndefined(TJsonValue& output) noexcept;
+
+ /**
+ @throw yexception if Back shouldn't be called on the object.
+ */
+ void BackChecks() const;
+ };
+
+ inline bool GetBoolean(const TJsonValue& jv, size_t index, bool* value) noexcept {
+ return jv[index].GetBoolean(value);
+ }
+
+ inline bool GetInteger(const TJsonValue& jv, size_t index, long long* value) noexcept {
+ return jv[index].GetInteger(value);
+ }
+
+ inline bool GetUInteger(const TJsonValue& jv, size_t index, unsigned long long* value) noexcept {
+ return jv[index].GetUInteger(value);
+ }
+
+ inline bool GetDouble(const TJsonValue& jv, size_t index, double* value) noexcept {
+ return jv[index].GetDouble(value);
+ }
+
+ inline bool GetString(const TJsonValue& jv, size_t index, TString* value) {
+ return jv[index].GetString(value);
+ }
+
+ bool GetMapPointer(const TJsonValue& jv, size_t index, const TJsonValue::TMapType** value);
+ bool GetArrayPointer(const TJsonValue& jv, size_t index, const TJsonValue::TArray** value);
+
+ inline bool GetBoolean(const TJsonValue& jv, TStringBuf key, bool* value) noexcept {
+ return jv[key].GetBoolean(value);
+ }
+
+ inline bool GetInteger(const TJsonValue& jv, TStringBuf key, long long* value) noexcept {
+ return jv[key].GetInteger(value);
+ }
+
+ inline bool GetUInteger(const TJsonValue& jv, TStringBuf key, unsigned long long* value) noexcept {
+ return jv[key].GetUInteger(value);
+ }
+
+ inline bool GetDouble(const TJsonValue& jv, TStringBuf key, double* value) noexcept {
+ return jv[key].GetDouble(value);
+ }
+
+ inline bool GetString(const TJsonValue& jv, TStringBuf key, TString* value) {
+ return jv[key].GetString(value);
+ }
+
+ bool GetMapPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TMapType** value);
+ bool GetArrayPointer(const TJsonValue& jv, const TStringBuf key, const TJsonValue::TArray** value);
+
+ class TJsonMap: public TJsonValue {
+ public:
+ TJsonMap()
+ : TJsonValue(NJson::JSON_MAP)
+ {}
+
+ TJsonMap(const std::initializer_list<std::pair<TString, TJsonValue>>& list)
+ : TJsonValue(NJson::JSON_MAP)
+ {
+ GetMapSafe() = THashMap<TString, TJsonValue>(list);
+ }
+ };
+
+ class TJsonArray: public TJsonValue {
+ public:
+ TJsonArray()
+ : TJsonValue(NJson::JSON_ARRAY)
+ {}
+
+ TJsonArray(const std::initializer_list<TJsonValue>& list)
+ : TJsonValue(NJson::JSON_ARRAY)
+ {
+ GetArraySafe() = TJsonValue::TArray(list);
+ }
+ };
+}
diff --git a/library/cpp/json/writer/json_value_ut.cpp b/library/cpp/json/writer/json_value_ut.cpp
new file mode 100644
index 0000000000..dc7f6affdf
--- /dev/null
+++ b/library/cpp/json/writer/json_value_ut.cpp
@@ -0,0 +1,650 @@
+#include "json_value.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/stream/input.h>
+
+using namespace NJson;
+
+Y_UNIT_TEST_SUITE(TJsonValueTest) {
+ Y_UNIT_TEST(UndefTest) {
+ TJsonValue undef;
+ TJsonValue null(JSON_NULL);
+ TJsonValue _false(false);
+ TJsonValue zeroInt(0);
+ TJsonValue zeroDouble(0.0);
+ TJsonValue emptyStr("");
+ TJsonValue emptyArray(JSON_ARRAY);
+ TJsonValue emptyMap(JSON_MAP);
+
+ UNIT_ASSERT(!undef.IsDefined());
+ UNIT_ASSERT(!null.IsDefined()); // json NULL is undefined too!
+ UNIT_ASSERT(_false.IsDefined());
+ UNIT_ASSERT(zeroInt.IsDefined());
+ UNIT_ASSERT(zeroDouble.IsDefined());
+ UNIT_ASSERT(emptyStr.IsDefined());
+ UNIT_ASSERT(emptyArray.IsDefined());
+ UNIT_ASSERT(emptyMap.IsDefined());
+
+ UNIT_ASSERT(undef == TJsonValue());
+ UNIT_ASSERT(undef != null);
+ UNIT_ASSERT(undef != _false);
+ UNIT_ASSERT(undef != zeroInt);
+ UNIT_ASSERT(undef != zeroDouble);
+ UNIT_ASSERT(undef != emptyStr);
+ UNIT_ASSERT(undef != emptyArray);
+ UNIT_ASSERT(undef != emptyMap);
+ }
+
+ Y_UNIT_TEST(DefaultCompareTest) {
+ {
+ TJsonValue lhs;
+ TJsonValue rhs;
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs;
+ TJsonValue rhs(JSON_NULL);
+ UNIT_ASSERT(lhs != rhs);
+ UNIT_ASSERT(rhs != lhs);
+ }
+ }
+
+ Y_UNIT_TEST(NullCompareTest) {
+ TJsonValue lhs(JSON_NULL);
+ TJsonValue rhs(JSON_NULL);
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ Y_UNIT_TEST(StringCompareTest) {
+ {
+ TJsonValue lhs(JSON_STRING);
+ TJsonValue rhs(JSON_STRING);
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs("");
+ TJsonValue rhs("");
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs("abc");
+ TJsonValue rhs("abc");
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs("1");
+ TJsonValue rhs(1);
+ UNIT_ASSERT(lhs != rhs);
+ UNIT_ASSERT(rhs != lhs);
+ }
+ }
+
+ Y_UNIT_TEST(ArrayCompareTest) {
+ {
+ TJsonValue lhs(JSON_ARRAY);
+ TJsonValue rhs(JSON_ARRAY);
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs;
+ TJsonValue rhs;
+
+ lhs.AppendValue(TJsonValue());
+
+ UNIT_ASSERT(lhs != rhs);
+ UNIT_ASSERT(rhs != lhs);
+ }
+
+ {
+ TJsonValue lhs;
+ TJsonValue rhs;
+
+ lhs.AppendValue(1);
+ lhs.AppendValue("2");
+ lhs.AppendValue(3.0);
+ lhs.AppendValue(TJsonValue());
+ lhs.AppendValue(TJsonValue(JSON_NULL));
+
+ rhs.AppendValue(1);
+ rhs.AppendValue("2");
+ rhs.AppendValue(3.0);
+ rhs.AppendValue(TJsonValue());
+ rhs.AppendValue(TJsonValue(JSON_NULL));
+
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs;
+ TJsonValue rhs;
+
+ lhs.AppendValue(1);
+ rhs.AppendValue("1");
+
+ UNIT_ASSERT(lhs != rhs);
+ UNIT_ASSERT(rhs != lhs);
+ }
+ }
+
+ Y_UNIT_TEST(CompareTest) {
+ {
+ TJsonValue lhs;
+ lhs.InsertValue("null value", TJsonValue(JSON_NULL));
+ lhs.InsertValue("int key", TJsonValue(10));
+ lhs.InsertValue("double key", TJsonValue(11.11));
+ lhs.InsertValue("string key", TJsonValue("string"));
+
+ TJsonValue array;
+ array.AppendValue(1);
+ array.AppendValue(2);
+ array.AppendValue(3);
+ array.AppendValue("string");
+ lhs.InsertValue("array", array);
+
+ lhs.InsertValue("bool key", TJsonValue(true));
+
+ TJsonValue rhs;
+ rhs = lhs;
+
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ // Insert keys in different orders
+ const int NUM_KEYS = 1000;
+
+ TJsonValue lhs;
+ for (int i = 0; i < NUM_KEYS; ++i)
+ lhs.InsertValue(ToString(i), i);
+
+ TJsonValue rhs;
+ for (int i = 0; i < NUM_KEYS; i += 2)
+ rhs.InsertValue(ToString(i), i);
+ for (int i = 1; i < NUM_KEYS; i += 2)
+ rhs.InsertValue(ToString(i), i);
+
+ UNIT_ASSERT(lhs == rhs);
+ UNIT_ASSERT(rhs == lhs);
+ }
+
+ {
+ TJsonValue lhs;
+ lhs.InsertValue("null value", TJsonValue(JSON_NULL));
+ lhs.InsertValue("int key", TJsonValue(10));
+ lhs.InsertValue("double key", TJsonValue(11.11));
+ lhs.InsertValue("string key", TJsonValue("string"));
+
+ TJsonValue array;
+ array.AppendValue(1);
+ array.AppendValue(2);
+ array.AppendValue(3);
+ array.AppendValue("string");
+ lhs.InsertValue("array", array);
+
+ lhs.InsertValue("bool key", TJsonValue(true));
+
+ TJsonValue rhs;
+ rhs.InsertValue("null value", TJsonValue(JSON_NULL));
+ rhs.InsertValue("int key", TJsonValue(10));
+ rhs.InsertValue("double key", TJsonValue(11.11));
+ rhs.InsertValue("string key", TJsonValue("string"));
+ rhs.InsertValue("bool key", TJsonValue(true));
+
+ UNIT_ASSERT(lhs != rhs);
+ UNIT_ASSERT(rhs != lhs);
+ }
+ }
+
+ Y_UNIT_TEST(SwapTest) {
+ {
+ TJsonValue lhs;
+ lhs.InsertValue("a", "b");
+ TJsonValue lhsCopy = lhs;
+
+ TJsonValue rhs(JSON_NULL);
+ TJsonValue rhsCopy = rhs;
+
+ UNIT_ASSERT(lhs == lhsCopy);
+ UNIT_ASSERT(rhs == rhsCopy);
+
+ lhs.Swap(rhs);
+
+ UNIT_ASSERT(rhs == lhsCopy);
+ UNIT_ASSERT(lhs == rhsCopy);
+
+ lhs.Swap(rhs);
+
+ UNIT_ASSERT(lhs == lhsCopy);
+ UNIT_ASSERT(rhs == rhsCopy);
+ }
+ }
+
+ Y_UNIT_TEST(GetValueByPathTest) {
+ {
+ TJsonValue lhs;
+ TJsonValue first;
+ TJsonValue second;
+ TJsonValue last;
+ first.InsertValue("e", "f");
+ second.InsertValue("c", first);
+ last.InsertValue("a", second);
+ lhs.InsertValue("l", last);
+
+ TJsonValue result;
+ UNIT_ASSERT(lhs.GetValueByPath("l/a/c/e", result, '/'));
+ UNIT_ASSERT(result.GetStringRobust() == "f");
+ UNIT_ASSERT(!lhs.GetValueByPath("l/a/c/se", result, '/'));
+ UNIT_ASSERT(lhs.GetValueByPath("l/a/c", result, '/'));
+ UNIT_ASSERT(result.GetStringRobust() == "{\"e\":\"f\"}");
+
+ // faster TStringBuf version
+ UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l", '/'), last);
+ UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l/a", '/'), second);
+ UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l/a/c", '/'), first);
+ UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("l.a.c.e", '.'), "f");
+ UNIT_ASSERT_EQUAL(lhs.GetValueByPath("l/a/c/e/x", '/'), NULL);
+ UNIT_ASSERT_EQUAL(lhs.GetValueByPath("a/c/e/x", '/'), NULL);
+ UNIT_ASSERT_EQUAL(lhs.GetValueByPath("nokey", '/'), NULL);
+ UNIT_ASSERT_EQUAL(*lhs.GetValueByPath("", '/'), lhs); // itself
+
+ TJsonValue array;
+ TJsonValue third;
+ array[0] = first;
+ array[1] = second;
+ third["t"] = array;
+
+ UNIT_ASSERT(array.GetValueByPath("[0].e", result));
+ UNIT_ASSERT(result.GetStringRobust() == "f");
+ UNIT_ASSERT(third.GetValueByPath("t.[0].e", result));
+ UNIT_ASSERT(result.GetStringRobust() == "f");
+ UNIT_ASSERT(third.GetValueByPath("t.[1].c.e", result));
+ UNIT_ASSERT(result.GetStringRobust() == "f");
+ UNIT_ASSERT(!third.GetValueByPath("t.[2]", result));
+
+ UNIT_ASSERT(third.SetValueByPath("t.[2]", "g"));
+ UNIT_ASSERT(third.GetValueByPath("t.[2]", result));
+ UNIT_ASSERT(result.GetStringRobust() == "g");
+
+ UNIT_ASSERT(lhs.SetValueByPath("l/a/c/se", "h", '/'));
+ UNIT_ASSERT(lhs.GetValueByPath("l/a/c/se", result, '/'));
+ UNIT_ASSERT(result.GetStringRobust() == "h");
+ }
+ }
+
+ Y_UNIT_TEST(GetValueByPathConstTest) {
+ TJsonValue lhs;
+ TJsonValue first;
+ TJsonValue second;
+ TJsonValue last;
+ first.InsertValue("e", "f");
+ second.InsertValue("c", first);
+ last.InsertValue("a", second);
+ lhs.InsertValue("l", last);
+
+ {
+ const TJsonValue* result = lhs.GetValueByPath("l", '/');
+ UNIT_ASSERT_EQUAL(*result, last);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("l/a", '/');
+ UNIT_ASSERT_EQUAL(*result, second);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("l/a/c", '/');
+ UNIT_ASSERT_EQUAL(*result, first);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("l.a.c.e", '.');
+ UNIT_ASSERT_EQUAL(*result, "f");
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("l/a/c/e/x", '/');
+ UNIT_ASSERT_EQUAL(result, nullptr);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("a/c/e/x", '/');
+ UNIT_ASSERT_EQUAL(result, nullptr);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("nokey", '/');
+ UNIT_ASSERT_EQUAL(result, nullptr);
+ }
+ {
+ const TJsonValue* result = lhs.GetValueByPath("", '/');
+ UNIT_ASSERT_EQUAL(*result, lhs); // itself
+ }
+
+ TJsonValue array;
+ TJsonValue third;
+ array[0] = first;
+ array[1] = second;
+ third["t"] = array;
+
+ UNIT_ASSERT(array.GetValueByPath("[0].e", '.')->GetStringRobust() == "f");
+ UNIT_ASSERT(third.GetValueByPath("t.[0].e", '.')->GetStringRobust() == "f");
+ UNIT_ASSERT(third.GetValueByPath("t.[1].c.e", '.')->GetStringRobust() == "f");
+ }
+
+ Y_UNIT_TEST(EraseValueFromArray) {
+ {
+ TJsonValue vec;
+ vec.AppendValue(TJsonValue(0));
+ vec.AppendValue(TJsonValue(1));
+ vec.AppendValue(TJsonValue("2"));
+ vec.AppendValue(TJsonValue("3.14"));
+
+ TJsonValue vec1;
+ vec1.AppendValue(TJsonValue(0));
+ vec1.AppendValue(TJsonValue("2"));
+ vec1.AppendValue(TJsonValue("3.14"));
+
+ TJsonValue vec2;
+ vec2.AppendValue(TJsonValue(0));
+ vec2.AppendValue(TJsonValue("2"));
+
+ TJsonValue vec3;
+ vec3.AppendValue(TJsonValue("2"));
+
+ TJsonValue vec4(JSON_ARRAY);
+
+ UNIT_ASSERT(vec.IsArray());
+ UNIT_ASSERT(vec.GetArray().size() == 4);
+ vec.EraseValue(1);
+ UNIT_ASSERT(vec.GetArray().size() == 3);
+ UNIT_ASSERT(vec == vec1);
+ vec.EraseValue(2);
+ UNIT_ASSERT(vec.GetArray().size() == 2);
+ UNIT_ASSERT(vec == vec2);
+ vec.EraseValue(0);
+ UNIT_ASSERT(vec.GetArray().size() == 1);
+ UNIT_ASSERT(vec == vec3);
+ vec.EraseValue(0);
+ UNIT_ASSERT(vec.GetArray().size() == 0);
+ UNIT_ASSERT(vec == vec4);
+ }
+ }
+
+ Y_UNIT_TEST(NonConstMethodsTest) {
+ {
+ TJsonValue src;
+ TJsonValue value1;
+ value1.AppendValue(1);
+ value1.AppendValue(2);
+ src.InsertValue("key", value1);
+ src.InsertValue("key1", "HI!");
+
+ TJsonValue dst;
+ TJsonValue value2;
+ value2.AppendValue(1);
+ value2.AppendValue(2);
+ value2.AppendValue(3);
+ dst.InsertValue("key", value2);
+
+ src.GetValueByPath("key", '.')->AppendValue(3);
+ src.EraseValue("key1");
+ UNIT_ASSERT(src == dst);
+
+ dst.GetValueByPath("key", '.')->EraseValue(0);
+ UNIT_ASSERT(src != dst);
+ src.GetValueByPath("key", '.')->EraseValue(0);
+ UNIT_ASSERT(src == dst);
+ }
+
+ {
+ TJsonValue src;
+ TJsonValue value1;
+ TJsonValue arr1;
+ value1.InsertValue("key", "value");
+ arr1.AppendValue(value1);
+ arr1.AppendValue(value1);
+ arr1.AppendValue(value1);
+ src.InsertValue("arr", arr1);
+
+ TJsonValue dst;
+ TJsonValue value2;
+ TJsonValue arr2;
+ value2.InsertValue("key", "value");
+ value2.InsertValue("yek", "eulav");
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ dst.InsertValue("arr", arr2);
+
+ src["arr"].AppendValue(value1);
+ for (auto& node : src["arr"].GetArraySafe()) {
+ node.InsertValue("yek", "eulav");
+ }
+ UNIT_ASSERT(src == dst);
+ }
+
+ {
+ TJsonValue src;
+ TJsonValue value1;
+ TJsonValue arr1;
+ value1.InsertValue("key", "value");
+ arr1.AppendValue(value1);
+ arr1.AppendValue(value1);
+ arr1.AppendValue(value1);
+ src.InsertValue("arr", arr1);
+
+ TJsonValue dst;
+ TJsonValue value2;
+ TJsonValue arr2;
+ value2.InsertValue("key", "value");
+ value2.InsertValue("yek", "eulav");
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ arr2.AppendValue(value2);
+ dst.InsertValue("arr", arr2);
+
+ src["arr"].AppendValue(value1);
+ for (auto& node : src.GetValueByPath("arr", '.')->GetArraySafe()) {
+ node.InsertValue("yek", "eulav");
+ }
+ UNIT_ASSERT(src == dst);
+ }
+
+ {
+ TJsonValue json;
+ json.InsertValue("key", "value");
+ try {
+ json.GetArraySafe();
+ UNIT_ASSERT(false);
+ } catch (const TJsonException&) {
+ }
+
+ const TJsonValue constJson(json);
+ try {
+ constJson.GetArray();
+ } catch (...) {
+ UNIT_ASSERT(false);
+ }
+ }
+
+ {
+ // Check non-const GetArraySafe()
+ TJsonValue json{JSON_ARRAY};
+ json.GetArraySafe().push_back(TJsonValue{"foo"});
+
+ TJsonValue expectedJson;
+ expectedJson.AppendValue(TJsonValue{"foo"});
+ UNIT_ASSERT(json == expectedJson);
+
+ TJsonValue::TArray jsonArray = std::move(json.GetArraySafe());
+ TJsonValue::TArray expectedArray = {TJsonValue{"foo"}};
+ UNIT_ASSERT(jsonArray == expectedArray);
+ }
+
+ {
+ // Check non-const GetMap()
+ TJsonValue json{JSON_MAP};
+ json.GetMapSafe()["foo"] = "bar";
+
+ TJsonValue expectedJson;
+ expectedJson.InsertValue("foo", "bar");
+ UNIT_ASSERT(json == expectedJson);
+
+ TJsonValue::TMapType jsonMap = std::move(json.GetMapSafe());
+ TJsonValue::TMapType expectedMap = {{"foo", TJsonValue{"bar"}}};
+ UNIT_ASSERT(jsonMap == expectedMap);
+ }
+ }
+
+ Y_UNIT_TEST(NonexistentFieldAccessTest) {
+ {
+ TJsonValue json;
+ json.InsertValue("some", "key");
+
+ UNIT_ASSERT(!json["some"]["weird"]["access"]["sequence"].Has("value"));
+ UNIT_ASSERT(!json["some"]["weird"]["access"]["sequence"].IsDefined());
+
+ UNIT_ASSERT(json["some"].GetType() == JSON_MAP);
+ }
+ }
+
+ Y_UNIT_TEST(DefaultValuesTest) {
+ {
+ TJsonValue json;
+ json.InsertValue("some", "key");
+ json.InsertValue("existing", 1.2);
+
+ UNIT_ASSERT_VALUES_EQUAL(json["existing"].GetDoubleSafe(), 1.2);
+ UNIT_ASSERT_VALUES_EQUAL(json["existing"].GetDoubleSafe(15), 1.2);
+
+ UNIT_ASSERT_EXCEPTION(json["some"].GetUIntegerSafe(), yexception);
+ UNIT_ASSERT_EXCEPTION(json["some"].GetUIntegerSafe(12), yexception);
+
+ UNIT_ASSERT_EXCEPTION(json["nonexistent"].GetUIntegerSafe(), yexception);
+ UNIT_ASSERT_VALUES_EQUAL(json["nonexistent"].GetUIntegerSafe(12), 12);
+ UNIT_ASSERT_VALUES_EQUAL(json["nonexistent"]["more_nonexistent"].GetUIntegerSafe(12), 12);
+
+ json.InsertValue("map", TJsonValue(JSON_MAP));
+
+ UNIT_ASSERT_VALUES_EQUAL(json["map"]["nonexistent"].GetUIntegerSafe(12), 12);
+ }
+ }
+
+ Y_UNIT_TEST(GetArrayPointerInArrayTest) {
+ TJsonValue outer;
+ {
+ TJsonValue json;
+ json.AppendValue(1);
+ json.AppendValue(2);
+ json.AppendValue(3);
+
+ outer.AppendValue(json);
+ }
+ const TJsonValue::TArray* array = nullptr;
+ GetArrayPointer(outer, 0, &array);
+ UNIT_ASSERT_VALUES_EQUAL((*array)[1], 2);
+ }
+
+ Y_UNIT_TEST(GetArrayPointerInMapTest) {
+ TJsonValue outer;
+ {
+ TJsonValue json;
+ json.AppendValue(1);
+ json.AppendValue(2);
+ json.AppendValue(3);
+
+ outer.InsertValue("x", json);
+ }
+ const TJsonValue::TArray* array = nullptr;
+ GetArrayPointer(outer, "x", &array);
+ UNIT_ASSERT_VALUES_EQUAL((*array)[1], 2);
+ }
+
+ Y_UNIT_TEST(GetMapPointerInArrayTest) {
+ TJsonValue outer;
+ {
+ TJsonValue json;
+ json.InsertValue("a", 1);
+ json.InsertValue("b", 2);
+ json.InsertValue("c", 3);
+
+ outer.AppendValue(json);
+ }
+ const TJsonValue::TMapType* map = nullptr;
+ GetMapPointer(outer, 0, &map);
+ UNIT_ASSERT_VALUES_EQUAL((*map).at("b"), 2);
+ }
+
+ Y_UNIT_TEST(GetMapPointerInMapTest) {
+ TJsonValue outer;
+ {
+ TJsonValue json;
+ json.InsertValue("a", 1);
+ json.InsertValue("b", 2);
+ json.InsertValue("c", 3);
+
+ outer.InsertValue("x", json);
+ }
+ const TJsonValue::TMapType* map = nullptr;
+ GetMapPointer(outer, "x", &map);
+ UNIT_ASSERT_VALUES_EQUAL((*map).at("b"), 2);
+ }
+
+ Y_UNIT_TEST(GetIntegerRobustBignumStringTest) {
+ TString value = "1626862681464633683";
+ TJsonValue json(value);
+ UNIT_ASSERT_VALUES_EQUAL(json.GetUIntegerRobust(), FromString<ui64>(value));
+ UNIT_ASSERT_VALUES_EQUAL(json.GetIntegerRobust(), FromString<i64>(value));
+ }
+
+ Y_UNIT_TEST(MoveSubpartToSelf) {
+ TJsonValue json;
+ json[0] = "testing 0";
+ json[1] = "testing 1";
+ json[2] = "testing 2";
+ json = std::move(json[1]);
+ UNIT_ASSERT_VALUES_EQUAL(json.GetString(), "testing 1");
+
+ const char* longTestString =
+ "Testing TJsonValue& operator=(TJsonValue&&) subpart self moving "
+ "after TJsonValue was constrcuted from TString&&.";
+
+ json["hello"] = TString{longTestString};
+ json = std::move(json["hello"]);
+ UNIT_ASSERT_VALUES_EQUAL(json.GetString(), longTestString);
+ }
+
+ Y_UNIT_TEST(TJsonArrayMapConstructor) {
+ TJsonMap emptyMap;
+ UNIT_ASSERT_VALUES_EQUAL(emptyMap.GetType(), JSON_MAP);
+ UNIT_ASSERT_VALUES_EQUAL(emptyMap.GetMapSafe().size(), 0);
+
+ TJsonArray emptyArray;
+ UNIT_ASSERT_VALUES_EQUAL(emptyArray.GetType(), JSON_ARRAY);
+ UNIT_ASSERT_VALUES_EQUAL(emptyArray.GetArraySafe().size(), 0);
+
+ TJsonMap filled = {
+ {"1", 1},
+ {"2", "2"},
+ {"3", TJsonArray{3}},
+ {"4", TJsonMap{{"5", 5}}},
+ };
+ UNIT_ASSERT_VALUES_EQUAL(filled.GetType(), JSON_MAP);
+ UNIT_ASSERT_VALUES_EQUAL(filled["1"], TJsonValue{1});
+ UNIT_ASSERT_VALUES_EQUAL(filled["2"], TJsonValue{"2"});
+ UNIT_ASSERT_VALUES_EQUAL(filled["3"].GetArraySafe().size(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(filled["3"][0], TJsonValue{3});
+ UNIT_ASSERT_VALUES_EQUAL(filled["4"].GetMapSafe().size(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(filled["4"]["5"], TJsonValue{5});
+ }
+} // TJsonValueTest
diff --git a/library/cpp/json/writer/ut/ya.make b/library/cpp/json/writer/ut/ya.make
new file mode 100644
index 0000000000..1e39dae6a1
--- /dev/null
+++ b/library/cpp/json/writer/ut/ya.make
@@ -0,0 +1,16 @@
+UNITTEST()
+
+OWNER(myltsev)
+
+PEERDIR(
+ ADDINCL library/cpp/json/writer
+)
+
+SRCDIR(library/cpp/json/writer)
+
+SRCS(
+ json_ut.cpp
+ json_value_ut.cpp
+)
+
+END()
diff --git a/library/cpp/json/writer/ya.make b/library/cpp/json/writer/ya.make
new file mode 100644
index 0000000000..3989ff3504
--- /dev/null
+++ b/library/cpp/json/writer/ya.make
@@ -0,0 +1,20 @@
+LIBRARY()
+
+OWNER(
+ mvel
+ myltsev
+ pg
+)
+
+PEERDIR(
+ library/cpp/json/common
+)
+
+SRCS(
+ json_value.cpp
+ json.cpp
+)
+
+GENERATE_ENUM_SERIALIZATION(json_value.h)
+
+END()