Commit d1af0291 authored by Adam Cozzette's avatar Adam Cozzette

Fixed JS parsing of unspecified map keys

We need to use a default of 0 when parsing unspecified map keys, instead
of failing an assertion.

This change was written by Michael Aaron (michaelaaron@google.com) but I
am cherry-picking it directly instead of waiting for the next sync of
Google-internal changes.
parent 6f723a66
...@@ -443,7 +443,8 @@ jspb.Map.prototype.serializeBinary = function( ...@@ -443,7 +443,8 @@ jspb.Map.prototype.serializeBinary = function(
/** /**
* Read one key/value message from the given BinaryReader. Compatible as the * Read one key/value message from the given BinaryReader. Compatible as the
* `reader` callback parameter to jspb.BinaryReader.readMessage, to be called * `reader` callback parameter to jspb.BinaryReader.readMessage, to be called
* when a key/value pair submessage is encountered. * when a key/value pair submessage is encountered. If the Key is undefined,
* we should default it to 0.
* @template K, V * @template K, V
* @param {!jspb.Map} map * @param {!jspb.Map} map
* @param {!jspb.BinaryReader} reader * @param {!jspb.BinaryReader} reader
...@@ -457,12 +458,17 @@ jspb.Map.prototype.serializeBinary = function( ...@@ -457,12 +458,17 @@ jspb.Map.prototype.serializeBinary = function(
* readMessage, in which case the second callback arg form is used. * readMessage, in which case the second callback arg form is used.
* *
* @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback * @param {?function(V,!jspb.BinaryReader)=} opt_valueReaderCallback
* The BinaryReader parsing callback for type V, if V is a message type. * The BinaryReader parsing callback for type V, if V is a message type
*
* @param {K=} opt_defaultKey
* The default value for the type of map keys. Accepting map
* entries with unset keys is required for maps to be backwards compatible
* with the repeated message representation described here: goo.gl/zuoLAC
* *
*/ */
jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn, jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn,
opt_valueReaderCallback) { opt_valueReaderCallback, opt_defaultKey) {
var key = undefined; var key = opt_defaultKey;
var value = undefined; var value = undefined;
while (reader.nextField()) { while (reader.nextField()) {
...@@ -470,6 +476,7 @@ jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn, ...@@ -470,6 +476,7 @@ jspb.Map.deserializeBinary = function(map, reader, keyReaderFn, valueReaderFn,
break; break;
} }
var field = reader.getFieldNumber(); var field = reader.getFieldNumber();
if (field == 1) { if (field == 1) {
// Key. // Key.
key = keyReaderFn.call(reader); key = keyReaderFn.call(reader);
......
...@@ -35,6 +35,11 @@ goog.require('goog.userAgent'); ...@@ -35,6 +35,11 @@ goog.require('goog.userAgent');
goog.require('proto.jspb.test.MapValueEnum'); goog.require('proto.jspb.test.MapValueEnum');
goog.require('proto.jspb.test.MapValueMessage'); goog.require('proto.jspb.test.MapValueMessage');
goog.require('proto.jspb.test.TestMapFields'); goog.require('proto.jspb.test.TestMapFields');
goog.require('proto.jspb.test.TestMapFieldsOptionalKeys');
goog.require('proto.jspb.test.MapEntryOptionalKeysStringKey');
goog.require('proto.jspb.test.MapEntryOptionalKeysInt32Key');
goog.require('proto.jspb.test.MapEntryOptionalKeysInt64Key');
goog.require('proto.jspb.test.MapEntryOptionalKeysBoolKey');
// CommonJS-LoadFromFile: test_pb proto.jspb.test // CommonJS-LoadFromFile: test_pb proto.jspb.test
goog.require('proto.jspb.test.MapValueMessageNoBinary'); goog.require('proto.jspb.test.MapValueMessageNoBinary');
...@@ -76,7 +81,7 @@ function toArray(iter) { ...@@ -76,7 +81,7 @@ function toArray(iter) {
* Helper: generate test methods for this TestMapFields class. * Helper: generate test methods for this TestMapFields class.
* @param {?} msgInfo * @param {?} msgInfo
* @param {?} submessageCtor * @param {?} submessageCtor
* @param {!string} suffix * @param {string} suffix
*/ */
function makeTests(msgInfo, submessageCtor, suffix) { function makeTests(msgInfo, submessageCtor, suffix) {
/** /**
...@@ -260,6 +265,39 @@ function makeTests(msgInfo, submessageCtor, suffix) { ...@@ -260,6 +265,39 @@ function makeTests(msgInfo, submessageCtor, suffix) {
var decoded = msgInfo.deserializeBinary(serialized); var decoded = msgInfo.deserializeBinary(serialized);
checkMapFields(decoded); checkMapFields(decoded);
}); });
/**
* Tests deserialization of undefined map keys go to default values in binary format.
*/
it('testMapDeserializationForUndefinedKeys', function() {
var testMessageOptionalKeys = new proto.jspb.test.TestMapFieldsOptionalKeys();
var mapEntryStringKey = new proto.jspb.test.MapEntryOptionalKeysStringKey();
mapEntryStringKey.setValue("a");
testMessageOptionalKeys.setMapStringString(mapEntryStringKey);
var mapEntryInt32Key = new proto.jspb.test.MapEntryOptionalKeysInt32Key();
mapEntryInt32Key.setValue("b");
testMessageOptionalKeys.setMapInt32String(mapEntryInt32Key);
var mapEntryInt64Key = new proto.jspb.test.MapEntryOptionalKeysInt64Key();
mapEntryInt64Key.setValue("c");
testMessageOptionalKeys.setMapInt64String(mapEntryInt64Key);
var mapEntryBoolKey = new proto.jspb.test.MapEntryOptionalKeysBoolKey();
mapEntryBoolKey.setValue("d");
testMessageOptionalKeys.setMapBoolString(mapEntryBoolKey);
var deserializedMessage = msgInfo.deserializeBinary(
testMessageOptionalKeys.serializeBinary()
);
checkMapEquals(deserializedMessage.getMapStringStringMap(), [
['', 'a']
]);
checkMapEquals(deserializedMessage.getMapInt32StringMap(), [
[0, 'b']
]);
checkMapEquals(deserializedMessage.getMapInt64StringMap(), [
[0, 'c']
]);
checkMapEquals(deserializedMessage.getMapBoolStringMap(), [
[false, 'd']
]);
});
} }
......
...@@ -201,6 +201,38 @@ message TestMapFields { ...@@ -201,6 +201,38 @@ message TestMapFields {
map<string, TestMapFields> map_string_testmapfields = 12; map<string, TestMapFields> map_string_testmapfields = 12;
} }
// These proto are 'mock map' entries to test the above map deserializing with
// undefined keys. Make sure TestMapFieldsOptionalKeys is written to be
// deserialized by TestMapFields
message MapEntryOptionalKeysStringKey {
optional string key = 1;
optional string value = 2;
}
message MapEntryOptionalKeysInt32Key {
optional int32 key = 1;
optional string value = 2;
}
message MapEntryOptionalKeysInt64Key {
optional int64 key = 1;
optional string value = 2;
}
message MapEntryOptionalKeysBoolKey {
optional bool key = 1;
optional string value = 2;
}
message TestMapFieldsOptionalKeys {
optional MapEntryOptionalKeysStringKey map_string_string = 1;
optional MapEntryOptionalKeysInt32Key map_int32_string= 8;
optional MapEntryOptionalKeysInt64Key map_int64_string = 9;
optional MapEntryOptionalKeysBoolKey map_bool_string = 10;
}
// End mock-map entries
enum MapValueEnum { enum MapValueEnum {
MAP_VALUE_FOO = 0; MAP_VALUE_FOO = 0;
MAP_VALUE_BAR = 1; MAP_VALUE_BAR = 1;
......
...@@ -2962,8 +2962,12 @@ void Generator::GenerateClassDeserializeBinaryField( ...@@ -2962,8 +2962,12 @@ void Generator::GenerateClassDeserializeBinaryField(
if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) { if (value_field->type() == FieldDescriptor::TYPE_MESSAGE) {
printer->Print(", $messageType$.deserializeBinaryFromReader", printer->Print(", $messageType$.deserializeBinaryFromReader",
"messageType", GetMessagePath(options, value_field->message_type())); "messageType", GetMessagePath(options, value_field->message_type()));
} else {
printer->Print(", null");
} }
printer->Print(", $defaultKey$",
"defaultKey", JSFieldDefault(key_field)
);
printer->Print(");\n"); printer->Print(");\n");
printer->Print(" });\n"); printer->Print(" });\n");
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment