diff --git a/examples/well-known-types.js b/examples/well-known-types.js new file mode 100644 index 000000000..e2e50c1d6 --- /dev/null +++ b/examples/well-known-types.js @@ -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()); diff --git a/package-lock.json b/package-lock.json index 42affcddc..2a795b23b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2657,8 +2657,7 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2682,15 +2681,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2700,22 +2697,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2836,8 +2830,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -2851,7 +2844,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2868,7 +2860,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2974,8 +2965,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2989,7 +2979,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3085,8 +3074,7 @@ "version": "5.1.1", "resolved": false, "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3128,7 +3116,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3150,7 +3137,6 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3183,8 +3169,7 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true } } }, @@ -4935,7 +4920,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -4945,8 +4929,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, diff --git a/src/converter.js b/src/converter.js index 44d952e61..ec0a429db 100644 --- a/src/converter.js +++ b/src/converter.js @@ -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. @@ -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) { diff --git a/src/wrappers.js b/src/wrappers.js index aad96c785..795db19fa 100644 --- a/src/wrappers.js +++ b/src/wrappers.js @@ -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; @@ -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 = ""; @@ -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; @@ -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); + } +};