-
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
Changes from all commits
ae79cc6
5aa5197
18e8071
288649e
d52b11a
3bf4a9e
7ba16d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,3 +100,220 @@ wrappers[".google.protobuf.Any"] = { | |
return this.toObject(message, options); | ||
} | ||
}; | ||
|
||
// recursive .fromObject implementation for google.protobuf.Value | ||
function googleProtobufValueFromObject(object, create) { | ||
if (object === null) { | ||
return create({ | ||
kind: "nullValue", | ||
nullValue: 0 | ||
}); | ||
} | ||
if (typeof object === "number") { | ||
return create({ | ||
kind: "numberValue", | ||
numberValue: object | ||
}); | ||
} | ||
if (typeof object === "string") { | ||
return create({ | ||
kind: "stringValue", | ||
stringValue: object | ||
}); | ||
} | ||
if (Array.isArray(object)) { | ||
var array = object.map(function(element) { return googleProtobufValueFromObject(element, create); }); | ||
return create({ | ||
kind: "listValue", | ||
listValue: { | ||
values: array | ||
} | ||
}); | ||
} | ||
if (typeof object === "object") { | ||
var fields = {}, | ||
names = Object.keys(object), | ||
i = 0; | ||
for (; i < names.length; ++i) { | ||
fields[names[i]] = googleProtobufValueFromObject(object[names[i]], create); | ||
} | ||
return create({ | ||
kind: "structValue", | ||
structValue: { | ||
fields: fields | ||
} | ||
}); | ||
} | ||
return undefined; | ||
} | ||
|
||
// recursive .toObject implementation for google.protobuf.Value | ||
function googleProtobufValueToObject(message) { | ||
if (message.kind === "nullValue") { | ||
return null; | ||
} | ||
if (message.kind === "numberValue") { | ||
return message.numberValue; | ||
} | ||
if (message.kind === "stringValue") { | ||
return message.stringValue; | ||
} | ||
if (message.kind === "listValue") { | ||
return message.listValue.values.map(googleProtobufValueToObject); | ||
} | ||
if (message.kind === "structValue") { | ||
if (!message.structValue.fields) { | ||
return {}; | ||
} | ||
var names = Object.keys(message.structValue.fields), | ||
i = 0, | ||
struct = {}; | ||
for (; i < names.length; ++i) { | ||
struct[names[i]] = googleProtobufValueToObject(message.structValue["fields"][names[i]]); | ||
} | ||
return struct; | ||
} | ||
return undefined; | ||
} | ||
|
||
// custom wrapper for google.protobuf.Value | ||
wrappers[".google.protobuf.Value"] = { | ||
fromObject: function(object) { | ||
// heuristic: if an object looks like a regular representation of google.protobuf.Value, | ||
// with all those stringValues, etc., just accept it as is for compatibility. | ||
if (typeof object === "object" && object) { | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. I think there are cases where Maybe add an undefined check here?
|
||
names.length === 2 && | ||
names.every(function(name) { | ||
return name.match(/^(?:kind|(?:null|number|string|list|struct)Value)$/); }) | ||
) { | ||
return this.fromObject(object); | ||
} | ||
} | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. I believe create() is a static method--is that correct? |
||
if (typeof message !== "undefined") { | ||
return message; | ||
} | ||
|
||
// fallback to the normal .fromObject if decoding failed | ||
return this.fromObject(object); | ||
}, | ||
|
||
toObject: function(message, options) { | ||
// decode value if requested | ||
// In the next major version we will get rid of "options.values". | ||
if (options && options.json && options.values) { | ||
var object = googleProtobufValueToObject(message); | ||
if (typeof object !== "undefined") { | ||
return object; | ||
} | ||
} | ||
|
||
return this.toObject(message, options); | ||
} | ||
}; | ||
|
||
// custom wrapper for google.protobuf.Struct | ||
wrappers[".google.protobuf.Struct"] = { | ||
fromObject: function(object) { | ||
if (typeof object === "object" && object) { | ||
var names = Object.keys(object), | ||
i = 0, | ||
fields = {}, | ||
Value = this.fields.fields.resolvedType; | ||
|
||
// heuristic: if an object looks like a regular representation of google.protobuf.Struct, | ||
// with just one field called `fields`, just accept it as is for compatibility. | ||
if (names.length === 1 && names[0] === "fields") { | ||
return this.fromObject(object); | ||
} | ||
|
||
for (; i < names.length; ++i) { | ||
fields[names[i]] = Value.fromObject(object[names[i]]); | ||
} | ||
return this.create({ | ||
fields: fields | ||
}); | ||
} | ||
|
||
// fallback to the normal .fromObject if decoding failed | ||
return this.fromObject(object); | ||
}, | ||
|
||
toObject: function(message, options) { | ||
// decode value if requested | ||
// In the next major version we will get rid of "options.values". | ||
if (options && options.json && options.values) { | ||
if (!message.fields) { | ||
return {}; | ||
} | ||
var names = Object.keys(message.fields), | ||
i = 0, | ||
struct = {}, | ||
Value = this.fields.fields.resolvedType; | ||
for (; i < names.length; ++i) { | ||
struct[names[i]] = Value.toObject(message["fields"][names[i]], options); | ||
} | ||
return struct; | ||
} | ||
|
||
return this.toObject(message, options); | ||
} | ||
}; | ||
|
||
// custom wrapper for google.protobuf.Duration | ||
wrappers[".google.protobuf.Duration"] = { | ||
fromObject: function(object) { | ||
var match; | ||
if (typeof object === "string") { | ||
// whole seconds | ||
match = object.match(/^(\d+)s$/); | ||
if (match) { | ||
return this.create({ | ||
seconds: Number(match[1]), | ||
nanos: 0 | ||
}); | ||
} | ||
// fractional seconds | ||
match = object.match(/^(\d*)\.(\d+)s$/); | ||
if (match) { | ||
var nanos = match[2]; | ||
// pad trailing zeros; cannot use .padEnd since it will break old versions | ||
while (nanos.length < 9) { | ||
nanos = nanos + "0"; | ||
} | ||
return this.create({ | ||
seconds: match[1].length > 0 ? Number(match[1]) : 0, | ||
nanos: Number(nanos) | ||
}); | ||
} | ||
} | ||
return this.fromObject(object); | ||
}, | ||
|
||
toObject: function(message, options) { | ||
if (options && options.json && options.values) { | ||
var durationSeconds = message.seconds; | ||
if (message.nanos > 0) { | ||
var nanosStr = String(message.nanos); | ||
// add leading zeros; cannot use .padStart since it will break old versions | ||
while (nanosStr.length < 9) { | ||
nanosStr = "0" + nanosStr; | ||
} | ||
// nanosStr should contain 3, 6, or 9 fractional digits. | ||
nanosStr = nanosStr.replace(/^((?:\d\d\d)+?)(?:0*)$/, "$1"); | ||
durationSeconds += "." + nanosStr; | ||
} | ||
durationSeconds += "s"; | ||
return durationSeconds; | ||
} | ||
|
||
return this.toObject(message, options); | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
var tape = require("tape"); | ||
var long = require("long"); | ||
|
||
var protobuf = require(".."); | ||
|
||
var root = protobuf.Root.fromJSON({ | ||
nested: { | ||
test: { | ||
nested: { | ||
Test: { | ||
fields: { | ||
value: { | ||
type: "google.protobuf.Duration", | ||
id: 1 | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}).addJSON(protobuf.common["google/protobuf/duration.proto"].nested).resolveAll(); | ||
|
||
var Test = root.lookupType("test.Test"); | ||
|
||
tape.test("google.protobuf.Duration", function(test) { | ||
// examples from google/protobuf/duration.proto | ||
var integerDuration = {value: "3s"}; | ||
var fractionalDuration1 = {value: "3.000000001s"}; | ||
var fractionalDuration2 = {value: "3.000001s"}; | ||
var regularDuration = { | ||
value: { | ||
seconds: long.fromNumber(4), | ||
nanos: 2 | ||
} | ||
}; | ||
|
||
var integerDurationMessage = Test.fromObject(integerDuration); | ||
var fractionalDuration1Message = Test.fromObject(fractionalDuration1); | ||
var fractionalDuration2Message = Test.fromObject(fractionalDuration2); | ||
var regularDurationMessage = Test.fromObject(regularDuration); | ||
|
||
test.same(integerDurationMessage, { | ||
value: { | ||
seconds: 3, | ||
nanos: 0 | ||
} | ||
}, "toObject should understand integer seconds as string"); | ||
test.same(fractionalDuration1Message, { | ||
value: { | ||
seconds: 3, | ||
nanos: 1 | ||
} | ||
}, "toObject should understand fractional seconds as string 1"); | ||
test.same(fractionalDuration2Message, { | ||
value: { | ||
seconds: 3, | ||
nanos: 1000 | ||
} | ||
}, "toObject should understand fractional seconds as string 2"); | ||
test.same(regularDurationMessage, { | ||
value: { | ||
seconds: {low: 4, high: 0, unsigned: false}, | ||
nanos: 2 | ||
} | ||
}, "toObject should understand regular Duration message"); | ||
|
||
test.same(Test.toObject(integerDurationMessage, {json: true, values: true}), {value: "3s"}, "toObject should produce integer seconds"); | ||
test.same(Test.toObject(fractionalDuration1Message, {json: true, values: true}), {value: "3.000000001s"}, "toObject should produce fractional seconds 1"); | ||
test.same(Test.toObject(fractionalDuration2Message, {json: true, values: true}), {value: "3.000001s"}, "toObject should produce fractional seconds 2"); | ||
test.same(Test.toObject(regularDurationMessage, {json: true, values: true}), {value: "4.000000002s"}, "toObject should produce fractional seconds 3"); | ||
test.same(Test.toObject(regularDurationMessage), regularDurationMessage, "toObject should produce a regular Duration object by default"); | ||
|
||
test.same(Test.toObject({ value: { seconds: 0, nanos: 100000000 }}, {json: true, values: true}), { value: "0.100s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 10000000 }}, {json: true, values: true}), { value: "0.010s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 1000000 }}, {json: true, values: true}), { value: "0.001s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 100000 }}, {json: true, values: true}), { value: "0.000100s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 10000 }}, {json: true, values: true}), { value: "0.000010s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 1000 }}, {json: true, values: true}), { value: "0.000001s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 100 }}, {json: true, values: true}), { value: "0.000000100s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 10 }}, {json: true, values: true}), { value: "0.000000010s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 1 }}, {json: true, values: true}), { value: "0.000000001s"}, "toObject string should contain 3, 6, or 9 fractional digits 1"); | ||
test.same(Test.toObject({ value: { seconds: 0, nanos: 0 }}, {json: true, values: true}), { value: "0s"}, "toObject string is valid for zero duration"); | ||
|
||
test.end(); | ||
}); |
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.