Skip to content

Commit

Permalink
Custom to, from object for google.protobuf Timestamp and Duration
Browse files Browse the repository at this point in the history
* Support Date or date string for Timestamp in fromObject

* Conversion to JSON in toObject to Timestamp outputs a Date object

* Support parsing simple duration strings, eg 5s or 1m, to Duration.
  Fractions of a second are not supported, eg 1.05s

* Conversion to JSON in toObject for Duration outputs a string with
  the second unit only if the nanos component is zero
  • Loading branch information
johncsnyder committed Sep 21, 2019
1 parent e1bb46c commit a6eddb4
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 41 deletions.
64 changes: 64 additions & 0 deletions examples/well-known-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// this example demonstrates how to use well known types.

/*eslint-disable strict, no-console*/
var protobuf = require("..");

var root = protobuf.Root.fromJSON({
nested: {
google: {
nested: {
protobuf: {
nested: {
Timestamp: {
fields: {
seconds: {
type: "int64",
id: 1
},
nanos: {
type: "int32",
id: 2
}
}
},
Duration: {
fields: {
seconds: {
type: "int64",
id: 1
},
nanos: {
type: "int32",
id: 2
}
}
}
}
}
}
},
Message: {
fields: {
timestamp: {
type: "google.protobuf.Timestamp",
id: 1
},
duration: {
type: "google.protobuf.Duration",
id: 2
}
}
}
}
});

var Message = root.lookup("Message");

const msg = Message.fromObject({
timestamp: new Date("2019-01-01"),
duration: "5m",
});

console.log(msg);

console.log(msg.toJSON());
37 changes: 10 additions & 27 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
var converter = exports;

var Enum = require("./enum"),
util = require("./util");
util = require("./util"),
wrappers = require("./wrappers");

/**
* Generates a partial value fromObject conveter.
Expand All @@ -19,7 +20,11 @@ var Enum = require("./enum"),
*/
function genValuePartial_fromObject(gen, field, fieldIndex, prop) {
/* eslint-disable no-unexpected-multiline, block-scoped-var, no-redeclare */
if (field.resolvedType) {
// console.log("genValuePartial_fromObject resolvedType", field.resolvedType)
if (field.resolvedType && (field.resolvedType.fullName == ".google.protobuf.Timestamp" || field.resolvedType.fullName == ".google.protobuf.Duration") ) {
gen
("m%s=types[%i].fromObject(d%s)", prop, fieldIndex, prop);
} else if (field.resolvedType) {
if (field.resolvedType instanceof Enum) { gen
("switch(d%s){", prop);
for (var values = field.resolvedType.values, keys = Object.keys(values), i = 0; i < keys.length; ++i) {
Expand Down
79 changes: 67 additions & 12 deletions src/wrappers.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,16 @@ var Message = require("./message");

// Custom wrapper for Any
wrappers[".google.protobuf.Any"] = {

fromObject: function(object) {

// unwrap value type if mapped
if (object && object["@type"]) {
// Only use fully qualified type name after the last '/'
// Only use fully qualified type name after the last '/'
var name = object["@type"].substring(object["@type"].lastIndexOf("/") + 1);
var type = this.lookup(name);
/* istanbul ignore else */
if (type) {
// type_url does not accept leading "."
var type_url = object["@type"].charAt(0) === "." ?
object["@type"].substr(1) : object["@type"];
var type_url = object["@type"].charAt(0) === "." ? object["@type"].substr(1) : object["@type"];
// type_url prefix is optional, but path seperator is required
if (type_url.indexOf("/") === -1) {
type_url = "/" + type_url;
Expand All @@ -60,12 +57,11 @@ wrappers[".google.protobuf.Any"] = {
});
}
}

return this.fromObject(object);
},

toObject: function(message, options) {

// Default prefix
var googleApi = "type.googleapis.com/";
var prefix = "";
Expand All @@ -75,18 +71,17 @@ wrappers[".google.protobuf.Any"] = {
// Only use fully qualified type name after the last '/'
var name = message.type_url.substring(message.type_url.lastIndexOf("/") + 1);
// Separate the prefix used
prefix = message.type_url.substring(0, message.type_url.lastIndexOf('/') + 1);
prefix = message.type_url.substring(0, message.type_url.lastIndexOf("/") + 1);
var type = this.lookup(name);
/* istanbul ignore else */
if (type)
message = type.decode(message.value);
if (type) message = type.decode(message.value);
}

// wrap value if unmapped
if (!(message instanceof this.ctor) && message instanceof Message) {
var object = message.$type.toObject(message, options);
var messageName = message.$type.fullName[0] === "." ?
message.$type.fullName.substr(1) : message.$type.fullName;
var messageName =
message.$type.fullName[0] === "." ? message.$type.fullName.substr(1) : message.$type.fullName;
// Default to type.googleapis.com prefix if no prefix is used
if (prefix === "") {
prefix = googleApi;
Expand All @@ -99,3 +94,63 @@ wrappers[".google.protobuf.Any"] = {
return this.toObject(message, options);
}
};

// Custom wrapper for Timestamp
wrappers[".google.protobuf.Timestamp"] = {
fromObject: function(object) {
if (typeof object === "string") {
const ts = new Date(object);
const seconds = Math.floor(ts.getTime() / 1000);
const nanos = ts.getMilliseconds() * 1000000;
return this.create({
seconds: seconds,
nanos: nanos
});
} else if (object instanceof Date) {
const seconds = Math.floor(object.getTime() / 1000);
const nanos = object.getMilliseconds() * 1000000;
return this.create({
seconds: seconds,
nanos: nanos
});
}

return this.fromObject(object);
},

toObject: function(message, options) {
if (options && options.json) {
return new Date(message.seconds * 1000 + message.nanos / 1000000);
}
return this.toObject(message, options);
}
};

// Custom wrapper for Duration
wrappers[".google.protobuf.Duration"] = {
fromObject: function(object) {
if (typeof object === "string") {
const unit = function() {
if (object.slice(-1) == "s") return 1;
if (object.slice(-1) == "m") return 60;
if (object.slice(-1) == "h") return 60 * 60;
if (object.slice(-1) == "d") return 60 * 60 * 24;
throw new Error("invalid duration unit : must be one of s, m, h, or d")
}();
const value = parseInt(object.slice(0,-1));
const seconds = value * unit;
return this.create({
seconds: seconds,
nanos: 0,
});
}
return this.fromObject(object);
},

toObject: function(message, options) {
if (options && options.json && message.nanos == 0) {
return message.seconds.toString() + "s"
}
return this.toObject(message, options);
}
};

0 comments on commit a6eddb4

Please sign in to comment.