diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/json/writer | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/json/writer')
-rw-r--r-- | library/cpp/json/writer/README | 23 | ||||
-rw-r--r-- | library/cpp/json/writer/json.cpp | 517 | ||||
-rw-r--r-- | library/cpp/json/writer/json.h | 289 | ||||
-rw-r--r-- | library/cpp/json/writer/json_ut.cpp | 307 | ||||
-rw-r--r-- | library/cpp/json/writer/json_value.cpp | 1105 | ||||
-rw-r--r-- | library/cpp/json/writer/json_value.h | 294 | ||||
-rw-r--r-- | library/cpp/json/writer/json_value_ut.cpp | 650 | ||||
-rw-r--r-- | library/cpp/json/writer/ut/ya.make | 16 | ||||
-rw-r--r-- | library/cpp/json/writer/ya.make | 20 |
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('"', """); + MATCH('\'', "'"); + MATCH('<', "<"); + MATCH('>', ">"); + MATCH('&', "&"); + } + //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: < > & \/ + 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 ' <tag> &ent; "txt" \""; + 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() |