aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yson/node
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/yson/node
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/yson/node')
-rw-r--r--library/cpp/yson/node/node.cpp915
-rw-r--r--library/cpp/yson/node/node.h523
-rw-r--r--library/cpp/yson/node/node_builder.cpp96
-rw-r--r--library/cpp/yson/node/node_builder.h46
-rw-r--r--library/cpp/yson/node/node_io.cpp154
-rw-r--r--library/cpp/yson/node/node_io.h40
-rw-r--r--library/cpp/yson/node/node_ut.cpp484
-rw-r--r--library/cpp/yson/node/node_visitor.cpp152
-rw-r--r--library/cpp/yson/node/node_visitor.h37
-rw-r--r--library/cpp/yson/node/pybind/node.cpp105
-rw-r--r--library/cpp/yson/node/pybind/node.h9
-rw-r--r--library/cpp/yson/node/pybind/ya.make16
-rw-r--r--library/cpp/yson/node/serialize.cpp101
-rw-r--r--library/cpp/yson/node/serialize.h45
-rw-r--r--library/cpp/yson/node/ut/ya.make12
-rw-r--r--library/cpp/yson/node/ya.make25
16 files changed, 2760 insertions, 0 deletions
diff --git a/library/cpp/yson/node/node.cpp b/library/cpp/yson/node/node.cpp
new file mode 100644
index 0000000000..b39e070718
--- /dev/null
+++ b/library/cpp/yson/node/node.cpp
@@ -0,0 +1,915 @@
+#include "node.h"
+
+#include "node_io.h"
+
+#include <library/cpp/yson/writer.h>
+
+#include <util/generic/overloaded.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TNode::TNull::operator==(const TNull&) const {
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TNode::TUndefined::operator==(const TUndefined&) const {
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NNodeCmp {
+
+bool IsComparableType(const TNode::EType type) {
+ switch (type) {
+ case TNode::String:
+ case TNode::Int64:
+ case TNode::Uint64:
+ case TNode::Double:
+ case TNode::Bool:
+ case TNode::Null:
+ case TNode::Undefined:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool operator<(const TNode& lhs, const TNode& rhs)
+{
+ if (!lhs.GetAttributes().Empty() || !rhs.GetAttributes().Empty()) {
+ ythrow TNode::TTypeError() << "Unsupported attributes comparison";
+ }
+
+ if (!IsComparableType(lhs.GetType()) || !IsComparableType(rhs.GetType())) {
+ ythrow TNode::TTypeError() << "Unsupported types for comparison: " << lhs.GetType() << " with " << rhs.GetType();
+ }
+
+ if (lhs.GetType() != rhs.GetType()) {
+ return lhs.GetType() < rhs.GetType();
+ }
+
+ switch (lhs.GetType()) {
+ case TNode::String:
+ return lhs.AsString() < rhs.AsString();
+ case TNode::Int64:
+ return lhs.AsInt64() < rhs.AsInt64();
+ case TNode::Uint64:
+ return lhs.AsUint64() < rhs.AsUint64();
+ case TNode::Double:
+ return lhs.AsDouble() < rhs.AsDouble();
+ case TNode::Bool:
+ return lhs.AsBool() < rhs.AsBool();
+ case TNode::Null:
+ case TNode::Undefined:
+ return false;
+ default:
+ Y_FAIL("Unexpected type: %d", lhs.GetType());
+ }
+}
+
+bool operator>(const TNode& lhs, const TNode& rhs)
+{
+ return rhs < lhs;
+}
+
+bool operator<=(const TNode& lhs, const TNode& rhs)
+{
+ return !(lhs > rhs);
+}
+
+bool operator>=(const TNode& lhs, const TNode& rhs)
+{
+ return !(lhs < rhs);
+}
+
+} // namespace NNodeCmp
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNode::TNode()
+ : Value_(TUndefined{})
+{ }
+
+TNode::TNode(const char* s)
+ : Value_(TString(s))
+{ }
+
+TNode::TNode(TStringBuf s)
+ : Value_(TString(s))
+{ }
+
+TNode::TNode(std::string_view s)
+ : Value_(TString(s))
+{ }
+
+TNode::TNode(const std::string& s)
+ : Value_(TString(s))
+{ }
+
+TNode::TNode(TString s)
+ : Value_(std::move(s))
+{ }
+
+TNode::TNode(int i)
+ : Value_(static_cast<i64>(i))
+{ }
+
+
+TNode::TNode(unsigned int ui)
+ : Value_(static_cast<ui64>(ui))
+{ }
+
+TNode::TNode(long i)
+ : Value_(static_cast<i64>(i))
+{ }
+
+TNode::TNode(unsigned long ui)
+ : Value_(static_cast<ui64>(ui))
+{ }
+
+TNode::TNode(long long i)
+ : Value_(static_cast<i64>(i))
+{ }
+
+TNode::TNode(unsigned long long ui)
+ : Value_(static_cast<ui64>(ui))
+{ }
+
+TNode::TNode(double d)
+ : Value_(d)
+{ }
+
+TNode::TNode(bool b)
+ : Value_(b)
+{ }
+
+TNode::TNode(TMapType map)
+ : Value_(std::move(map))
+{ }
+
+TNode::TNode(const TNode& rhs)
+ : TNode()
+{
+ if (rhs.Attributes_) {
+ CreateAttributes();
+ *Attributes_ = *rhs.Attributes_;
+ }
+ Value_ = rhs.Value_;
+}
+
+TNode& TNode::operator=(const TNode& rhs)
+{
+ if (this != &rhs) {
+ TNode tmp = rhs;
+ Move(std::move(tmp));
+ }
+ return *this;
+}
+
+TNode::TNode(TNode&& rhs) noexcept
+ : TNode()
+{
+ Move(std::move(rhs));
+}
+
+TNode& TNode::operator=(TNode&& rhs) noexcept
+{
+ if (this != &rhs) {
+ TNode tmp = std::move(rhs);
+ Move(std::move(tmp));
+ }
+ return *this;
+}
+
+TNode::~TNode() = default;
+
+void TNode::Clear()
+{
+ ClearAttributes();
+ Value_ = TUndefined();
+}
+
+bool TNode::IsString() const
+{
+ return std::holds_alternative<TString>(Value_);
+}
+
+bool TNode::IsInt64() const
+{
+ return std::holds_alternative<i64>(Value_);
+}
+
+bool TNode::IsUint64() const
+{
+ return std::holds_alternative<ui64>(Value_);
+}
+
+bool TNode::IsDouble() const
+{
+ return std::holds_alternative<double>(Value_);
+}
+
+bool TNode::IsBool() const
+{
+ return std::holds_alternative<bool>(Value_);
+}
+
+bool TNode::IsList() const
+{
+ return std::holds_alternative<TListType>(Value_);
+}
+
+bool TNode::IsMap() const
+{
+ return std::holds_alternative<TMapType>(Value_);
+}
+
+bool TNode::IsEntity() const
+{
+ return IsNull();
+}
+
+bool TNode::IsNull() const
+{
+ return std::holds_alternative<TNull>(Value_);
+}
+
+bool TNode::IsUndefined() const
+{
+ return std::holds_alternative<TUndefined>(Value_);
+}
+
+bool TNode::HasValue() const
+{
+ return !IsNull() && !IsUndefined();
+}
+
+bool TNode::Empty() const
+{
+ switch (GetType()) {
+ case String:
+ return std::get<TString>(Value_).empty();
+ case List:
+ return std::get<TListType>(Value_).empty();
+ case Map:
+ return std::get<TMapType>(Value_).empty();
+ default:
+ ythrow TTypeError() << "Empty() called for type " << GetType();
+ }
+}
+
+size_t TNode::Size() const
+{
+ switch (GetType()) {
+ case String:
+ return std::get<TString>(Value_).size();
+ case List:
+ return std::get<TListType>(Value_).size();
+ case Map:
+ return std::get<TMapType>(Value_).size();
+ default:
+ ythrow TTypeError() << "Size() called for type " << GetType();
+ }
+}
+
+TNode::EType TNode::GetType() const
+{
+ return std::visit(TOverloaded{
+ [](const TUndefined&) { return Undefined; },
+ [](const TString&) { return String; },
+ [](i64) { return Int64; },
+ [](ui64) { return Uint64; },
+ [](double) { return Double; },
+ [](bool) { return Bool; },
+ [](const TListType&) { return List; },
+ [](const TMapType&) { return Map; },
+ [](const TNull&) { return Null; }
+ }, Value_);
+}
+
+const TString& TNode::AsString() const
+{
+ CheckType(String);
+ return std::get<TString>(Value_);
+}
+
+i64 TNode::AsInt64() const
+{
+ CheckType(Int64);
+ return std::get<i64>(Value_);
+}
+
+ui64 TNode::AsUint64() const
+{
+ CheckType(Uint64);
+ return std::get<ui64>(Value_);
+}
+
+double TNode::AsDouble() const
+{
+ CheckType(Double);
+ return std::get<double>(Value_);
+}
+
+bool TNode::AsBool() const
+{
+ CheckType(Bool);
+ return std::get<bool>(Value_);
+}
+
+const TNode::TListType& TNode::AsList() const
+{
+ CheckType(List);
+ return std::get<TListType>(Value_);
+}
+
+const TNode::TMapType& TNode::AsMap() const
+{
+ CheckType(Map);
+ return std::get<TMapType>(Value_);
+}
+
+TNode::TListType& TNode::AsList()
+{
+ CheckType(List);
+ return std::get<TListType>(Value_);
+}
+
+TNode::TMapType& TNode::AsMap()
+{
+ CheckType(Map);
+ return std::get<TMapType>(Value_);
+}
+
+const TString& TNode::UncheckedAsString() const noexcept
+{
+ return std::get<TString>(Value_);
+}
+
+i64 TNode::UncheckedAsInt64() const noexcept
+{
+ return std::get<i64>(Value_);
+}
+
+ui64 TNode::UncheckedAsUint64() const noexcept
+{
+ return std::get<ui64>(Value_);
+}
+
+double TNode::UncheckedAsDouble() const noexcept
+{
+ return std::get<double>(Value_);
+}
+
+bool TNode::UncheckedAsBool() const noexcept
+{
+ return std::get<bool>(Value_);
+}
+
+const TNode::TListType& TNode::UncheckedAsList() const noexcept
+{
+ return std::get<TListType>(Value_);
+}
+
+const TNode::TMapType& TNode::UncheckedAsMap() const noexcept
+{
+ return std::get<TMapType>(Value_);
+}
+
+TNode::TListType& TNode::UncheckedAsList() noexcept
+{
+ return std::get<TListType>(Value_);
+}
+
+TNode::TMapType& TNode::UncheckedAsMap() noexcept
+{
+ return std::get<TMapType>(Value_);
+}
+
+TNode TNode::CreateList()
+{
+ TNode node;
+ node.Value_ = TListType{};
+ return node;
+}
+
+TNode TNode::CreateList(TListType list)
+{
+ TNode node;
+ node.Value_ = std::move(list);
+ return node;
+}
+
+TNode TNode::CreateMap()
+{
+ TNode node;
+ node.Value_ = TMapType{};
+ return node;
+}
+
+TNode TNode::CreateMap(TMapType map)
+{
+ TNode node;
+ node.Value_ = std::move(map);
+ return node;
+}
+
+TNode TNode::CreateEntity()
+{
+ TNode node;
+ node.Value_ = TNull{};
+ return node;
+}
+
+const TNode& TNode::operator[](size_t index) const
+{
+ CheckType(List);
+ return std::get<TListType>(Value_)[index];
+}
+
+TNode& TNode::operator[](size_t index)
+{
+ CheckType(List);
+ return std::get<TListType>(Value_)[index];
+}
+
+const TNode& TNode::At(size_t index) const {
+ CheckType(List);
+ const auto& list = std::get<TListType>(Value_);
+ if (index >= list.size()) {
+ ythrow TLookupError() << "List out-of-range: requested index=" << index << ", but size=" << list.size();
+ }
+ return list[index];
+}
+
+TNode& TNode::At(size_t index) {
+ CheckType(List);
+ auto& list = std::get<TListType>(Value_);
+ if (index >= list.size()) {
+ ythrow TLookupError() << "List out-of-range: requested index=" << index << ", but size=" << list.size();
+ }
+ return list[index];
+}
+
+TNode& TNode::Add() &
+{
+ AssureList();
+ return std::get<TListType>(Value_).emplace_back();
+}
+
+TNode TNode::Add() &&
+{
+ return std::move(Add());
+}
+
+TNode& TNode::Add(const TNode& node) &
+{
+ AssureList();
+ std::get<TListType>(Value_).emplace_back(node);
+ return *this;
+}
+
+TNode TNode::Add(const TNode& node) &&
+{
+ return std::move(Add(node));
+}
+
+TNode& TNode::Add(TNode&& node) &
+{
+ AssureList();
+ std::get<TListType>(Value_).emplace_back(std::move(node));
+ return *this;
+}
+
+TNode TNode::Add(TNode&& node) &&
+{
+ return std::move(Add(std::move(node)));
+}
+
+bool TNode::HasKey(const TStringBuf key) const
+{
+ CheckType(Map);
+ return std::get<TMapType>(Value_).contains(key);
+}
+
+TNode& TNode::operator()(const TString& key, const TNode& value) &
+{
+ AssureMap();
+ std::get<TMapType>(Value_)[key] = value;
+ return *this;
+}
+
+TNode TNode::operator()(const TString& key, const TNode& value) &&
+{
+ return std::move(operator()(key, value));
+}
+
+TNode& TNode::operator()(const TString& key, TNode&& value) &
+{
+ AssureMap();
+ std::get<TMapType>(Value_)[key] = std::move(value);
+ return *this;
+}
+
+TNode TNode::operator()(const TString& key, TNode&& value) &&
+{
+ return std::move(operator()(key, std::move(value)));
+}
+
+const TNode& TNode::operator[](const TStringBuf key) const
+{
+ CheckType(Map);
+ static TNode notFound;
+ const auto& map = std::get<TMapType>(Value_);
+ TMapType::const_iterator i = map.find(key);
+ if (i == map.end()) {
+ return notFound;
+ } else {
+ return i->second;
+ }
+}
+
+TNode& TNode::operator[](const TStringBuf key)
+{
+ AssureMap();
+ return std::get<TMapType>(Value_)[key];
+}
+
+const TNode& TNode::At(const TStringBuf key) const {
+ CheckType(Map);
+ const auto& map = std::get<TMapType>(Value_);
+ TMapType::const_iterator i = map.find(key);
+ if (i == map.end()) {
+ ythrow TLookupError() << "Cannot find key " << key;
+ } else {
+ return i->second;
+ }
+}
+
+TNode& TNode::At(const TStringBuf key) {
+ CheckType(Map);
+ auto& map = std::get<TMapType>(Value_);
+ TMapType::iterator i = map.find(key);
+ if (i == map.end()) {
+ ythrow TLookupError() << "Cannot find key " << key;
+ } else {
+ return i->second;
+ }
+}
+
+const TString& TNode::ChildAsString(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsString();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+i64 TNode::ChildAsInt64(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsInt64();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+ui64 TNode::ChildAsUint64(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsUint64();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+double TNode::ChildAsDouble(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsDouble();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+bool TNode::ChildAsBool(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsBool();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+const TNode::TListType& TNode::ChildAsList(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsList();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+const TNode::TMapType& TNode::ChildAsMap(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.AsMap();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+TNode::TListType& TNode::ChildAsList(const TStringBuf key) {
+ auto& node = At(key);
+ try {
+ return node.AsList();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+TNode::TMapType& TNode::ChildAsMap(const TStringBuf key) {
+ auto& node = At(key);
+ try {
+ return node.AsMap();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+const TString& TNode::ChildAsString(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsString();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+i64 TNode::ChildAsInt64(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsInt64();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+ui64 TNode::ChildAsUint64(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsUint64();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+double TNode::ChildAsDouble(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsDouble();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+bool TNode::ChildAsBool(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsBool();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+const TNode::TListType& TNode::ChildAsList(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsList();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+const TNode::TMapType& TNode::ChildAsMap(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.AsMap();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+TNode::TListType& TNode::ChildAsList(size_t index) {
+ auto& node = At(index);
+ try {
+ return node.AsList();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+TNode::TMapType& TNode::ChildAsMap(size_t index) {
+ auto& node = At(index);
+ try {
+ return node.AsMap();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+bool TNode::HasAttributes() const
+{
+ return Attributes_ && !Attributes_->Empty();
+}
+
+void TNode::ClearAttributes()
+{
+ if (Attributes_) {
+ Attributes_.Destroy();
+ }
+}
+
+const TNode& TNode::GetAttributes() const
+{
+ static TNode notFound = TNode::CreateMap();
+ if (!Attributes_) {
+ return notFound;
+ }
+ return *Attributes_;
+}
+
+TNode& TNode::Attributes()
+{
+ if (!Attributes_) {
+ CreateAttributes();
+ }
+ return *Attributes_;
+}
+
+void TNode::MoveWithoutAttributes(TNode&& rhs)
+{
+ Value_ = std::move(rhs.Value_);
+ rhs.Clear();
+}
+
+void TNode::Move(TNode&& rhs)
+{
+ Value_ = std::move(rhs.Value_);
+ Attributes_ = std::move(rhs.Attributes_);
+}
+
+void TNode::CheckType(EType type) const
+{
+ Y_ENSURE_EX(GetType() == type,
+ TTypeError() << "TNode type " << type << " expected, actual type " << GetType();
+ );
+}
+
+void TNode::AssureMap()
+{
+ if (std::holds_alternative<TUndefined>(Value_)) {
+ Value_ = TMapType();
+ } else {
+ CheckType(Map);
+ }
+}
+
+void TNode::AssureList()
+{
+ if (std::holds_alternative<TUndefined>(Value_)) {
+ Value_ = TListType();
+ } else {
+ CheckType(List);
+ }
+}
+
+void TNode::CreateAttributes()
+{
+ Attributes_ = MakeHolder<TNode>();
+ Attributes_->Value_ = TMapType();
+}
+
+void TNode::Save(IOutputStream* out) const
+{
+ NodeToYsonStream(*this, out, NYson::EYsonFormat::Binary);
+}
+
+void TNode::Load(IInputStream* in)
+{
+ Clear();
+ *this = NodeFromYsonStream(in, ::NYson::EYsonType::Node);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator==(const TNode& lhs, const TNode& rhs)
+{
+ if (std::holds_alternative<TNode::TUndefined>(lhs.Value_) ||
+ std::holds_alternative<TNode::TUndefined>(rhs.Value_))
+ {
+ // TODO: should try to remove this behaviour if nobody uses it.
+ return false;
+ }
+
+ if (lhs.GetType() != rhs.GetType()) {
+ return false;
+ }
+
+ if (lhs.Attributes_) {
+ if (rhs.Attributes_) {
+ if (*lhs.Attributes_ != *rhs.Attributes_) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } else {
+ if (rhs.Attributes_) {
+ return false;
+ }
+ }
+
+ return rhs.Value_ == lhs.Value_;
+}
+
+bool operator!=(const TNode& lhs, const TNode& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool GetBool(const TNode& node)
+{
+ if (node.IsBool()) {
+ return node.AsBool();
+ } else if (node.IsString()) {
+ return node.AsString() == "true";
+ } else {
+ ythrow TNode::TTypeError()
+ << "GetBool(): not a boolean or string type";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node.h b/library/cpp/yson/node/node.h
new file mode 100644
index 0000000000..5f90f95df0
--- /dev/null
+++ b/library/cpp/yson/node/node.h
@@ -0,0 +1,523 @@
+#pragma once
+
+#include <util/generic/bt_exception.h>
+#include <util/generic/cast.h>
+#include <util/generic/hash.h>
+#include <util/generic/variant.h>
+#include <util/generic/vector.h>
+#include <util/generic/yexception.h>
+#include <util/generic/ylimits.h>
+#include <util/string/cast.h>
+
+#include <cmath>
+#include <variant>
+
+class IInputStream;
+class IOutputStream;
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TNode
+{
+public:
+ class TLookupError
+ : public TWithBackTrace<yexception>
+ { };
+
+ class TTypeError
+ : public TWithBackTrace<yexception>
+ { };
+
+ enum EType {
+ Undefined = 0 /*"undefined"*/,
+
+ // NOTE: string representation of all node types
+ // are compatible with server node type (except `Undefined' which is missing on server).
+ String = 1 /*"string_node"*/,
+ Int64 = 2 /*"int64_node"*/,
+ Uint64 = 3 /*"uint64_node"*/,
+ Double = 4 /*"double_node"*/,
+ Bool = 5 /*"boolean_node"*/,
+ List = 6 /*"list_node"*/,
+ Map = 7 /*"map_node"*/,
+ Null = 8 /*"null"*/,
+ };
+
+ using TListType = TVector<TNode>;
+ using TMapType = THashMap<TString, TNode>;
+
+private:
+ struct TNull {
+ bool operator==(const TNull&) const;
+ };
+
+ struct TUndefined {
+ bool operator==(const TUndefined&) const;
+ };
+
+ using TValue = std::variant<
+ bool,
+ i64,
+ ui64,
+ double,
+ TString,
+ TListType,
+ TMapType,
+ TNull,
+ TUndefined
+ >;
+
+public:
+
+ TNode();
+ TNode(const char* s);
+ TNode(TStringBuf s);
+ explicit TNode(std::string_view s);
+ explicit TNode(const std::string& s);
+ TNode(TString s);
+ TNode(int i);
+
+ //this case made speccially for prevent mess cast of EType into TNode through TNode(int) constructor
+ //usual case of error SomeNode == TNode::Undefined <-- SomeNode indeed will be compared with TNode(0) without this method
+ //correct way is SomeNode.GetType() == TNode::Undefined
+ template<class T = EType>
+ Y_FORCE_INLINE TNode(EType)
+ {
+ static_assert(!std::is_same<T, EType>::value, "looks like a mistake, may be you forget .GetType()");
+ }
+
+ //this case made speccially for prevent mess cast of T* into TNode through implicit bool ctr
+ template<class T = int>
+ Y_FORCE_INLINE TNode(const T*) : TNode() {
+ static_assert(!std::is_same<T,T>::value, "looks like a mistake, and pointer have converted to bool");
+ }
+
+ TNode(unsigned int ui);
+ TNode(long i);
+ TNode(unsigned long ui);
+ TNode(long long i);
+ TNode(unsigned long long ui);
+ TNode(double d);
+ TNode(bool b);
+ TNode(TMapType map);
+
+ TNode(const TNode& rhs);
+ TNode& operator=(const TNode& rhs);
+
+ TNode(TNode&& rhs) noexcept;
+ TNode& operator=(TNode&& rhs) noexcept;
+
+ ~TNode();
+
+ void Clear();
+
+ bool IsString() const;
+ bool IsInt64() const;
+ bool IsUint64() const;
+ bool IsDouble() const;
+ bool IsBool() const;
+ bool IsList() const;
+ bool IsMap() const;
+
+ // `IsEntity' is deprecated use `IsNull' instead.
+ bool IsEntity() const;
+ bool IsNull() const;
+ bool IsUndefined() const;
+ // Returns true if TNode is neither Null, nor Undefined
+ bool HasValue() const;
+
+ template<typename T>
+ bool IsOfType() const noexcept;
+
+ // Int64, Uint64, Double, or Bool
+ bool IsArithmetic() const;
+
+ bool Empty() const;
+ size_t Size() const;
+
+ EType GetType() const;
+
+ const TString& AsString() const;
+ i64 AsInt64() const;
+ ui64 AsUint64() const;
+ double AsDouble() const;
+ bool AsBool() const;
+ const TListType& AsList() const;
+ const TMapType& AsMap() const;
+ TListType& AsList();
+ TMapType& AsMap();
+
+ const TString& UncheckedAsString() const noexcept;
+ i64 UncheckedAsInt64() const noexcept;
+ ui64 UncheckedAsUint64() const noexcept;
+ double UncheckedAsDouble() const noexcept;
+ bool UncheckedAsBool() const noexcept;
+ const TListType& UncheckedAsList() const noexcept;
+ const TMapType& UncheckedAsMap() const noexcept;
+ TListType& UncheckedAsList() noexcept;
+ TMapType& UncheckedAsMap() noexcept;
+
+ // integer types cast
+ // makes overflow checks
+ template<typename T>
+ T IntCast() const;
+
+ // integers <-> double <-> string
+ // makes overflow checks
+ template<typename T>
+ T ConvertTo() const;
+
+ template<typename T>
+ T& As();
+
+ template<typename T>
+ const T& As() const;
+
+ static TNode CreateList();
+ static TNode CreateList(TListType list);
+ static TNode CreateMap();
+ static TNode CreateMap(TMapType map);
+ static TNode CreateEntity();
+
+ const TNode& operator[](size_t index) const;
+ TNode& operator[](size_t index);
+ const TNode& At(size_t index) const;
+ TNode& At(size_t index);
+
+ TNode& Add() &;
+ TNode Add() &&;
+ TNode& Add(const TNode& node) &;
+ TNode Add(const TNode& node) &&;
+ TNode& Add(TNode&& node) &;
+ TNode Add(TNode&& node) &&;
+
+ bool HasKey(const TStringBuf key) const;
+
+ TNode& operator()(const TString& key, const TNode& value) &;
+ TNode operator()(const TString& key, const TNode& value) &&;
+ TNode& operator()(const TString& key, TNode&& value) &;
+ TNode operator()(const TString& key, TNode&& value) &&;
+
+ const TNode& operator[](const TStringBuf key) const;
+ TNode& operator[](const TStringBuf key);
+ const TNode& At(const TStringBuf key) const;
+ TNode& At(const TStringBuf key);
+
+ // map getters
+ // works the same way like simple getters
+ const TString& ChildAsString(const TStringBuf key) const;
+ i64 ChildAsInt64(const TStringBuf key) const;
+ ui64 ChildAsUint64(const TStringBuf key) const;
+ double ChildAsDouble(const TStringBuf key) const;
+ bool ChildAsBool(const TStringBuf key) const;
+ const TListType& ChildAsList(const TStringBuf key) const;
+ const TMapType& ChildAsMap(const TStringBuf key) const;
+ TListType& ChildAsList(const TStringBuf key);
+ TMapType& ChildAsMap(const TStringBuf key);
+
+ template<typename T>
+ T ChildIntCast(const TStringBuf key) const;
+
+ template<typename T>
+ T ChildConvertTo(const TStringBuf key) const;
+
+ template<typename T>
+ const T& ChildAs(const TStringBuf key) const;
+
+ template<typename T>
+ T& ChildAs(const TStringBuf key);
+
+ // list getters
+ // works the same way like simple getters
+ const TString& ChildAsString(size_t index) const;
+ i64 ChildAsInt64(size_t index) const;
+ ui64 ChildAsUint64(size_t index) const;
+ double ChildAsDouble(size_t index) const;
+ bool ChildAsBool(size_t index) const;
+ const TListType& ChildAsList(size_t index) const;
+ const TMapType& ChildAsMap(size_t index) const;
+ TListType& ChildAsList(size_t index);
+ TMapType& ChildAsMap(size_t index);
+
+ template<typename T>
+ T ChildIntCast(size_t index) const;
+
+ template<typename T>
+ T ChildConvertTo(size_t index) const;
+
+ template<typename T>
+ const T& ChildAs(size_t index) const;
+
+ template<typename T>
+ T& ChildAs(size_t index);
+
+
+ // attributes
+ bool HasAttributes() const;
+ void ClearAttributes();
+ const TNode& GetAttributes() const;
+ TNode& Attributes();
+
+ void MoveWithoutAttributes(TNode&& rhs);
+
+ // Serialize TNode using binary yson format.
+ // Methods for ysaveload.
+ void Save(IOutputStream* output) const;
+ void Load(IInputStream* input);
+
+private:
+ void Move(TNode&& rhs);
+
+ void CheckType(EType type) const;
+
+ void AssureMap();
+ void AssureList();
+
+ void CreateAttributes();
+
+private:
+ TValue Value_;
+ THolder<TNode> Attributes_;
+
+ friend bool operator==(const TNode& lhs, const TNode& rhs);
+ friend bool operator!=(const TNode& lhs, const TNode& rhs);
+};
+
+bool operator==(const TNode& lhs, const TNode& rhs);
+bool operator!=(const TNode& lhs, const TNode& rhs);
+
+bool GetBool(const TNode& node);
+
+inline bool TNode::IsArithmetic() const {
+ return IsInt64() || IsUint64() || IsDouble() || IsBool();
+}
+
+template<typename T>
+inline T TNode::IntCast() const {
+ if constexpr (std::is_integral<T>::value) {
+ try {
+ switch (GetType()) {
+ case TNode::Uint64:
+ return SafeIntegerCast<T>(AsUint64());
+ case TNode::Int64:
+ return SafeIntegerCast<T>(AsInt64());
+ default:
+ ythrow TTypeError() << "IntCast() called for type " << GetType();
+ }
+ } catch(TBadCastException& exc) {
+ ythrow TTypeError() << "TBadCastException during IntCast(): " << exc.what();
+ }
+ } else {
+ static_assert(sizeof(T) != sizeof(T), "implemented only for std::is_integral types");
+ }
+}
+
+template<typename T>
+inline T TNode::ConvertTo() const {
+ if constexpr (std::is_integral<T>::value) {
+ switch (GetType()) {
+ case NYT::TNode::String:
+ return ::FromString(AsString());
+ case NYT::TNode::Int64:
+ case NYT::TNode::Uint64:
+ return IntCast<T>();
+ case NYT::TNode::Double:
+ if (AsDouble() < Min<T>() || AsDouble() > MaxFloor<T>() || !std::isfinite(AsDouble())) {
+ ythrow TTypeError() << AsDouble() << " can't be converted to " << TypeName<T>();
+ }
+ return AsDouble();
+ case NYT::TNode::Bool:
+ return AsBool();
+ case NYT::TNode::List:
+ case NYT::TNode::Map:
+ case NYT::TNode::Null:
+ case NYT::TNode::Undefined:
+ ythrow TTypeError() << "ConvertTo<" << TypeName<T>() << ">() called for type " << GetType();
+ };
+ } else {
+ static_assert(sizeof(T) != sizeof(T), "should have template specialization");
+ }
+}
+
+template<>
+inline TString TNode::ConvertTo<TString>() const {
+ switch (GetType()) {
+ case NYT::TNode::String:
+ return AsString();
+ case NYT::TNode::Int64:
+ return ::ToString(AsInt64());
+ case NYT::TNode::Uint64:
+ return ::ToString(AsUint64());
+ case NYT::TNode::Double:
+ return ::ToString(AsDouble());
+ case NYT::TNode::Bool:
+ return ::ToString(AsBool());
+ case NYT::TNode::List:
+ case NYT::TNode::Map:
+ case NYT::TNode::Null:
+ case NYT::TNode::Undefined:
+ ythrow TTypeError() << "ConvertTo<TString>() called for type " << GetType();
+ }
+ Y_UNREACHABLE();
+}
+
+template<>
+inline double TNode::ConvertTo<double>() const {
+ switch (GetType()) {
+ case NYT::TNode::String:
+ return ::FromString(AsString());
+ case NYT::TNode::Int64:
+ return AsInt64();
+ case NYT::TNode::Uint64:
+ return AsUint64();
+ case NYT::TNode::Double:
+ return AsDouble();
+ case NYT::TNode::Bool:
+ return AsBool();
+ case NYT::TNode::List:
+ case NYT::TNode::Map:
+ case NYT::TNode::Null:
+ case NYT::TNode::Undefined:
+ ythrow TTypeError() << "ConvertTo<double>() called for type " << GetType();
+ }
+}
+
+template<>
+inline bool TNode::ConvertTo<bool>() const {
+ switch (GetType()) {
+ case NYT::TNode::String:
+ return ::FromString(AsString());
+ case NYT::TNode::Int64:
+ return AsInt64();
+ case NYT::TNode::Uint64:
+ return AsUint64();
+ case NYT::TNode::Double:
+ return AsDouble();
+ case NYT::TNode::Bool:
+ return AsBool();
+ case NYT::TNode::List:
+ case NYT::TNode::Map:
+ case NYT::TNode::Null:
+ case NYT::TNode::Undefined:
+ ythrow TTypeError() << "ConvertTo<bool>() called for type " << GetType();
+ }
+}
+
+template<typename T>
+inline T TNode::ChildIntCast(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.IntCast<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+template<typename T>
+inline T TNode::ChildIntCast(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.IntCast<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+template<typename T>
+inline T TNode::ChildConvertTo(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.ConvertTo<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+template<typename T>
+inline T TNode::ChildConvertTo(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.ConvertTo<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+template<typename T>
+inline const T& TNode::ChildAs(const TStringBuf key) const {
+ const auto& node = At(key);
+ try {
+ return node.As<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting key=" << key;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting key=" << key;
+ }
+}
+
+template<typename T>
+inline const T& TNode::ChildAs(size_t index) const {
+ const auto& node = At(index);
+ try {
+ return node.As<T>();
+ } catch (TTypeError& e) {
+ e << ", during getting index=" << index;
+ throw e;
+ } catch (...) {
+ ythrow TTypeError() << CurrentExceptionMessage() << ", during getting index=" << index;
+ }
+}
+
+template<typename T>
+inline T& TNode::ChildAs(const TStringBuf key) {
+ return const_cast<T&>(static_cast<const TNode*>(this)->ChildAs<T>(key));
+}
+
+template<typename T>
+inline T& TNode::ChildAs(size_t index) {
+ return const_cast<T&>(static_cast<const TNode*>(this)->ChildAs<T>(index));
+}
+
+template<typename T>
+inline bool TNode::IsOfType() const noexcept {
+ return std::holds_alternative<T>(Value_);
+}
+
+template<typename T>
+inline T& TNode::As() {
+ return std::get<T>(Value_);
+}
+
+template<typename T>
+inline const T& TNode::As() const {
+ return std::get<T>(Value_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NNodeCmp {
+ bool operator<(const TNode& lhs, const TNode& rhs);
+ bool operator<=(const TNode& lhs, const TNode& rhs);
+ bool operator>(const TNode& lhs, const TNode& rhs);
+ bool operator>=(const TNode& lhs, const TNode& rhs);
+ bool IsComparableType(const TNode::EType type);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_builder.cpp b/library/cpp/yson/node/node_builder.cpp
new file mode 100644
index 0000000000..b4431bc77a
--- /dev/null
+++ b/library/cpp/yson/node/node_builder.cpp
@@ -0,0 +1,96 @@
+#include "node_builder.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNodeBuilder::TNodeBuilder(TNode* node)
+{
+ Stack_.push(node);
+}
+
+void TNodeBuilder::OnStringScalar(TStringBuf value)
+{
+ AddNode(value, true);
+}
+
+void TNodeBuilder::OnInt64Scalar(i64 value)
+{
+ AddNode(value, true);
+}
+
+void TNodeBuilder::OnUint64Scalar(ui64 value)
+{
+ AddNode(value, true);
+}
+
+void TNodeBuilder::OnDoubleScalar(double value)
+{
+ AddNode(value, true);
+}
+
+void TNodeBuilder::OnBooleanScalar(bool value)
+{
+ AddNode(value, true);
+}
+
+void TNodeBuilder::OnEntity()
+{
+ AddNode(TNode::CreateEntity(), true);
+}
+
+void TNodeBuilder::OnBeginList()
+{
+ AddNode(TNode::CreateList(), false);
+}
+
+void TNodeBuilder::OnListItem()
+{
+ Stack_.push(&Stack_.top()->Add());
+}
+
+void TNodeBuilder::OnEndList()
+{
+ Stack_.pop();
+}
+
+void TNodeBuilder::OnBeginMap()
+{
+ AddNode(TNode::CreateMap(), false);
+}
+
+void TNodeBuilder::OnKeyedItem(TStringBuf key)
+{
+ Stack_.push(&(*Stack_.top())[TString(key)]);
+}
+
+void TNodeBuilder::OnEndMap()
+{
+ Stack_.pop();
+}
+
+void TNodeBuilder::OnBeginAttributes()
+{
+ Stack_.push(&Stack_.top()->Attributes());
+}
+
+void TNodeBuilder::OnEndAttributes()
+{
+ Stack_.pop();
+}
+
+void TNodeBuilder::OnNode(TNode node)
+{
+ AddNode(std::move(node), true);
+}
+
+void TNodeBuilder::AddNode(TNode value, bool pop)
+{
+ Stack_.top()->MoveWithoutAttributes(std::move(value));
+ if (pop)
+ Stack_.pop();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_builder.h b/library/cpp/yson/node/node_builder.h
new file mode 100644
index 0000000000..69800016e0
--- /dev/null
+++ b/library/cpp/yson/node/node_builder.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "node.h"
+
+#include <library/cpp/json/json_reader.h>
+
+#include <library/cpp/yson/consumer.h>
+
+#include <util/generic/stack.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TNodeBuilder
+ : public ::NYson::TYsonConsumerBase
+{
+public:
+ TNodeBuilder(TNode* node);
+
+ void OnStringScalar(TStringBuf) override;
+ void OnInt64Scalar(i64) override;
+ void OnUint64Scalar(ui64) override;
+ void OnDoubleScalar(double) override;
+ void OnBooleanScalar(bool) override;
+ void OnEntity() override;
+ void OnBeginList() override;
+ void OnListItem() override;
+ void OnEndList() override;
+ void OnBeginMap() override;
+ void OnKeyedItem(TStringBuf) override;
+ void OnEndMap() override;
+ void OnBeginAttributes() override;
+ void OnEndAttributes() override;
+ void OnNode(TNode node);
+
+private:
+ TStack<TNode*> Stack_;
+
+private:
+ inline void AddNode(TNode node, bool pop);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_io.cpp b/library/cpp/yson/node/node_io.cpp
new file mode 100644
index 0000000000..294a7f7217
--- /dev/null
+++ b/library/cpp/yson/node/node_io.cpp
@@ -0,0 +1,154 @@
+#include "node_io.h"
+
+#include "node_builder.h"
+#include "node_visitor.h"
+
+#include <library/cpp/yson/json/json_writer.h>
+#include <library/cpp/yson/parser.h>
+#include <library/cpp/yson/writer.h>
+#include <library/cpp/yson/json/yson2json_adapter.h>
+
+#include <library/cpp/json/json_reader.h>
+#include <library/cpp/json/json_value.h>
+
+#include <util/stream/input.h>
+#include <util/stream/output.h>
+#include <util/stream/str.h>
+#include <util/stream/mem.h>
+
+namespace NYT {
+
+static void WalkJsonTree(const NJson::TJsonValue& jsonValue, NJson::TJsonCallbacks* callbacks)
+{
+ using namespace NJson;
+ switch (jsonValue.GetType()) {
+ case JSON_NULL:
+ callbacks->OnNull();
+ return;
+ case JSON_BOOLEAN:
+ callbacks->OnBoolean(jsonValue.GetBoolean());
+ return;
+ case JSON_INTEGER:
+ callbacks->OnInteger(jsonValue.GetInteger());
+ return;
+ case JSON_UINTEGER:
+ callbacks->OnUInteger(jsonValue.GetUInteger());
+ return;
+ case JSON_DOUBLE:
+ callbacks->OnDouble(jsonValue.GetDouble());
+ return;
+ case JSON_STRING:
+ callbacks->OnString(jsonValue.GetString());
+ return;
+ case JSON_MAP:
+ {
+ callbacks->OnOpenMap();
+ for (const auto& item : jsonValue.GetMap()) {
+ callbacks->OnMapKey(item.first);
+ WalkJsonTree(item.second, callbacks);
+ }
+ callbacks->OnCloseMap();
+ }
+ return;
+ case JSON_ARRAY:
+ {
+ callbacks->OnOpenArray();
+ for (const auto& item : jsonValue.GetArray()) {
+ WalkJsonTree(item, callbacks);
+ }
+ callbacks->OnCloseArray();
+ }
+ return;
+ case JSON_UNDEFINED:
+ ythrow yexception() << "cannot consume undefined json value";
+ return;
+ }
+ Y_UNREACHABLE();
+}
+
+static TNode CreateEmptyNodeByType(::NYson::EYsonType type)
+{
+ TNode result;
+ switch (type) {
+ case ::NYson::EYsonType::ListFragment:
+ result = TNode::CreateList();
+ break;
+ case ::NYson::EYsonType::MapFragment:
+ result = TNode::CreateMap();
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+TNode NodeFromYsonString(const TStringBuf input, ::NYson::EYsonType type)
+{
+ TMemoryInput stream(input);
+ return NodeFromYsonStream(&stream, type);
+}
+
+TString NodeToYsonString(const TNode& node, NYson::EYsonFormat format)
+{
+ TStringStream stream;
+ NodeToYsonStream(node, &stream, format);
+ return stream.Str();
+}
+
+TString NodeToCanonicalYsonString(const TNode& node, NYson::EYsonFormat format)
+{
+ TStringStream stream;
+ NodeToCanonicalYsonStream(node, &stream, format);
+ return stream.Str();
+}
+
+TNode NodeFromYsonStream(IInputStream* input, ::NYson::EYsonType type)
+{
+ TNode result = CreateEmptyNodeByType(type);
+
+ TNodeBuilder builder(&result);
+ ::NYson::TYsonParser parser(&builder, input, type);
+ parser.Parse();
+ return result;
+}
+
+void NodeToYsonStream(const TNode& node, IOutputStream* output, NYson::EYsonFormat format)
+{
+ ::NYson::TYsonWriter writer(output, format);
+ TNodeVisitor visitor(&writer);
+ visitor.Visit(node);
+}
+
+void NodeToCanonicalYsonStream(const TNode& node, IOutputStream* output, NYson::EYsonFormat format)
+{
+ ::NYson::TYsonWriter writer(output, format);
+ TNodeVisitor visitor(&writer, /*sortMapKeys*/ true);
+ visitor.Visit(node);
+}
+
+TNode NodeFromJsonString(const TStringBuf input)
+{
+ TMemoryInput stream(input);
+
+ TNode result;
+
+ TNodeBuilder builder(&result);
+ TYson2JsonCallbacksAdapter callbacks(&builder, /*throwException*/ true);
+ NJson::TJsonReaderConfig config;
+ config.DontValidateUtf8 = true;
+ NJson::ReadJson(&stream, &config, &callbacks);
+ return result;
+}
+
+TNode NodeFromJsonValue(const NJson::TJsonValue& input)
+{
+ TNode result;
+ TNodeBuilder builder(&result);
+ TYson2JsonCallbacksAdapter callbacks(&builder, /*throwException*/ true);
+ WalkJsonTree(input, &callbacks);
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_io.h b/library/cpp/yson/node/node_io.h
new file mode 100644
index 0000000000..2ad23b658f
--- /dev/null
+++ b/library/cpp/yson/node/node_io.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "node.h"
+#include <library/cpp/yson/public.h>
+
+namespace NJson {
+ class TJsonValue;
+} // namespace NJson
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Parse TNode from string in YSON format
+TNode NodeFromYsonString(const TStringBuf input, ::NYson::EYsonType type = ::NYson::EYsonType::Node);
+
+// Serialize TNode to string in one of YSON formats with random order of maps' keys (don't use in tests)
+TString NodeToYsonString(const TNode& node, ::NYson::EYsonFormat format = ::NYson::EYsonFormat::Text);
+
+// Same as the latter, but maps' keys are sorted lexicographically (to be used in tests)
+TString NodeToCanonicalYsonString(const TNode& node, ::NYson::EYsonFormat format = ::NYson::EYsonFormat::Text);
+
+// Parse TNode from stream in YSON format
+TNode NodeFromYsonStream(IInputStream* input, ::NYson::EYsonType type = ::NYson::EYsonType::Node);
+
+// Serialize TNode to stream in one of YSON formats with random order of maps' keys (don't use in tests)
+void NodeToYsonStream(const TNode& node, IOutputStream* output, ::NYson::EYsonFormat format = ::NYson::EYsonFormat::Text);
+
+// Same as the latter, but maps' keys are sorted lexicographically (to be used in tests)
+void NodeToCanonicalYsonStream(const TNode& node, IOutputStream* output, ::NYson::EYsonFormat format = ::NYson::EYsonFormat::Text);
+
+// Parse TNode from string in JSON format
+TNode NodeFromJsonString(const TStringBuf input);
+
+// Convert TJsonValue to TNode
+TNode NodeFromJsonValue(const NJson::TJsonValue& input);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_ut.cpp b/library/cpp/yson/node/node_ut.cpp
new file mode 100644
index 0000000000..448e99f575
--- /dev/null
+++ b/library/cpp/yson/node/node_ut.cpp
@@ -0,0 +1,484 @@
+#include "node.h"
+#include "node_io.h"
+
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/ysaveload.h>
+
+using namespace NYT;
+
+template<>
+void Out<NYT::TNode>(IOutputStream& s, const NYT::TNode& node)
+{
+ s << "TNode:" << NodeToYsonString(node);
+}
+
+Y_UNIT_TEST_SUITE(YtNodeTest) {
+ Y_UNIT_TEST(TestConstsructors) {
+ TNode nodeEmpty;
+ UNIT_ASSERT_EQUAL(nodeEmpty.GetType(), TNode::Undefined);
+
+ TNode nodeString("foobar");
+ UNIT_ASSERT_EQUAL(nodeString.GetType(), TNode::String);
+ UNIT_ASSERT(nodeString.IsString());
+ UNIT_ASSERT_VALUES_EQUAL(nodeString.AsString(), "foobar");
+
+ TNode nodeInt(int(54));
+ UNIT_ASSERT_EQUAL(nodeInt.GetType(), TNode::Int64);
+ UNIT_ASSERT(nodeInt.IsInt64());
+ UNIT_ASSERT(!nodeInt.IsUint64());
+ UNIT_ASSERT_VALUES_EQUAL(nodeInt.AsInt64(), 54ull);
+
+ TNode nodeUint(ui64(42));
+ UNIT_ASSERT_EQUAL(nodeUint.GetType(), TNode::Uint64);
+ UNIT_ASSERT(nodeUint.IsUint64());
+ UNIT_ASSERT(!nodeUint.IsInt64());
+ UNIT_ASSERT_VALUES_EQUAL(nodeUint.AsUint64(), 42ull);
+
+ TNode nodeDouble(double(2.3));
+ UNIT_ASSERT_EQUAL(nodeDouble.GetType(), TNode::Double);
+ UNIT_ASSERT(nodeDouble.IsDouble());
+ UNIT_ASSERT_VALUES_EQUAL(nodeDouble.AsDouble(), double(2.3));
+
+ TNode nodeBool(true);
+ UNIT_ASSERT_EQUAL(nodeBool.GetType(), TNode::Bool);
+ UNIT_ASSERT(nodeBool.IsBool());
+ UNIT_ASSERT_VALUES_EQUAL(nodeBool.AsBool(), true);
+
+ TNode nodeEntity = TNode::CreateEntity();
+ UNIT_ASSERT_EQUAL(nodeEntity.GetType(), TNode::Null);
+ UNIT_ASSERT(nodeEntity.IsEntity());
+ }
+
+ Y_UNIT_TEST(TestPredicates) {
+ const TNode undefinedNode;
+ UNIT_ASSERT(undefinedNode.IsUndefined());
+ UNIT_ASSERT(!undefinedNode.IsNull());
+ UNIT_ASSERT(!undefinedNode.HasValue());
+
+ const TNode nullNode = TNode::CreateEntity();
+ UNIT_ASSERT(!nullNode.IsUndefined());
+ UNIT_ASSERT(nullNode.IsNull());
+ UNIT_ASSERT(!nullNode.HasValue());
+
+ const TNode intNode(int(64));
+ UNIT_ASSERT(!intNode.IsUndefined());
+ UNIT_ASSERT(!intNode.IsNull());
+ UNIT_ASSERT(intNode.HasValue());
+
+ const TNode stringNode("blah");
+ UNIT_ASSERT(!stringNode.IsUndefined());
+ UNIT_ASSERT(!stringNode.IsNull());
+ UNIT_ASSERT(stringNode.HasValue());
+ }
+
+ Y_UNIT_TEST(TestComplexConstructors) {
+ const TNode listNode = TNode::CreateList({"one", 2, "tree"});
+ const auto expectedListValue = std::vector<TNode>({"one", 2, "tree"});
+ UNIT_ASSERT_VALUES_EQUAL(listNode.AsList(), expectedListValue);
+
+ const TNode mapNode = TNode::CreateMap({{"one", 1}, {"two", 2u}});
+ const auto expectedMapValue = THashMap<TString, TNode>({{"one", 1}, {"two", 2u}});
+ UNIT_ASSERT_VALUES_EQUAL(mapNode.AsMap(), expectedMapValue);
+ }
+
+ Y_UNIT_TEST(TestNodeMap) {
+ TNode nodeMap = TNode()("foo", "bar")("bar", "baz");
+ UNIT_ASSERT(nodeMap.IsMap());
+ UNIT_ASSERT_EQUAL(nodeMap.GetType(), TNode::Map);
+ UNIT_ASSERT_VALUES_EQUAL(nodeMap.Size(), 2);
+
+ UNIT_ASSERT(nodeMap.HasKey("foo"));
+ UNIT_ASSERT(!nodeMap.HasKey("42"));
+ UNIT_ASSERT_EQUAL(nodeMap["foo"], TNode("bar"));
+ UNIT_ASSERT_EQUAL(nodeMap["bar"], TNode("baz"));
+
+ // const version of operator[]
+ UNIT_ASSERT_EQUAL(static_cast<const TNode&>(nodeMap)["42"].GetType(), TNode::Undefined);
+ UNIT_ASSERT(!nodeMap.HasKey("42"));
+
+ // nonconst version of operator[]
+ UNIT_ASSERT_EQUAL(nodeMap["42"].GetType(), TNode::Undefined);
+ UNIT_ASSERT(nodeMap.HasKey("42"));
+
+ nodeMap("rock!!!", TNode()
+ ("Pink", "Floyd")
+ ("Purple", "Deep"));
+
+ TNode copyNode;
+ copyNode = nodeMap;
+ UNIT_ASSERT_EQUAL(copyNode["foo"], TNode("bar"));
+ UNIT_ASSERT_EQUAL(copyNode["bar"], TNode("baz"));
+ UNIT_ASSERT(copyNode["42"].GetType() == TNode::Undefined);
+ UNIT_ASSERT_EQUAL(copyNode["rock!!!"]["Purple"], TNode("Deep"));
+ }
+
+ Y_UNIT_TEST(TestNodeList) {
+ TNode nodeList = TNode().Add("foo").Add(42).Add(3.14);
+ UNIT_ASSERT(nodeList.IsList());
+ UNIT_ASSERT_EQUAL(nodeList.GetType(), TNode::List);
+ UNIT_ASSERT_VALUES_EQUAL(nodeList.Size(), 3);
+
+ UNIT_ASSERT_EQUAL(nodeList[1], TNode(42));
+ nodeList.Add(TNode().Add("ls").Add("pwd"));
+
+ TNode copyNode;
+ copyNode = nodeList;
+ UNIT_ASSERT_EQUAL(copyNode[0], TNode("foo"));
+ UNIT_ASSERT_EQUAL(copyNode[3][1], TNode("pwd"));
+ }
+
+ Y_UNIT_TEST(TestInsertingMethodsFromTemporaryObjects) {
+ // check that .Add(...) doesn't return lvalue reference to temporary object
+ {
+ const TNode& nodeList = TNode().Add(0).Add("pass").Add(0);
+ UNIT_ASSERT_EQUAL(nodeList[1], TNode("pass"));
+ }
+
+ // check that .operator()(...) doesn't return lvalue reference to temporary object
+ {
+ const TNode& nodeMap = TNode()("1", 0)("2", "pass")("3", 0);
+ UNIT_ASSERT_EQUAL(nodeMap["2"], TNode("pass"));
+ }
+ }
+
+ Y_UNIT_TEST(TestAttributes) {
+ TNode node = TNode()("lee", 42)("faa", 54);
+ UNIT_ASSERT(!node.HasAttributes());
+ node.Attributes()("foo", true)("bar", false);
+ UNIT_ASSERT(node.HasAttributes());
+
+ {
+ TNode copyNode;
+ UNIT_ASSERT(!copyNode.HasAttributes());
+ copyNode = node;
+ UNIT_ASSERT(copyNode.HasAttributes());
+ UNIT_ASSERT_EQUAL(copyNode.GetAttributes()["foo"], TNode(true));
+ }
+
+ {
+ TNode movedWithoutAttributes(42);
+ movedWithoutAttributes.Attributes()("one", 1)("two", 2);
+ movedWithoutAttributes.MoveWithoutAttributes(TNode(node));
+ UNIT_ASSERT(movedWithoutAttributes.IsMap());
+ UNIT_ASSERT_EQUAL(movedWithoutAttributes["lee"], TNode(42));
+ UNIT_ASSERT_EQUAL(movedWithoutAttributes.GetAttributes()["one"], TNode(1));
+ UNIT_ASSERT(!movedWithoutAttributes.GetAttributes().HasKey("foo"));
+ }
+
+ {
+ TNode copyNode = node;
+ UNIT_ASSERT(copyNode.HasAttributes());
+ UNIT_ASSERT(copyNode.GetAttributes().HasKey("foo"));
+ copyNode.ClearAttributes();
+ UNIT_ASSERT(!copyNode.HasAttributes());
+ UNIT_ASSERT(!copyNode.GetAttributes().HasKey("foo"));
+ }
+
+ {
+ TNode copyNode = node;
+ UNIT_ASSERT(copyNode.HasAttributes());
+ UNIT_ASSERT(copyNode.GetAttributes().HasKey("foo"));
+ copyNode.Clear();
+ UNIT_ASSERT(!copyNode.HasAttributes());
+ UNIT_ASSERT(!copyNode.GetAttributes().HasKey("foo"));
+ }
+ }
+
+ Y_UNIT_TEST(TestEq) {
+ TNode nodeNoAttributes = TNode()("lee", 42)("faa", 54);
+ TNode node = nodeNoAttributes;
+ node.Attributes()("foo", true)("bar", false);
+ UNIT_ASSERT(node != nodeNoAttributes);
+ UNIT_ASSERT(nodeNoAttributes != node);
+ TNode copyNode = node;
+ UNIT_ASSERT(copyNode == node);
+ UNIT_ASSERT(node == copyNode);
+ }
+
+ Y_UNIT_TEST(TestComparison) {
+ using namespace NYT::NNodeCmp;
+ {
+ TNode nodeNoAttributes = TNode()("lee", 42)("faa", 54);
+ TNode node = nodeNoAttributes;
+ node.Attributes()("foo", true)("bar", false);
+ UNIT_ASSERT_EXCEPTION(node > nodeNoAttributes, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node >= nodeNoAttributes, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(nodeNoAttributes < node, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(nodeNoAttributes <= node, TNode::TTypeError);
+ }
+ {
+ TNode nodeMap = TNode()("map", 23);
+ TNode nodeList = TNode::CreateList();
+ UNIT_ASSERT_EXCEPTION(nodeList > nodeMap, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(nodeMap < nodeList, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(nodeMap >= nodeMap, TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(nodeList <= nodeList, TNode::TTypeError);
+ }
+ {
+ TNode node1("aaa");
+ TNode node2("bbb");
+ TNode node3("ccc");
+ UNIT_ASSERT(node1 < node2);
+ UNIT_ASSERT(node1 <= node2);
+ UNIT_ASSERT(node1 < node3);
+ UNIT_ASSERT(node1 <= node3);
+ UNIT_ASSERT(!(node3 < node1));
+ UNIT_ASSERT(!(node1 > node3));
+ UNIT_ASSERT(!(node3 <= node1));
+ UNIT_ASSERT(!(node1 >= node3));
+
+ UNIT_ASSERT(node3 > node2);
+ UNIT_ASSERT(node3 >= node2);
+ UNIT_ASSERT(node3 > node1);
+ UNIT_ASSERT(node3 >= node1);
+
+ UNIT_ASSERT(node1 <= node1);
+ UNIT_ASSERT(node1 >= node1);
+ }
+ {
+ TNode node1(23);
+ TNode node2("bbb");
+ TNode node3 = TNode::CreateEntity();
+
+ UNIT_ASSERT(node1 > node2);
+ UNIT_ASSERT(node1 >= node2);
+ UNIT_ASSERT(node2 < node1);
+ UNIT_ASSERT(node2 <= node1);
+
+ UNIT_ASSERT(!(node1 < node2));
+ UNIT_ASSERT(!(node1 <= node2));
+ UNIT_ASSERT(!(node2 > node1));
+ UNIT_ASSERT(!(node2 >= node1));
+
+ UNIT_ASSERT(node1 < node3);
+ UNIT_ASSERT(node2 < node3);
+ UNIT_ASSERT(node3 <= node3);
+ UNIT_ASSERT(!(node3 < node3));
+ UNIT_ASSERT(!(node3 > node3));
+ UNIT_ASSERT(!(node2 >= node3));
+ }
+ }
+
+ Y_UNIT_TEST(TestSaveLoad) {
+ TNode node = TNode()("foo", "bar")("baz", 42);
+ node.Attributes()["attr_name"] = "attr_value";
+
+ TString bytes;
+ {
+ TStringOutput s(bytes);
+ ::Save(&s, node);
+ }
+
+ TNode nodeCopy;
+ {
+ TStringInput s(bytes);
+ ::Load(&s, nodeCopy);
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(node, nodeCopy);
+ }
+
+ Y_UNIT_TEST(TestIntCast) {
+ TNode node = 1ull << 31;
+ UNIT_ASSERT(node.IsUint64());
+ UNIT_ASSERT_EXCEPTION(node.IntCast<i32>(), TNode::TTypeError);
+ UNIT_ASSERT(node.IntCast<ui32>() == static_cast<ui32>(node.AsUint64()));
+ UNIT_ASSERT(node.IntCast<i64>() == static_cast<i64>(node.AsUint64()));
+ UNIT_ASSERT(node.IntCast<ui64>() == node.AsUint64());
+
+ node = 1ull << 63;
+ UNIT_ASSERT(node.IsUint64());
+ UNIT_ASSERT_EXCEPTION(node.IntCast<i64>(), TNode::TTypeError);
+ UNIT_ASSERT(node.IntCast<ui64>() == node.AsUint64());
+
+ node = 12345;
+ UNIT_ASSERT(node.IsInt64());
+ UNIT_ASSERT_EXCEPTION(node.IntCast<i8>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node.IntCast<ui8>(), TNode::TTypeError);
+ UNIT_ASSERT(node.IntCast<i16>() == static_cast<i16>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<ui16>() == static_cast<ui16>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<i32>() == static_cast<i32>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<ui32>() == static_cast<ui32>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<i64>() == node.AsInt64());
+ UNIT_ASSERT(node.IntCast<ui64>() == static_cast<ui64>(node.AsInt64()));
+
+ node = -5;
+ UNIT_ASSERT(node.IsInt64());
+ UNIT_ASSERT(node.IntCast<i8>() == static_cast<i8>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<i16>() == static_cast<i16>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<i32>() == static_cast<i32>(node.AsInt64()));
+ UNIT_ASSERT(node.IntCast<i64>() == node.AsInt64());
+ UNIT_ASSERT_EXCEPTION(node.IntCast<ui8>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node.IntCast<ui16>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node.IntCast<ui32>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node.IntCast<ui64>(), TNode::TTypeError);
+ }
+
+ Y_UNIT_TEST(TestConvertToString) {
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5).ConvertTo<TString>(), "5");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(123432423).ConvertTo<TString>(), "123432423");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(123456789012345678ll).ConvertTo<TString>(), "123456789012345678");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(123456789012345678ull).ConvertTo<TString>(), "123456789012345678");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(-123456789012345678ll).ConvertTo<TString>(), "-123456789012345678");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(true).ConvertTo<TString>(), "1");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(false).ConvertTo<TString>(), "0");
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5.3).ConvertTo<TString>(), "5.3");
+ }
+
+ Y_UNIT_TEST(TestConvertFromString) {
+ UNIT_ASSERT_VALUES_EQUAL(TNode("123456789012345678").ConvertTo<ui64>(), 123456789012345678ull);
+ UNIT_ASSERT_VALUES_EQUAL(TNode("123456789012345678").ConvertTo<i64>(), 123456789012345678);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(ToString(1ull << 63)).ConvertTo<ui64>(), 1ull << 63);
+ UNIT_ASSERT_EXCEPTION(TNode(ToString(1ull << 63)).ConvertTo<i64>(), TFromStringException);
+ UNIT_ASSERT_VALUES_EQUAL(TNode("5.34").ConvertTo<double>(), 5.34);
+ }
+
+ Y_UNIT_TEST(TestConvertDoubleInt) {
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5.3).ConvertTo<i8>(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5.3).ConvertTo<ui8>(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5.3).ConvertTo<i64>(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(5.3).ConvertTo<ui64>(), 5);
+
+ UNIT_ASSERT_VALUES_EQUAL(TNode(-5.3).ConvertTo<i8>(), -5);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(-5.3).ConvertTo<i64>(), -5);
+ UNIT_ASSERT_EXCEPTION(TNode(-5.3).ConvertTo<ui8>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(TNode(-5.3).ConvertTo<ui64>(), TNode::TTypeError);
+
+ UNIT_ASSERT_VALUES_EQUAL(TNode(127.0).ConvertTo<i8>(), 127);
+ UNIT_ASSERT_EXCEPTION(TNode(128.0).ConvertTo<i8>(), TNode::TTypeError);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(255.0).ConvertTo<ui8>(), 255);
+ UNIT_ASSERT_EXCEPTION(TNode(256.0).ConvertTo<ui8>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(TNode(1e100).ConvertTo<i64>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(TNode(1e100).ConvertTo<ui64>(), TNode::TTypeError);
+ {
+ double v = 1ull << 63;
+ TNode node = v;
+ UNIT_ASSERT(node.IsDouble());
+ UNIT_ASSERT_EXCEPTION(node.ConvertTo<i64>(), TNode::TTypeError);
+ UNIT_ASSERT_VALUES_EQUAL(node.ConvertTo<ui64>(), static_cast<ui64>(v));
+ }
+ {
+ double v = (double)(1ull << 63) + (1ull << 63);
+ TNode node = v;
+ UNIT_ASSERT(node.IsDouble());
+ UNIT_ASSERT_EXCEPTION(node.ConvertTo<i64>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(node.ConvertTo<ui64>(), TNode::TTypeError);
+ }
+ UNIT_ASSERT_EXCEPTION(TNode(NAN).ConvertTo<ui64>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(TNode(NAN).ConvertTo<i64>(), TNode::TTypeError);
+
+ UNIT_ASSERT_EXCEPTION(TNode(INFINITY).ConvertTo<ui64>(), TNode::TTypeError);
+ UNIT_ASSERT_EXCEPTION(TNode(INFINITY).ConvertTo<i64>(), TNode::TTypeError);
+ }
+
+ Y_UNIT_TEST(TestConvertToBool) {
+ UNIT_ASSERT_VALUES_EQUAL(TNode("true").ConvertTo<bool>(), true);
+ UNIT_ASSERT_VALUES_EQUAL(TNode("TRUE").ConvertTo<bool>(), true);
+ UNIT_ASSERT_VALUES_EQUAL(TNode("false").ConvertTo<bool>(), false);
+ UNIT_ASSERT_VALUES_EQUAL(TNode("FALSE").ConvertTo<bool>(), false);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(1).ConvertTo<bool>(), true);
+ UNIT_ASSERT_VALUES_EQUAL(TNode(0).ConvertTo<bool>(), false);
+ UNIT_ASSERT_EXCEPTION(TNode("random").ConvertTo<bool>(), TFromStringException);
+ UNIT_ASSERT_EXCEPTION(TNode("").ConvertTo<bool>(), TFromStringException);
+ }
+
+ Y_UNIT_TEST(TestCanonicalSerialization) {
+ auto node = TNode()
+ ("ca", "ca")("c", "c")("a", "a")("b", "b")
+ ("bb", TNode()
+ ("ii", "ii")("i", "i")("jj", "jj"));
+ node.Attributes() = TNode()("za", "za")("z", "z")("xxx", "xxx")("xx", "xx");
+ UNIT_ASSERT_VALUES_EQUAL(NodeToCanonicalYsonString(node),
+ "<\"xx\"=\"xx\";\"xxx\"=\"xxx\";\"z\"=\"z\";\"za\"=\"za\">"
+ "{\"a\"=\"a\";\"b\"=\"b\";\"bb\"="
+ "{\"i\"=\"i\";\"ii\"=\"ii\";\"jj\"=\"jj\"};"
+ "\"c\"=\"c\";\"ca\"=\"ca\"}");
+ }
+
+ Y_UNIT_TEST(OperatorEqualSubnode) {
+ TNode node;
+ node["a"]["b"] = "c";
+
+ node = node["a"];
+ node = node["b"];
+
+ UNIT_ASSERT_VALUES_EQUAL(node.AsString(), "c");
+ }
+
+ Y_UNIT_TEST(TestMapGetters) {
+ auto node = TNode::CreateMap()
+ ("string", "7")
+ ("int64", 3)
+ ("uint64", 5u)
+ ("double", -3.5)
+ ("list", TNode::CreateList().Add(5))
+ ("map", TNode::CreateMap()("key", "value"));
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TString>("string"), "7");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsString("string"), "7");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<i64>("string"), 7);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<i64>("int64"), 3);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsInt64("int64"), 3);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildIntCast<ui64>("int64"), 3u);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<ui64>("uint64"), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsUint64("uint64"), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildIntCast<i64>("uint64"), 5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<TString>("uint64"), "5");
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<double>("double"), -3.5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsDouble("double"), -3.5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<TString>("double"), "-3.5");
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TNode::TListType>("list")[0].AsInt64(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsList("list")[0].AsInt64(), 5);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TNode::TMapType>("map")["key"].AsString(), "value");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsMap("map")["key"].AsString(), "value");
+
+ // mutable accessor
+ auto& childString = node.ChildAs<TString>("string");
+ childString = "yaddayadda";
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TString>("string"), "yaddayadda");
+ }
+
+ Y_UNIT_TEST(TestListGetters) {
+ auto node = TNode::CreateList()
+ .Add("7")
+ .Add(3)
+ .Add(5u)
+ .Add(-3.5)
+ .Add(TNode::CreateList().Add(5))
+ .Add(TNode::CreateMap()("key", "value"));
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TString>(0), "7");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsString(0), "7");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<i64>(0), 7);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<i64>(1), 3);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsInt64(1), 3);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildIntCast<ui64>(1), 3u);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<ui64>(2), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsUint64(2), 5u);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildIntCast<i64>(2), 5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<TString>(2), "5");
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<double>(3), -3.5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsDouble(3), -3.5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildConvertTo<TString>(3), "-3.5");
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TNode::TListType>(4)[0].AsInt64(), 5);
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsList(4)[0].AsInt64(), 5);
+
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TNode::TMapType>(5)["key"].AsString(), "value");
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAsMap(5)["key"].AsString(), "value");
+
+ // mutable accessor
+ auto& childString = node.ChildAs<TString>(0);
+ childString = "yaddayadda";
+ UNIT_ASSERT_VALUES_EQUAL(node.ChildAs<TString>(0), "yaddayadda");
+ }
+}
diff --git a/library/cpp/yson/node/node_visitor.cpp b/library/cpp/yson/node/node_visitor.cpp
new file mode 100644
index 0000000000..899fbfa02a
--- /dev/null
+++ b/library/cpp/yson/node/node_visitor.cpp
@@ -0,0 +1,152 @@
+#include "node_visitor.h"
+
+#include <util/generic/algorithm.h>
+#include <util/string/printf.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+template <typename Fun>
+void Iterate(const TNode::TMapType& nodeMap, bool sortByKey, Fun action)
+{
+ if (sortByKey) {
+ TVector<TNode::TMapType::const_iterator> iterators;
+ for (auto it = nodeMap.begin(); it != nodeMap.end(); ++it) {
+ iterators.push_back(it);
+ }
+ SortBy(iterators, [](TNode::TMapType::const_iterator it) { return it->first; });
+ for (const auto& it : iterators) {
+ action(*it);
+ }
+ } else {
+ ForEach(nodeMap.begin(), nodeMap.end(), action);
+ }
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TNodeVisitor::TNodeVisitor(NYson::IYsonConsumer* consumer, bool sortMapKeys)
+ : Consumer_(consumer)
+ , SortMapKeys_(sortMapKeys)
+{ }
+
+void TNodeVisitor::Visit(const TNode& node)
+{
+ VisitAny(node);
+}
+
+void TNodeVisitor::VisitAny(const TNode& node)
+{
+ if (node.HasAttributes()) {
+ Consumer_->OnBeginAttributes();
+ Iterate(node.GetAttributes().AsMap(), SortMapKeys_, [&](const std::pair<TString, TNode>& item) {
+ Consumer_->OnKeyedItem(item.first);
+ if (item.second.IsUndefined()) {
+ ythrow TNode::TTypeError() << "unable to visit attribute value of type "
+ << TNode::EType::Undefined << "; attribute name: `" << item.first << '\'' ;
+ }
+ VisitAny(item.second);
+ });
+ Consumer_->OnEndAttributes();
+ }
+
+ switch (node.GetType()) {
+ case TNode::String:
+ VisitString(node);
+ break;
+ case TNode::Int64:
+ VisitInt64(node);
+ break;
+ case TNode::Uint64:
+ VisitUint64(node);
+ break;
+ case TNode::Double:
+ VisitDouble(node);
+ break;
+ case TNode::Bool:
+ VisitBool(node);
+ break;
+ case TNode::List:
+ VisitList(node.AsList());
+ break;
+ case TNode::Map:
+ VisitMap(node.AsMap());
+ break;
+ case TNode::Null:
+ VisitEntity();
+ break;
+ case TNode::Undefined:
+ ythrow TNode::TTypeError() << "unable to visit TNode of type " << node.GetType();
+ default:
+ Y_FAIL("Unexpected type: %d", node.GetType());
+ }
+}
+
+void TNodeVisitor::VisitString(const TNode& node)
+{
+ Consumer_->OnStringScalar(node.AsString());
+}
+
+void TNodeVisitor::VisitInt64(const TNode& node)
+{
+ Consumer_->OnInt64Scalar(node.AsInt64());
+}
+
+void TNodeVisitor::VisitUint64(const TNode& node)
+{
+ Consumer_->OnUint64Scalar(node.AsUint64());
+}
+
+void TNodeVisitor::VisitDouble(const TNode& node)
+{
+ Consumer_->OnDoubleScalar(node.AsDouble());
+}
+
+void TNodeVisitor::VisitBool(const TNode& node)
+{
+ Consumer_->OnBooleanScalar(node.AsBool());
+}
+
+void TNodeVisitor::VisitList(const TNode::TListType& nodeList)
+{
+ Consumer_->OnBeginList();
+ size_t index = 0;
+ for (const auto& item : nodeList) {
+ Consumer_->OnListItem();
+ if (item.IsUndefined()) {
+ ythrow TNode::TTypeError() << "unable to visit list node child of type "
+ << TNode::EType::Undefined << "; list index: " << index;
+ }
+ VisitAny(item);
+ ++index;
+ }
+ Consumer_->OnEndList();
+}
+
+void TNodeVisitor::VisitMap(const TNode::TMapType& nodeMap)
+{
+ Consumer_->OnBeginMap();
+ Iterate(nodeMap, SortMapKeys_, [&](const std::pair<TString, TNode>& item) {
+ Consumer_->OnKeyedItem(item.first);
+ if (item.second.IsUndefined()) {
+ ythrow TNode::TTypeError() << "unable to visit map node child of type "
+ << TNode::EType::Undefined << "; map key: `" << item.first << '\'' ;
+ }
+ VisitAny(item.second);
+ });
+ Consumer_->OnEndMap();
+}
+
+void TNodeVisitor::VisitEntity()
+{
+ Consumer_->OnEntity();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/node_visitor.h b/library/cpp/yson/node/node_visitor.h
new file mode 100644
index 0000000000..db25832309
--- /dev/null
+++ b/library/cpp/yson/node/node_visitor.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "node.h"
+
+#include <library/cpp/yson/consumer.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TNodeVisitor
+{
+public:
+ TNodeVisitor(NYson::IYsonConsumer* consumer, bool sortMapKeys = false);
+
+ void Visit(const TNode& node);
+ void VisitMap(const TNode::TMapType& nodeMap);
+ void VisitList(const TNode::TListType& nodeMap);
+
+private:
+ NYson::IYsonConsumer* Consumer_;
+ bool SortMapKeys_;
+
+private:
+ void VisitAny(const TNode& node);
+
+ void VisitString(const TNode& node);
+ void VisitInt64(const TNode& node);
+ void VisitUint64(const TNode& node);
+ void VisitDouble(const TNode& node);
+ void VisitBool(const TNode& node);
+ void VisitEntity();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/pybind/node.cpp b/library/cpp/yson/node/pybind/node.cpp
new file mode 100644
index 0000000000..79beba3647
--- /dev/null
+++ b/library/cpp/yson/node/pybind/node.cpp
@@ -0,0 +1,105 @@
+#include "node.h"
+
+#include <library/cpp/yson/node/node.h>
+
+#include <library/cpp/pybind/cast.h>
+
+#include <Python.h>
+
+namespace NYT {
+
+ PyObject* BuildPyObject(const TNode& node) {
+ switch (node.GetType()) {
+ case TNode::Bool:
+ return NPyBind::BuildPyObject(node.AsBool());
+ case TNode::Int64:
+ return NPyBind::BuildPyObject(node.AsInt64());
+ case TNode::Uint64:
+ return NPyBind::BuildPyObject(node.AsUint64());
+ case TNode::Double:
+ return NPyBind::BuildPyObject(node.AsDouble());
+ case TNode::String:
+ return NPyBind::BuildPyObject(node.AsString());
+ case TNode::List:
+ return NPyBind::BuildPyObject(node.AsList());
+ case TNode::Map:
+ return NPyBind::BuildPyObject(node.AsMap());
+ case TNode::Null:
+ Py_RETURN_NONE;
+ case TNode::Undefined:
+ ythrow TNode::TTypeError() << "BuildPyObject called for undefined TNode";
+ }
+ }
+
+} // namespace NYT
+
+namespace NPyBind {
+
+ template <>
+ bool FromPyObject(PyObject* obj, NYT::TNode& res) {
+ if (obj == Py_None) {
+ res = NYT::TNode::CreateEntity();
+ return true;
+ }
+ if (PyBool_Check(obj)) {
+ res = false;
+ return FromPyObject(obj, res.As<bool>());
+ }
+ if (PyFloat_Check(obj)) {
+ res = 0.0;
+ return FromPyObject(obj, res.As<double>());
+ }
+#if PY_MAJOR_VERSION < 3
+ if (PyString_Check(obj)) {
+ res = TString();
+ return FromPyObject(obj, res.As<TString>());
+ }
+#else
+ if (PyUnicode_Check(obj)) {
+ res = TString();
+ return FromPyObject(obj, res.As<TString>());
+ }
+ if (PyBytes_Check(obj)) {
+ res = TString();
+ return FromPyObject(obj, res.As<TString>());
+ }
+#endif
+ if (PyList_Check(obj)) {
+ res = NYT::TNode::CreateList();
+ return FromPyObject(obj, res.AsList());
+ }
+ if (PyDict_Check(obj)) {
+ res = NYT::TNode::CreateMap();
+ return FromPyObject(obj, res.AsMap());
+ }
+#if PY_MAJOR_VERSION < 3
+ if (PyInt_Check(obj)) {
+ auto valAsLong = PyInt_AsLong(obj);
+ if (valAsLong == -1 && PyErr_Occurred()) {
+ return false;
+ }
+ res = valAsLong;
+ return true;
+ }
+#endif
+ if (PyLong_Check(obj)) {
+ int overflow = 0;
+ auto valAsLong = PyLong_AsLongAndOverflow(obj, &overflow);
+ if (!overflow) {
+ if (valAsLong == -1 && PyErr_Occurred()) {
+ return false;
+ }
+ res = valAsLong;
+ return true;
+ }
+ auto valAsULong = PyLong_AsUnsignedLong(obj);
+ if (valAsULong == static_cast<decltype(valAsULong)>(-1) && PyErr_Occurred()) {
+ return false;
+ }
+ res = valAsULong;
+ return true;
+ }
+ return false;
+ }
+
+} // namespace NPyBind
diff --git a/library/cpp/yson/node/pybind/node.h b/library/cpp/yson/node/pybind/node.h
new file mode 100644
index 0000000000..65f7236de6
--- /dev/null
+++ b/library/cpp/yson/node/pybind/node.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <Python.h>
+
+#include <library/cpp/yson/node/node.h>
+
+namespace NYT {
+ PyObject* BuildPyObject(const TNode& val);
+}
diff --git a/library/cpp/yson/node/pybind/ya.make b/library/cpp/yson/node/pybind/ya.make
new file mode 100644
index 0000000000..97b7583e96
--- /dev/null
+++ b/library/cpp/yson/node/pybind/ya.make
@@ -0,0 +1,16 @@
+PY23_NATIVE_LIBRARY()
+
+OWNER(
+ inngonch
+ g:yt
+)
+
+PEERDIR(
+ library/cpp/pybind
+ library/cpp/yson/node
+)
+SRCS(
+ node.cpp
+)
+
+END()
diff --git a/library/cpp/yson/node/serialize.cpp b/library/cpp/yson/node/serialize.cpp
new file mode 100644
index 0000000000..aeb467622b
--- /dev/null
+++ b/library/cpp/yson/node/serialize.cpp
@@ -0,0 +1,101 @@
+#include "serialize.h"
+
+#include "node_visitor.h"
+
+#include <library/cpp/yson/consumer.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TString& value, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnStringScalar(value);
+}
+
+void Serialize(const TStringBuf& value, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnStringScalar(value);
+}
+
+void Serialize(const char* value, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnStringScalar(value);
+}
+
+void Deserialize(TString& value, const TNode& node)
+{
+ value = node.AsString();
+}
+
+#define SERIALIZE_SIGNED(type) \
+void Serialize(type value, NYson::IYsonConsumer* consumer) \
+{ \
+ consumer->OnInt64Scalar(static_cast<i64>(value)); \
+}
+
+#define SERIALIZE_UNSIGNED(type) \
+void Serialize(type value, NYson::IYsonConsumer* consumer) \
+{ \
+ consumer->OnUint64Scalar(static_cast<ui64>(value)); \
+}
+
+SERIALIZE_SIGNED(signed char);
+SERIALIZE_SIGNED(short);
+SERIALIZE_SIGNED(int);
+SERIALIZE_SIGNED(long);
+SERIALIZE_SIGNED(long long);
+
+SERIALIZE_UNSIGNED(unsigned char);
+SERIALIZE_UNSIGNED(unsigned short);
+SERIALIZE_UNSIGNED(unsigned int);
+SERIALIZE_UNSIGNED(unsigned long);
+SERIALIZE_UNSIGNED(unsigned long long);
+
+#undef SERIALIZE_SIGNED
+#undef SERIALIZE_UNSIGNED
+
+void Deserialize(i64& value, const TNode& node)
+{
+ value = node.AsInt64();
+}
+
+void Deserialize(ui64& value, const TNode& node)
+{
+ value = node.AsUint64();
+}
+
+void Serialize(double value, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnDoubleScalar(value);
+}
+
+void Deserialize(double& value, const TNode& node)
+{
+ value = node.AsDouble();
+}
+
+void Serialize(bool value, NYson::IYsonConsumer* consumer)
+{
+ consumer->OnBooleanScalar(value);
+}
+
+void Deserialize(bool& value, const TNode& node)
+{
+ value = node.AsBool();
+}
+
+void Serialize(const TNode& node, NYson::IYsonConsumer* consumer)
+{
+ TNodeVisitor visitor(consumer);
+ visitor.Visit(node);
+}
+
+void Deserialize(TNode& value, const TNode& node)
+{
+ value = node;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/serialize.h b/library/cpp/yson/node/serialize.h
new file mode 100644
index 0000000000..99b598a44c
--- /dev/null
+++ b/library/cpp/yson/node/serialize.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "node.h"
+
+namespace NYT {
+
+namespace NYson {
+struct IYsonConsumer;
+} // namespace NYson
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Serialize(const TString& value, NYson::IYsonConsumer* consumer);
+void Serialize(const TStringBuf& value, NYson::IYsonConsumer* consumer);
+void Serialize(const char* value, NYson::IYsonConsumer* consumer);
+void Deserialize(TString& value, const TNode& node);
+
+void Serialize(signed char value, NYson::IYsonConsumer* consumer);
+void Serialize(short value, NYson::IYsonConsumer* consumer);
+void Serialize(int value, NYson::IYsonConsumer* consumer);
+void Serialize(long value, NYson::IYsonConsumer* consumer);
+void Serialize(long long value, NYson::IYsonConsumer* consumer);
+void Deserialize(i64& value, const TNode& node);
+
+void Serialize(unsigned char value, NYson::IYsonConsumer* consumer);
+void Serialize(unsigned short value, NYson::IYsonConsumer* consumer);
+void Serialize(unsigned int value, NYson::IYsonConsumer* consumer);
+void Serialize(unsigned long value, NYson::IYsonConsumer* consumer);
+void Serialize(unsigned long long value, NYson::IYsonConsumer* consumer);
+void Deserialize(ui64& value, const TNode& node);
+
+void Serialize(double value, NYson::IYsonConsumer* consumer);
+void Deserialize(double& value, const TNode& node);
+
+void Serialize(bool value, NYson::IYsonConsumer* consumer);
+void Deserialize(bool& value, const TNode& node);
+
+void Serialize(const TNode& node, NYson::IYsonConsumer* consumer);
+void Deserialize(TNode& value, const TNode& node);
+
+void Serialize(const THashMap<TString, TString>& renameColumns, NYson::IYsonConsumer* consumer);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yson/node/ut/ya.make b/library/cpp/yson/node/ut/ya.make
new file mode 100644
index 0000000000..f49a0bf7df
--- /dev/null
+++ b/library/cpp/yson/node/ut/ya.make
@@ -0,0 +1,12 @@
+UNITTEST_FOR(library/cpp/yson/node)
+
+OWNER(
+ ermolovd
+ g:yt
+)
+
+SRCS(
+ node_ut.cpp
+)
+
+END()
diff --git a/library/cpp/yson/node/ya.make b/library/cpp/yson/node/ya.make
new file mode 100644
index 0000000000..a082b293c4
--- /dev/null
+++ b/library/cpp/yson/node/ya.make
@@ -0,0 +1,25 @@
+LIBRARY()
+
+GENERATE_ENUM_SERIALIZATION(node.h)
+
+PEERDIR(
+ library/cpp/yson
+ library/cpp/yson/json
+)
+
+OWNER(
+ ermolovd
+ g:yt
+)
+
+SRCS(
+ node.cpp
+ node_io.cpp
+ node_builder.cpp
+ node_visitor.cpp
+ serialize.cpp
+)
+
+END()
+
+RECURSE_FOR_TESTS(ut)