@@ -32,7 +32,7 @@ import type {
3232import { reflect } from "./reflect/reflect.js" ;
3333import { FieldError , isFieldError } from "./reflect/error.js" ;
3434import { formatVal } from "./reflect/reflect-check.js" ;
35- import { type ScalarValue , scalarZeroValue } from "./reflect/scalar.js" ;
35+ import type { ScalarValue } from "./reflect/scalar.js" ;
3636import type { EnumJsonType , EnumShape , MessageShape } from "./types.js" ;
3737import { base64Decode } from "./wire/base64-encoding.js" ;
3838import type {
@@ -176,11 +176,7 @@ export function enumFromJson<Desc extends DescEnum>(
176176 descEnum : Desc ,
177177 json : EnumJsonType < Desc > ,
178178) : EnumShape < Desc > {
179- const val = readEnum ( descEnum , json , false , false ) ;
180- if ( val === tokenIgnoredUnknownEnum ) {
181- throw new Error ( `cannot decode ${ descEnum } from JSON: ${ formatVal ( json ) } ` ) ;
182- }
183- return val as EnumShape < Desc > ;
179+ return readEnum ( descEnum , json , false ) as EnumShape < Desc > ;
184180}
185181
186182/**
@@ -276,6 +272,30 @@ function readField(
276272 }
277273}
278274
275+ function readListOrMapItem (
276+ field : DescField & { fieldKind : "map" | "list" } ,
277+ json : JsonValue ,
278+ opts : JsonReadOptions ,
279+ ) {
280+ if ( field . scalar && json !== null ) {
281+ return scalarFromJson ( field , json ) ;
282+ }
283+ if ( field . message && ! isResetSentinelNullValue ( field , json ) ) {
284+ const msgValue = reflect ( field . message ) ;
285+ readMessage ( msgValue , json , opts ) ;
286+
287+ return msgValue ;
288+ }
289+ if ( field . enum && ! isResetSentinelNullValue ( field , json ) ) {
290+ return readEnum ( field . enum , json , opts . ignoreUnknownFields ) ;
291+ }
292+
293+ throw new FieldError (
294+ field ,
295+ `${ field . fieldKind === "list" ? "list item" : "map value" } must not be null` ,
296+ ) ;
297+ }
298+
279299function readMapField ( map : ReflectMap , json : JsonValue , opts : JsonReadOptions ) {
280300 if ( json === null ) {
281301 return ;
@@ -285,33 +305,11 @@ function readMapField(map: ReflectMap, json: JsonValue, opts: JsonReadOptions) {
285305 throw new FieldError ( field , "expected object, got " + formatVal ( json ) ) ;
286306 }
287307 for ( const [ jsonMapKey , jsonMapValue ] of Object . entries ( json ) ) {
288- if ( jsonMapValue === null && ! isSafeNullValueInListOrMap ( field ) ) {
289- throw new FieldError ( field , "map value must not be null" ) ;
290- }
291- let value : unknown ;
292- switch ( field . mapKind ) {
293- case "message" :
294- const msgValue = reflect ( field . message ) ;
295- readMessage ( msgValue , jsonMapValue , opts ) ;
296- value = msgValue ;
297- break ;
298- case "enum" :
299- value = readEnum (
300- field . enum ,
301- jsonMapValue ,
302- opts . ignoreUnknownFields ,
303- true ,
304- ) ;
305- if ( value === tokenIgnoredUnknownEnum ) {
306- return ;
307- }
308- break ;
309- case "scalar" :
310- value = scalarFromJson ( field , jsonMapValue , true ) ;
311- break ;
312- }
313308 const key = mapKeyFromJson ( field . mapKey , jsonMapKey ) ;
314- map . set ( key , value ) ;
309+ const value = readListOrMapItem ( field , jsonMapValue , opts ) ;
310+ if ( value !== tokenIgnoredUnknownEnum ) {
311+ map . set ( key , value ) ;
312+ }
315313 }
316314}
317315
@@ -328,49 +326,20 @@ function readListField(
328326 throw new FieldError ( field , "expected Array, got " + formatVal ( json ) ) ;
329327 }
330328 for ( const jsonItem of json ) {
331- if ( jsonItem === null && ! isSafeNullValueInListOrMap ( field ) ) {
332- throw new FieldError ( field , "list item must not be null" ) ;
333- }
334- switch ( field . listKind ) {
335- case "message" :
336- const msgValue = reflect ( field . message ) ;
337- readMessage ( msgValue , jsonItem , opts ) ;
338- list . add ( msgValue ) ;
339- break ;
340- case "enum" :
341- const enumValue = readEnum (
342- field . enum ,
343- jsonItem ,
344- opts . ignoreUnknownFields ,
345- true ,
346- ) ;
347- if ( enumValue !== tokenIgnoredUnknownEnum ) {
348- list . add ( enumValue ) ;
349- }
350- break ;
351- case "scalar" :
352- list . add ( scalarFromJson ( field , jsonItem , true ) ) ;
353- break ;
329+ const value = readListOrMapItem ( field , jsonItem , opts ) ;
330+ if ( value !== tokenIgnoredUnknownEnum ) {
331+ list . add ( value ) ;
354332 }
355333 }
356334}
357335
358- function isSafeNullValueInListOrMap (
359- field : DescField & { fieldKind : "map" | "list" } ,
360- ) : boolean {
361- return (
362- field . message ?. typeName == "google.protobuf.Value" ||
363- field . enum ?. typeName == "google.protobuf.NullValue"
364- ) ;
365- }
366-
367336function readMessageField (
368337 msg : ReflectMessage ,
369338 field : DescField & { fieldKind : "message" } ,
370339 json : JsonValue ,
371340 opts : JsonReadOptions ,
372341) {
373- if ( json === null && field . message . typeName != "google.protobuf.Value" ) {
342+ if ( isResetSentinelNullValue ( field , json ) ) {
374343 msg . clear ( field ) ;
375344 return ;
376345 }
@@ -385,10 +354,12 @@ function readEnumField(
385354 json : JsonValue ,
386355 opts : JsonReadOptions ,
387356) {
388- const enumValue = readEnum ( field . enum , json , opts . ignoreUnknownFields , false ) ;
389- if ( enumValue === tokenNull ) {
357+ if ( isResetSentinelNullValue ( field , json ) ) {
390358 msg . clear ( field ) ;
391- } else if ( enumValue !== tokenIgnoredUnknownEnum ) {
359+ return ;
360+ }
361+ const enumValue = readEnum ( field . enum , json , opts . ignoreUnknownFields ) ;
362+ if ( enumValue !== tokenIgnoredUnknownEnum ) {
392363 msg . set ( field , enumValue ) ;
393364 }
394365}
@@ -398,39 +369,59 @@ function readScalarField(
398369 field : DescField & { fieldKind : "scalar" } ,
399370 json : JsonValue ,
400371) {
401- const scalarValue = scalarFromJson ( field , json , false ) ;
402- if ( scalarValue === tokenNull ) {
372+ if ( json === null ) {
403373 msg . clear ( field ) ;
404374 } else {
405- msg . set ( field , scalarValue ) ;
375+ msg . set ( field , scalarFromJson ( field , json ) ) ;
406376 }
407377}
408378
379+ /**
380+ * Indicates whether a value is a sentinel for reseting a field.
381+ *
382+ * For this to be true, the value must be a JSON null and the field must not
383+ * permit a present, Protobuf-serializable null.
384+ *
385+ * Only message google.protobuf.Value and enum google.protobuf.NullValue fields
386+ * permit Protobuf-serializable nulls.
387+ *
388+ * Note that field-resetting sentinel nulls are not permitted in lists and maps.
389+ */
390+ function isResetSentinelNullValue (
391+ field : DescField & ( { message : DescMessage } | { enum : DescEnum } ) ,
392+ json : JsonValue ,
393+ ) : boolean {
394+ return (
395+ json === null &&
396+ field . message ?. typeName != "google.protobuf.Value" &&
397+ field . enum ?. typeName != "google.protobuf.NullValue"
398+ ) ;
399+ }
400+
409401const tokenIgnoredUnknownEnum = Symbol ( ) ;
410402
403+ /**
404+ * Try to parse a JSON value to an enum value. JSON null returns the enum's first value.
405+ * With ignoreUnknownFields false, unknown values raise a FieldError
406+ * With ignoreUnknownFields true, unknown values return tokenIgnoredUnknownEnum.
407+ */
411408function readEnum (
412409 desc : DescEnum ,
413410 json : JsonValue ,
414- ignoreUnknownFields : boolean ,
415- nullAsZeroValue : false ,
416- ) : number | typeof tokenIgnoredUnknownEnum | typeof tokenNull ;
411+ ignoreUnknownFields : false ,
412+ ) : number ;
417413function readEnum (
418414 desc : DescEnum ,
419415 json : JsonValue ,
420416 ignoreUnknownFields : boolean ,
421- nullAsZeroValue : true ,
422417) : number | typeof tokenIgnoredUnknownEnum ;
423418function readEnum (
424419 desc : DescEnum ,
425420 json : JsonValue ,
426421 ignoreUnknownFields : boolean ,
427- nullAsZeroValue : boolean ,
428- ) : number | typeof tokenNull | typeof tokenIgnoredUnknownEnum {
422+ ) : number | typeof tokenIgnoredUnknownEnum {
429423 if ( json === null ) {
430- if ( desc . typeName == "google.protobuf.NullValue" ) {
431- return 0 ; // google.protobuf.NullValue.NULL_VALUE = 0
432- }
433- return nullAsZeroValue ? desc . values [ 0 ] . number : tokenNull ;
424+ return desc . values [ 0 ] . number ;
434425 }
435426 switch ( typeof json ) {
436427 case "number" :
@@ -451,45 +442,16 @@ function readEnum(
451442 throw new Error ( `cannot decode ${ desc } from JSON: ${ formatVal ( json ) } ` ) ;
452443}
453444
454- const tokenNull = Symbol ( ) ;
455-
456445/**
457446 * Try to parse a JSON value to a scalar value for the reflect API.
458447 *
459448 * Returns the input if the JSON value cannot be converted. Raises a FieldError
460449 * if conversion would be ambiguous.
461- *
462- * JSON null returns the scalar zero value.
463450 */
464451function scalarFromJson (
465452 field : DescField & { scalar : ScalarType } ,
466- json : JsonValue ,
467- nullAsZeroValue : true ,
468- ) : ScalarValue ;
469- /**
470- * Try to parse a JSON value to a scalar value for the reflect API.
471- *
472- * Returns the input if the JSON value cannot be converted. Raises a FieldError
473- * if conversion would be ambiguous.
474- *
475- * JSON null returns the symbol `tokenNull`.
476- */
477- function scalarFromJson (
478- field : DescField & { scalar : ScalarType } ,
479- json : JsonValue ,
480- nullAsZeroValue : false ,
481- ) : ScalarValue | typeof tokenNull | JsonValue ;
482- function scalarFromJson (
483- field : DescField & { scalar : ScalarType } ,
484- json : JsonValue ,
485- nullAsZeroValue : boolean ,
486- ) : ScalarValue | typeof tokenNull | JsonValue {
487- if ( json === null ) {
488- if ( nullAsZeroValue ) {
489- return scalarZeroValue ( field . scalar , false ) ;
490- }
491- return tokenNull ;
492- }
453+ json : NonNullable < JsonValue > ,
454+ ) : ScalarValue | NonNullable < JsonValue > {
493455 // int64, sfixed64, sint64, fixed64, uint64: Reflect supports string and number.
494456 // string, bool: Supported by reflect.
495457 switch ( field . scalar ) {
@@ -566,25 +528,25 @@ function mapKeyFromJson(
566528 ScalarType ,
567529 ScalarType . BYTES | ScalarType . DOUBLE | ScalarType . FLOAT
568530 > ,
569- json : JsonValue ,
531+ jsonString : string ,
570532) {
571533 switch ( type ) {
572534 case ScalarType . BOOL :
573- switch ( json ) {
535+ switch ( jsonString ) {
574536 case "true" :
575537 return true ;
576538 case "false" :
577539 return false ;
578540 }
579- return json ;
541+ return jsonString ;
580542 case ScalarType . INT32 :
581543 case ScalarType . FIXED32 :
582544 case ScalarType . UINT32 :
583545 case ScalarType . SFIXED32 :
584546 case ScalarType . SINT32 :
585- return int32FromJson ( json ) ;
547+ return int32FromJson ( jsonString ) ;
586548 default :
587- return json ;
549+ return jsonString ;
588550 }
589551}
590552
@@ -593,7 +555,7 @@ function mapKeyFromJson(
593555 *
594556 * Returns the input if the JSON value cannot be converted.
595557 */
596- function int32FromJson ( json : JsonValue ) {
558+ function int32FromJson ( json : NonNullable < JsonValue > ) {
597559 if ( typeof json == "string" ) {
598560 if ( json === "" ) {
599561 // empty string is not a number
@@ -662,7 +624,7 @@ function tryWktFromJson(
662624 if ( jsonValue === null ) {
663625 msg . clear ( valueField ) ;
664626 } else {
665- msg . set ( valueField , scalarFromJson ( valueField , jsonValue , true ) ) ;
627+ msg . set ( valueField , scalarFromJson ( valueField , jsonValue ) ) ;
666628 }
667629 return true ;
668630 }
0 commit comments