1- #region Copyright notice and license
1+ #region Copyright notice and license
22// Protocol Buffers - Google's data interchange format
33// Copyright 2015 Google Inc. All rights reserved.
44//
@@ -219,8 +219,10 @@ private void MergeField(IMessage message, FieldDescriptor field, JsonTokenizer t
219219 }
220220 else
221221 {
222- var value = ParseSingleValue ( field , tokenizer ) ;
223- field . Accessor . SetValue ( message , value ) ;
222+ if ( TryParseSingleValue ( field , tokenizer , out var value ) )
223+ {
224+ field . Accessor . SetValue ( message , value ) ;
225+ }
224226 }
225227 }
226228
@@ -241,12 +243,14 @@ private void MergeRepeatedField(IMessage message, FieldDescriptor field, JsonTok
241243 return ;
242244 }
243245 tokenizer . PushBack ( token ) ;
244- object value = ParseSingleValue ( field , tokenizer ) ;
245- if ( value == null )
246+ if ( TryParseSingleValue ( field , tokenizer , out object value ) )
246247 {
247- throw new InvalidProtocolBufferException ( "Repeated field elements cannot be null" ) ;
248+ if ( value == null )
249+ {
250+ throw new InvalidProtocolBufferException ( "Repeated field elements cannot be null" ) ;
251+ }
252+ list . Add ( value ) ;
248253 }
249- list . Add ( value ) ;
250254 }
251255 }
252256
@@ -276,8 +280,10 @@ private void MergeMapField(IMessage message, FieldDescriptor field, JsonTokenize
276280 return ;
277281 }
278282 object key = ParseMapKey ( keyField , token . StringValue ) ;
279- object value = ParseSingleValue ( valueField , tokenizer ) ;
280- dictionary [ key ] = value ?? throw new InvalidProtocolBufferException ( "Map values must not be null" ) ;
283+ if ( TryParseSingleValue ( valueField , tokenizer , out object value ) )
284+ {
285+ dictionary [ key ] = value ?? throw new InvalidProtocolBufferException ( "Map values must not be null" ) ;
286+ }
281287 }
282288 }
283289
@@ -293,7 +299,15 @@ private static bool IsGoogleProtobufNullValueField(FieldDescriptor field)
293299 field . EnumType . FullName == NullValueDescriptor . FullName ;
294300 }
295301
296- private object ParseSingleValue ( FieldDescriptor field , JsonTokenizer tokenizer )
302+ /// <summary>
303+ /// Attempts to parse a single value from the JSON. When the value is completely invalid,
304+ /// this will still throw an exception; when it's "conditionally invalid" (currently meaning
305+ /// "when there's an unknown enum string value") the method returns false instead.
306+ /// </summary>
307+ /// <returns>
308+ /// true if the value was parsed successfully; false for an ignorable parse failure.
309+ /// </returns>
310+ private bool TryParseSingleValue ( FieldDescriptor field , JsonTokenizer tokenizer , out object value )
297311 {
298312 var token = tokenizer . Next ( ) ;
299313 if ( token . Type == JsonToken . TokenType . Null )
@@ -302,13 +316,17 @@ private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
302316 // dynamically.
303317 if ( IsGoogleProtobufValueField ( field ) )
304318 {
305- return Value . ForNull ( ) ;
319+ value = Value . ForNull ( ) ;
306320 }
307- if ( IsGoogleProtobufNullValueField ( field ) )
321+ else if ( IsGoogleProtobufNullValueField ( field ) )
308322 {
309- return NullValue . NullValue ;
323+ value = NullValue . NullValue ;
310324 }
311- return null ;
325+ else
326+ {
327+ value = null ;
328+ }
329+ return true ;
312330 }
313331
314332 var fieldType = field . FieldType ;
@@ -327,7 +345,8 @@ private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
327345 tokenizer . PushBack ( token ) ;
328346 IMessage subMessage = NewMessageForField ( field ) ;
329347 Merge ( subMessage , tokenizer ) ;
330- return subMessage ;
348+ value = subMessage ;
349+ return true ;
331350 }
332351 }
333352
@@ -337,18 +356,26 @@ private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
337356 case JsonToken . TokenType . False :
338357 if ( fieldType == FieldType . Bool )
339358 {
340- return token . Type == JsonToken . TokenType . True ;
359+ value = token . Type == JsonToken . TokenType . True ;
360+ return true ;
341361 }
342362 // Fall through to "we don't support this type for this case"; could duplicate the behaviour of the default
343363 // case instead, but this way we'd only need to change one place.
344364 goto default ;
345365 case JsonToken . TokenType . StringValue :
346- return ParseSingleStringValue ( field , token . StringValue ) ;
366+ if ( field . FieldType != FieldType . Enum )
367+ {
368+ value = ParseSingleStringValue ( field , token . StringValue ) ;
369+ return true ;
370+ }
371+ else
372+ {
373+ return TryParseEnumStringValue ( field , token . StringValue , out value ) ;
374+ }
347375 // Note: not passing the number value itself here, as we may end up storing the string value in the token too.
348376 case JsonToken . TokenType . Number :
349- return ParseSingleNumberValue ( field , token ) ;
350- case JsonToken . TokenType . Null :
351- throw new NotImplementedException ( "Haven't worked out what to do for null yet" ) ;
377+ value = ParseSingleNumberValue ( field , token ) ;
378+ return true ;
352379 default :
353380 throw new InvalidProtocolBufferException ( "Unsupported JSON token type " + token . Type + " for field type " + fieldType ) ;
354381 }
@@ -694,18 +721,32 @@ private static object ParseSingleStringValue(FieldDescriptor field, string text)
694721 ValidateInfinityAndNan ( text , float . IsPositiveInfinity ( f ) , float . IsNegativeInfinity ( f ) , float . IsNaN ( f ) ) ;
695722 return f ;
696723 case FieldType . Enum :
697- var enumValue = field . EnumType . FindValueByName ( text ) ;
698- if ( enumValue == null )
699- {
700- throw new InvalidProtocolBufferException ( $ "Invalid enum value: { text } for enum type: { field . EnumType . FullName } ") ;
701- }
702- // Just return it as an int, and let the CLR convert it.
703- return enumValue . Number ;
724+ throw new InvalidOperationException ( $ "Use TryParseEnumStringValue for enums") ;
704725 default :
705726 throw new InvalidProtocolBufferException ( $ "Unsupported conversion from JSON string for field type { field . FieldType } ") ;
706727 }
707728 }
708729
730+ private bool TryParseEnumStringValue ( FieldDescriptor field , string text , out object value )
731+ {
732+ var enumValue = field . EnumType . FindValueByName ( text ) ;
733+ if ( enumValue == null )
734+ {
735+ if ( settings . IgnoreUnknownFields )
736+ {
737+ value = null ;
738+ return false ;
739+ }
740+ else
741+ {
742+ throw new InvalidProtocolBufferException ( $ "Invalid enum value: { text } for enum type: { field . EnumType . FullName } ") ;
743+ }
744+ }
745+ // Just return it as an int, and let the CLR convert it.
746+ value = enumValue . Number ;
747+ return true ;
748+ }
749+
709750 /// <summary>
710751 /// Creates a new instance of the message type for the given field.
711752 /// </summary>
0 commit comments