-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
feat: support proper JSON for Value and Duration #1495
Conversation
66131d7
to
18e8071
Compare
67840bb
to
288649e
Compare
src/wrappers.js
Outdated
var Value = root.lookup("google.protobuf.Value"); | ||
|
||
for (; i < names.length; ++i) { | ||
fields[names[i]] = googleProtobufValueFromObject( |
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.
Why not just make this fields[names[i]] = Value.fromObject(object[names[i]])
? That should be properly delegated to the fromObject
wrapper here.
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.
Done, thank you! (changed both here and in toObject
)
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 don't like the heuristics in the Value and Struct fromObject
wrappers for interpreting the input differently. With those functions the idea is that you can take an arbitrary JSON object and convert it to a protobuf message, but those heuristics create bizarre and potentially confusing exceptions. I think it would be better to either choose the code path based on the value of the flags, like with toObject
, or have separate methods.
@murgatroid99 The heuristics are there because it will be a breaking change without them - Unfortunately, I feel that probably the better alternative is to remove heuristics and mark this as a breaking change for v7.0. At the same time it will allow us drop the What do you think? |
Just removing the heuristics has its own problems. If you do that, it's impossible to convert from the direct object representation of a Struct to a Struct message. |
True. And that will match the experience in other languages. The spec explains how different types must be encoded in JSON. Here, protobuf.js v6 (and earlier) violates the spec, while I plan to make protobuf.js v7 compatible with the spec. Example in Python: given this syntax = "proto3";
import "google/protobuf/struct.proto";
package test;
message Test {
google.protobuf.Struct struct = 1;
} and this test code: from google.protobuf.json_format import MessageToJson, Parse
from google.protobuf.struct_pb2 import Struct, Value
from test_pb2 import Test
value = Value(string_value="test")
fields = {"key": value}
struct = Struct(fields=fields)
message = Test(struct=struct)
# writes JSON
with open("test.json", "w") as f:
f.write(MessageToJson(message) + "\n")
# reads JSON
with open("test.json", "r") as f:
json_data = f.read()
parsed = Parse(json_data, Test())
print(parsed) it will write and then successfully read the following JSON representation of {
"struct": {
"key": "test"
}
} which corresponds to the following internal structure:
Now, if you try to pass the direct representation to this code: {
"struct": {
"fields": {
"key": {
"stringValue": "test"
}
}
}
} the Python code won't read it properly, instead getting this:
So basically, I'm not aware of a way to read the direct JSON representation of The only concern here is the compatibility. We definitely cannot drop the ability to read the direct representation without bumping the major, so - as I see it - we either go to v7 dropping the ability to read the direct representation with The third alternative would be to provide an option for
What do you think? |
JavaScript objects are not JSON. I think the right approach here may be to provide a separate |
OK, that sounds like a good solution - I will look at the code to see if I can implement it this way. I suppose the JSON functionality for |
|
||
// otherwise, it's a JSON representation as described in google/protobuf/struct.proto | ||
var self = this; | ||
var message = googleProtobufValueFromObject(object, function(obj) { return self.create(obj); }); |
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 believe create() is a static method--is that correct?
nullValue: 0 | ||
}); | ||
} | ||
if (typeof object === "number") { |
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 think this is missing boolValue.
// something that has just one property called stringValue, listValue, etc., | ||
// and possibly a property called kind, is likely an object we don't want to touch | ||
var names = Object.keys(object); | ||
if (names.length === 1 && names.match(/^(?:null|number|string|list|struct)Value$/) || |
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 think there are cases where names.match()
will be called on an Array object that doesn't have the match()
method. For example, you might have an obj
value that is { content: text }
that results in a names
array with a length of 1 (but no match()
method).
Maybe add an undefined check here?
if (names.length === 1 && names.match && names.match(...) ...
I think |
Folks, for those who follows this PR and, in general, follows the topic of proper proto3 JSON serialization/deserialization according to the protobuf spec, I wanted to post a quick update here. For reasons not directly related to anything in this PR, I won't be able to complete it here soon, so I released the proto3 JSON serialization/deserialization code as a separate npm package. $ npm install proto3-json-serializer At this moment, we only support instances of This separate package will cover the workflows I need at this time; if I have some free time to backport its functionality directly to protobuf.js (e.g. by generating |
This PR is blocking googleapis/nodejs-ai-platform#28 @alexander-fenster Is there an owner for getting this merged? |
@nicain This PR is not going to be merged because the original approach - to make proto3 JSON (de-)serialization using the existing Let's continue the discussion in the PR you mentioned. |
Forcing the use of a package for json serialization doesn't make much sense to me. Generally, I don't care about converting protobufs TO json. However, I do want to be able to take a javascript object that has the same structure as a protobuf schema and use Type.fromObject(msg) (or Type.create(msg) but that seems like asking a lot) to get a protobuf - even if a date field has a Date() in it instead of { seconds: x, nanos: y } Is there some way to set that up? It's really not clear from reading this, though I have yet to delve into the source code to see what I can understand about the wrappers functionality and dynamically adding wrappers. At the moment, I have schemaless code that is using plain javascript objects which is migrating to protobufs. We were able to define compatible protos for 100% of our existing messages except that we used Timestamp for date fields. The result is that we cannot serialize those date fields without first changing them out to { seconds: x, nanos: y }, which is quite the pain in the neck. Is there some way to automate that conversion? I don't care about the deserialization side as our consumers of serialized protos are all protobuf-aware and never require conversion back to plain javascript objects. |
Fixes #839.
This PR adds support for proper JSON encoding for
google.protobuf.Value
,google.protobuf.Struct
, andgoogle.protobuf.Duration
. The "proper JSON" is described in the corresponding protos: duration, struct, and here.To make sure this change does not break anyone, the modifications to
.toObject
are hidden under the new undocumented optionvalues: true
. The problem here is that we already have an undocumented optionjson: true
that changes the behavior forgoogle.protobuf.Any
, so I cannot safely reuse it without a possibility of breaking users' code.So, while we're in 6.x, use
{json: true, values: true}
to make.toObject
generate proper JSON representations for these types. In 7.x I plan to removevalues
and properly documentjson
(and probably make it enabled by default - this needs to be discussed).Note that
.fromObject
is safe to be updated since it will now work in cases where it previously failed (e.g. when the duration of"3s"
is passed instead of an object withseconds
andnanos
), and I consider this to be a feature and not a breaking change.See also: #1258 - after I spent some time working on this one, I think I will be able to properly review that one too :) (cc: @johncsnyder - thanks for your PR, we'll finally get it merged soon!)