Skip to content

Commit

Permalink
Add support for longs: BigInt in toObject
Browse files Browse the repository at this point in the history
  • Loading branch information
jtbandes committed Aug 29, 2023
1 parent 4436cc7 commit 902b0fe
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 15 deletions.
2 changes: 1 addition & 1 deletion cli/package-lock.json

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

2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,7 @@ export interface IConversionOptions {

/**
* Long conversion type.
* Valid values are `String` and `Number` (the global types).
* Valid values are `BigInt`, `String`, and `Number` (the global types).
* Defaults to copy the present value, which is a possibly unsafe number without and a {@link Long} with a long library.
*/
longs?: Function;
Expand Down
8 changes: 6 additions & 2 deletions src/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,9 @@ function genValuePartial_toObject(gen, field, fieldIndex, prop) {
case "sint64":
case "fixed64":
case "sfixed64": gen
("if(typeof m%s===\"number\")", prop)
("if(typeof BigInt!==\"undefined\"&&o.longs===BigInt){")
("d%s=typeof m%s===\"number\"?BigInt(m%s):util._toBigInt(m%s.low,m%s.high,%j)", prop, prop, prop, prop, prop, isUnsigned)
("}else if(typeof m%s===\"number\")", prop)
("d%s=o.longs===String?String(m%s):m%s", prop, prop, prop)
("else") // Long-like
("d%s=o.longs===String?util.Long.prototype.toString.call(m%s):o.longs===Number?new util.LongBits(m%s.low>>>0,m%s.high>>>0).toNumber(%s):m%s", prop, prop, prop, prop, isUnsigned ? "true": "", prop);
Expand Down Expand Up @@ -247,7 +249,9 @@ converter.toObject = function toObject(mtype) {
if (field.resolvedType instanceof Enum) gen
("d%s=o.enums===String?%j:%j", prop, field.resolvedType.valuesById[field.typeDefault], field.typeDefault);
else if (field.long) gen
("if(util.Long){")
("if(typeof BigInt!==\"undefined\"&&o.longs===BigInt){")
("d%s=BigInt(%s)", prop, field.typeDefault.toString())
("}else if(util.Long){")
("var n=new util.Long(%i,%i,%j)", field.typeDefault.low, field.typeDefault.high, field.typeDefault.unsigned)
("d%s=o.longs===String?n.toString():o.longs===Number?n.toNumber():n", prop)
("}else")
Expand Down
2 changes: 1 addition & 1 deletion src/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ Type.prototype.fromObject = function fromObject(object) {
* Conversion options as used by {@link Type#toObject} and {@link Message.toObject}.
* @interface IConversionOptions
* @property {Function} [longs] Long conversion type.
* Valid values are `String` and `Number` (the global types).
* Valid values are `BigInt`, `String`, and `Number` (the global types).
* Defaults to copy the present value, which is a possibly unsafe number without and a {@link Long} with a long library.
* @property {Function} [enums] Enum value conversion type.
* Only valid value is `String` (the global type).
Expand Down
20 changes: 20 additions & 0 deletions src/util/minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,3 +436,23 @@ util._configure = function() {
return new Buffer(size);
};
};

// Used in converter for `longs: BigInt`
try {
let view = new DataView(new ArrayBuffer(8));
view.getBigInt64(0, true);
view.getBigUint64(0, true);
util._toBigInt = function(low, high, isUnsigned) {
view.setUint32(0, low, true);
view.setUint32(4, high, true);
return isUnsigned ? view.getBigUint64(0, true) : view.getBigInt64(0, true);
};
} catch (err) {
util._toBigInt = function(low, high, isUnsigned) {
const unsigned = (BigInt(high >>> 0) << BigInt(32)) | BigInt(low >>> 0);
if (!isUnsigned && (unsigned & BigInt(1) << BigInt(63))) {
return unsigned - (BigInt(1) << BigInt(64));
}
return unsigned;
};
}
32 changes: 22 additions & 10 deletions tests/api_converters.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tape.test("converters", function(test) {

test.test(test.name + " - Message#toObject", function(test) {

test.plan(6);
test.plan(7);

test.test(test.name + " - called with defaults = true", function(test) {
var obj = Message.toObject(Message.create(), { defaults: true });
Expand All @@ -34,6 +34,14 @@ tape.test("converters", function(test) {
test.end();
});

test.test(test.name + " - called with defaults = true and longs = bigint", function(test) {
var obj = Message.toObject(Message.create(), { defaults: true, longs: BigInt });

test.same(obj.uint64Val, 0n, "should set uint64Val");

test.end();
});

test.test(test.name + " - called with defaults = undefined", function(test) {
var obj = Message.toObject(Message.create());

Expand Down Expand Up @@ -98,28 +106,32 @@ tape.test("converters", function(test) {
var buf = protobuf.util.newBuffer(3);
buf[0] = buf[1] = buf[2] = 49; // "111"
var msg = Message.create({
uint64Val: protobuf.util.Long.fromNumber(1),
uint64Val: protobuf.util.Long.fromString("11000000000000000001", true),
uint64Repeated: [2, 3],
bytesVal: buf,
bytesRepeated: [buf, buf],
enumVal: 2,
enumRepeated: [1, 100, 2],
int64Map: {
a: protobuf.util.Long.fromNumber(2),
b: protobuf.util.Long.fromNumber(3)
a: protobuf.util.Long.fromString("-200000000000000001"),
b: protobuf.util.Long.fromString("-300000000000000001")
}
});

var msgLongsToNumber = Message.toObject(msg, { longs: Number }),
msgLongsToString = Message.toObject(msg, { longs: String });
msgLongsToString = Message.toObject(msg, { longs: String }),
msgLongsToBigInt = Message.toObject(msg, { longs: BigInt });

test.same(Message.ctor.toObject(msg, { longs: Number}), msgLongsToNumber, "should convert the same using the static and the instance method");
test.same(Message.ctor.toObject(msg, { longs: String}), msgLongsToString, "should convert the same using the static and the instance method");

test.equal(msgLongsToNumber.uint64Val, 1, "longs to numbers");
test.equal(msgLongsToString.uint64Val, "1", "longs to strings");
test.same(msgLongsToNumber.int64Map, { a: 2, b: 3}, "long map values to numbers");
test.same(msgLongsToString.int64Map, { a: "2", b: "3"}, "long map values to strings");
test.same(Message.ctor.toObject(msg, { longs: BigInt}), msgLongsToBigInt, "should convert the same using the static and the instance method");

test.equal(msgLongsToNumber.uint64Val, 11000000000000000000, "longs to numbers");
test.equal(msgLongsToString.uint64Val, "11000000000000000001", "longs to strings");
test.equal(msgLongsToBigInt.uint64Val, 11000000000000000001n, "longs to bigints");
test.same(msgLongsToNumber.int64Map, { a: -200000000000000000, b: -300000000000000000}, "long map values to numbers");
test.same(msgLongsToString.int64Map, { a: "-200000000000000001", b: "-300000000000000001"}, "long map values to strings");
test.same(msgLongsToBigInt.int64Map, { a: -200000000000000001n, b: -300000000000000001n}, "long map values to bigints");

test.equal(Object.prototype.toString.call(Message.toObject(msg, { bytes: Array }).bytesVal), "[object Array]", "bytes to arrays");
test.equal(Message.toObject(msg, { bytes: String }).bytesVal, "MTEx", "bytes to base64 strings");
Expand Down

0 comments on commit 902b0fe

Please sign in to comment.