-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
utility: new utility method to convert proto value to string #36334
Conversation
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
This method will be used in the access log formatter first. |
source/common/protobuf/utility.cc
Outdated
switch (value.kind_case()) { | ||
case ProtobufWkt::Value::KIND_NOT_SET: | ||
case ProtobufWkt::Value::kNullValue: | ||
streamer.addNull(); | ||
break; | ||
case ProtobufWkt::Value::kNumberValue: | ||
streamer.addNumber(value.number_value()); | ||
break; | ||
case ProtobufWkt::Value::kStringValue: | ||
streamer.addString(value.string_value()); | ||
break; | ||
case ProtobufWkt::Value::kBoolValue: | ||
streamer.addBool(value.bool_value()); | ||
break; | ||
case ProtobufWkt::Value::kStructValue: { | ||
auto map = streamer.makeRootMap(); | ||
structValueToJson(value.struct_value(), *map); | ||
break; | ||
} | ||
case ProtobufWkt::Value::kListValue: | ||
auto arr = streamer.makeRootArray(); | ||
listValueToJson(value.list_value(), *arr); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are some duplicated code in the ValueUtil::toJsonString
and valueToJson
. If the Streamer
could provides addMap()
and addArray()
, then we can deduplicate these code by template. But it's up to @jmarantz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following what's duplicated yet; can you describe more If there's a small refactor to the streamer can you propose that as a separate PR just to make it go faster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void valueToJson(const ProtobufWkt::Value& value, JsonStringStreamer::Level& level) {
switch (value.kind_case()) {
case ProtobufWkt::Value::KIND_NOT_SET:
case ProtobufWkt::Value::kNullValue:
level.addNull();
break;
case ProtobufWkt::Value::kNumberValue:
level.addNumber(value.number_value());
break;
case ProtobufWkt::Value::kStringValue:
level.addString(value.string_value());
break;
case ProtobufWkt::Value::kBoolValue:
level.addBool(value.bool_value());
break;
case ProtobufWkt::Value::kStructValue: {
auto map = level.addMap();
structValueToJson(value.struct_value(), *map);
break;
}
case ProtobufWkt::Value::kListValue: {
auto array = level.addArray();
listValueToJson(value.list_value(), *array);
break;
}
}
}
vs
void ValueUtil::appendJsonToString(const ProtobufWkt::Value& value, std::string& dest) {
JsonStringStreamer streamer(dest);
switch (value.kind_case()) {
case ProtobufWkt::Value::KIND_NOT_SET:
case ProtobufWkt::Value::kNullValue:
streamer.addNull();
break;
case ProtobufWkt::Value::kNumberValue:
streamer.addNumber(value.number_value());
break;
case ProtobufWkt::Value::kStringValue:
streamer.addString(value.string_value());
break;
case ProtobufWkt::Value::kBoolValue:
streamer.addBool(value.bool_value());
break;
case ProtobufWkt::Value::kStructValue: {
auto map = streamer.makeRootMap();
structValueToJson(value.struct_value(), *map);
break;
}
case ProtobufWkt::Value::kListValue:
auto arr = streamer.makeRootArray();
listValueToJson(value.list_value(), *arr);
break;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmarantz will address other comments first. Also create a minor refactoring PR. But it completely up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm at high level.
source/common/protobuf/utility.cc
Outdated
break; | ||
case ProtobufWkt::Value::kStructValue: { | ||
auto map = level.addMap(); | ||
structValueToJson(value.struct_value(), *map); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you could drop the temp here and just call this with *level.addMap()
, so you wouldn't need the new scope. Up to you.
If you decide to keep the temp then please don't use auto
for this case per style guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*level.addMap()
looks good.
source/common/protobuf/utility.cc
Outdated
break; | ||
} | ||
case ProtobufWkt::Value::kListValue: { | ||
auto array = level.addArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
source/common/protobuf/utility.cc
Outdated
} | ||
// Sort the keys to make the output deterministic. | ||
std::sort(sorted_fields.begin(), sorted_fields.end(), | ||
[](auto a, auto b) { return a.get().first < b.get().first; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explicit types per style guide
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it.
source/common/protobuf/utility.cc
Outdated
@@ -852,6 +911,34 @@ ProtobufWkt::Value ValueUtil::listValue(const std::vector<ProtobufWkt::Value>& v | |||
return val; | |||
} | |||
|
|||
void ValueUtil::toJsonString(const ProtobufWkt::Value& value, std::string& dest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming this toJsonString
makes me think it will return a string. Should we call this appendJsonToString
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM.
source/common/protobuf/utility.cc
Outdated
switch (value.kind_case()) { | ||
case ProtobufWkt::Value::KIND_NOT_SET: | ||
case ProtobufWkt::Value::kNullValue: | ||
streamer.addNull(); | ||
break; | ||
case ProtobufWkt::Value::kNumberValue: | ||
streamer.addNumber(value.number_value()); | ||
break; | ||
case ProtobufWkt::Value::kStringValue: | ||
streamer.addString(value.string_value()); | ||
break; | ||
case ProtobufWkt::Value::kBoolValue: | ||
streamer.addBool(value.bool_value()); | ||
break; | ||
case ProtobufWkt::Value::kStructValue: { | ||
auto map = streamer.makeRootMap(); | ||
structValueToJson(value.struct_value(), *map); | ||
break; | ||
} | ||
case ProtobufWkt::Value::kListValue: | ||
auto arr = streamer.makeRootArray(); | ||
listValueToJson(value.list_value(), *arr); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following what's duplicated yet; can you describe more If there's a small refactor to the streamer can you propose that as a separate PR just to make it go faster?
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
source/common/protobuf/utility.cc
Outdated
structValueToJson(value.struct_value(), *streamer.makeRootMap()); | ||
break; | ||
case ProtobufWkt::Value::kListValue: | ||
listValueToJson(value.list_value(), *streamer.makeRootArray()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing how renaming makeRootArray to addArray in the streamer here would eliminate duplication; can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we rename the makeRoot* to add*, then we can update our two function to following to eliminate the duplicated code:
template <class StreamerOrLevel>
void valueToJson(const ProtobufWkt::Value& value, StreamerOrLevel& streamer_or_level) {
switch (value.kind_case()) {
case ProtobufWkt::Value::KIND_NOT_SET:
case ProtobufWkt::Value::kNullValue:
streamer_or_level.addNull();
break;
case ProtobufWkt::Value::kNumberValue:
streamer_or_level.addNumber(value.number_value());
break;
case ProtobufWkt::Value::kStringValue:
streamer_or_level.addString(value.string_value());
break;
case ProtobufWkt::Value::kBoolValue:
streamer_or_level.addBool(value.bool_value());
break;
case ProtobufWkt::Value::kStructValue:
structValueToJson(value.struct_value(), *streamer_or_level.addMap());
break;
case ProtobufWkt::Value::kListValue:
listValueToJson(value.list_value(), *streamer_or_level.addArray());
break;
}
}
void ValueUtil::appendJsonToString(const ProtobufWkt::Value& value, std::string& dest) {
JsonStringStreamer streamer(dest);
valueToJson(value, streamer);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can compare this with #36334 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, OK. I think I am OK with the way you have it in this PR with a duplicated switch. I can see the benefit of typing less code, but I feel like it creates an external dependency that Json::StreamerBase and Json::StreamerBase::Level have to have the same API, and if we are going to do that I think they should probably implement the same interface. I think it's probably not worth it to save this duplicated switch.
I am really a big fan of C++in most ways but one that would be nice is a language feature for describing an interface that a class passed into a template must implement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept? Haha.
test/common/protobuf/utility_test.cc
Outdated
repeated.Add()->set_value(20); | ||
EXPECT_THAT(RepeatedPtrUtil::debugString(repeated), | ||
testing::ContainsRegex("\\[.*[\n]*value:\\s*10\n,.*[\n]*value:\\s*20\n\\]")); | ||
testing::ContainsRegex("\\[value: 10\n, value: 20\n\\]")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this test work without this expectation change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at least cannot work at my local env. I can revert it to see whether will our ci say ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine, our CI say it ok
test/common/protobuf/utility_test.cc
Outdated
ValueUtil::toJsonString(v, json_string); | ||
EXPECT_EQ(json_string, "null"); | ||
json_string.clear(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add a test-method helper that takes a Value as an arg and returns the json string, with these 4 lines. Then you can structure tests like
ProtobufWkt::Value v;
EXPECT_EQ("null", toJson(v));
v.set_null_value(ProtobufWkt::NULL_VALUE));
EXPECT_EQ("null", toJson(v));
I think this will make tests more information-dense and easier to read.
/wait |
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flushing some more comments.
/wait
source/common/protobuf/utility.cc
Outdated
@@ -10,6 +10,7 @@ | |||
#include "source/common/common/assert.h" | |||
#include "source/common/common/documentation_url.h" | |||
#include "source/common/common/fmt.h" | |||
#include "source/common/json/json_streamer.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we move the new utility that depends on JsonStreamer into the json library? I think it's confusing, at the directory level, to have json depend on proto and vice versa. I don't think it's hard to break the cycle by moving the new utility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM.
source/common/protobuf/utility.cc
Outdated
@@ -106,6 +107,60 @@ absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& | |||
return validateDurationNoThrow(duration, kMaxInt64Nanoseconds); | |||
} | |||
|
|||
using JsonStringStreamer = Json::StreamerBase<Json::StringOutput>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this to the end of json_streamer.h, next to the nickname for the buffered streamer. You can name it StringStreamer since it will be in the 'Json' namespace.
We should do a follow-up PR to rename Json::Streamer to Json::BufferStreamer for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM.
source/common/protobuf/utility.cc
Outdated
structValueToJson(value.struct_value(), *streamer.makeRootMap()); | ||
break; | ||
case ProtobufWkt::Value::kListValue: | ||
listValueToJson(value.list_value(), *streamer.makeRootArray()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, OK. I think I am OK with the way you have it in this PR with a duplicated switch. I can see the benefit of typing less code, but I feel like it creates an external dependency that Json::StreamerBase and Json::StreamerBase::Level have to have the same API, and if we are going to do that I think they should probably implement the same interface. I think it's probably not worth it to save this duplicated switch.
I am really a big fan of C++in most ways but one that would be nice is a language feature for describing an interface that a class passed into a template must implement.
test/common/protobuf/utility_test.cc
Outdated
ValueUtil::toJsonString(v, json_string); | ||
EXPECT_EQ(json_string, "true"); | ||
json_string.clear(); | ||
EXPECT_EQ(toJson(v), "true"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a lot nicer. Do we need to declare a new ProtobufWkt::Value in each section? Or would this have the same effect:
ProtobufWkt::Value v;
EXPECT_EQ(toJson(v), "null");
v.set_bool_value(true);
EXPECT_EQ(toJson(v), "null");
v.set_number_value(1);
EXPECT_EQ(toJson(v), "1");
...
alternatively you could put ProtobufWkt::Value value_;
in the test fixture and have every one of these stanzas be a separate 1 or 2-line TEST_F.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM.
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
/retest |
…alue-to-string
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
Signed-off-by: wangbaiping <wangbaiping@bytedance.com>
…alue-to-string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, thanks!
Defer this to @alyssawilk for a senior maintainer review. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry for the delay, was out sick
LGTM assuming you're going to use this in a follow-up (which from PR description looks like). Can we convert both NO_YAML and YAML builds to this and remove legacy code?
Sadly, only part of positions could be optimized with this PR for now. |
…oxy#36334) Commit Message: utility: new utility method to convert proto value to string Additional Description: New utility method to convert the proto value to json. This could work even the `ENVOY_ENABLE_YAML` is not set and is exception free. Risk Level: low. Testing: unit test. Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. --------- Signed-off-by: wangbaiping <wangbaiping@bytedance.com> Signed-off-by: Steven Jin Xuan <sjinxuan@microsoft.com>
Commit Message: utility: new utility method to convert proto value to string
Additional Description:
New utility method to convert the proto value to json. This could work even the
ENVOY_ENABLE_YAML
is not set and is exception free.Risk Level: low.
Testing: unit test.
Docs Changes: n/a.
Release Notes: n/a.
Platform Specific Features: n/a.