Fix JSON parsing and formatting of NullValue in C#

The NullValue enum is typically used as part of the Value well-known type, but can be used elsewhere. In previous code, a NullValue field (other than in Value) would result in a JSON value of the string "NULL_VALUE"; it should instead be the null literal.
When parsing, we still accept "NULL_VALUE" as a valid value for NullValue - this new code being unable to parse old data, and comes for free anyway.
pull/7660/head
Jon Skeet 5 years ago committed by Jon Skeet
parent 206b973afd
commit 0dd5fffba3
  1. 11
      csharp/protos/unittest_issues.proto
  2. 284
      csharp/src/Google.Protobuf.Test.TestProtos/UnittestIssues.cs
  3. 7
      csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
  4. 20
      csharp/src/Google.Protobuf.Test/JsonParserTest.cs
  5. BIN
      csharp/src/Google.Protobuf.Test/testprotos.pb
  6. 2
      csharp/src/Google.Protobuf/JsonFormatter.cs
  7. 17
      csharp/src/Google.Protobuf/JsonParser.cs

@ -8,6 +8,8 @@ option csharp_namespace = "UnitTest.Issues.TestProtos";
package unittest_issues;
import "google/protobuf/struct.proto";
// Issue 307: when generating doubly-nested types, any references
// should be of the form A.Types.B.Types.C.
message Issue307 {
@ -137,4 +139,11 @@ message OneofMerging {
string text = 1;
Nested nested = 2;
}
}
}
message NullValueOutsideStruct {
oneof value {
string string_value = 1;
google.protobuf.NullValue null_value = 2;
}
}

@ -24,36 +24,39 @@ namespace UnitTest.Issues.TestProtos {
static UnittestIssuesReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"ChV1bml0dGVzdF9pc3N1ZXMucHJvdG8SD3VuaXR0ZXN0X2lzc3VlcyInCghJ",
"c3N1ZTMwNxobCgpOZXN0ZWRPbmNlGg0KC05lc3RlZFR3aWNlIrABChNOZWdh",
"dGl2ZUVudW1NZXNzYWdlEiwKBXZhbHVlGAEgASgOMh0udW5pdHRlc3RfaXNz",
"dWVzLk5lZ2F0aXZlRW51bRIxCgZ2YWx1ZXMYAiADKA4yHS51bml0dGVzdF9p",
"c3N1ZXMuTmVnYXRpdmVFbnVtQgIQABI4Cg1wYWNrZWRfdmFsdWVzGAMgAygO",
"Mh0udW5pdHRlc3RfaXNzdWVzLk5lZ2F0aXZlRW51bUICEAEiEQoPRGVwcmVj",
"YXRlZENoaWxkIrkCChdEZXByZWNhdGVkRmllbGRzTWVzc2FnZRIaCg5Qcmlt",
"aXRpdmVWYWx1ZRgBIAEoBUICGAESGgoOUHJpbWl0aXZlQXJyYXkYAiADKAVC",
"AhgBEjoKDE1lc3NhZ2VWYWx1ZRgDIAEoCzIgLnVuaXR0ZXN0X2lzc3Vlcy5E",
"ZXByZWNhdGVkQ2hpbGRCAhgBEjoKDE1lc3NhZ2VBcnJheRgEIAMoCzIgLnVu",
"aXR0ZXN0X2lzc3Vlcy5EZXByZWNhdGVkQ2hpbGRCAhgBEjYKCUVudW1WYWx1",
"ZRgFIAEoDjIfLnVuaXR0ZXN0X2lzc3Vlcy5EZXByZWNhdGVkRW51bUICGAES",
"NgoJRW51bUFycmF5GAYgAygOMh8udW5pdHRlc3RfaXNzdWVzLkRlcHJlY2F0",
"ZWRFbnVtQgIYASIZCglJdGVtRmllbGQSDAoEaXRlbRgBIAEoBSJECg1SZXNl",
"cnZlZE5hbWVzEg0KBXR5cGVzGAEgASgFEhIKCmRlc2NyaXB0b3IYAiABKAUa",
"EAoOU29tZU5lc3RlZFR5cGUioAEKFVRlc3RKc29uRmllbGRPcmRlcmluZxIT",
"CgtwbGFpbl9pbnQzMhgEIAEoBRITCglvMV9zdHJpbmcYAiABKAlIABISCghv",
"MV9pbnQzMhgFIAEoBUgAEhQKDHBsYWluX3N0cmluZxgBIAEoCRISCghvMl9p",
"bnQzMhgGIAEoBUgBEhMKCW8yX3N0cmluZxgDIAEoCUgBQgQKAm8xQgQKAm8y",
"IksKDFRlc3RKc29uTmFtZRIMCgRuYW1lGAEgASgJEhkKC2Rlc2NyaXB0aW9u",
"GAIgASgJUgRkZXNjEhIKBGd1aWQYAyABKAlSBGV4aWQifwoMT25lb2ZNZXJn",
"aW5nEg4KBHRleHQYASABKAlIABI2CgZuZXN0ZWQYAiABKAsyJC51bml0dGVz",
"dF9pc3N1ZXMuT25lb2ZNZXJnaW5nLk5lc3RlZEgAGh4KBk5lc3RlZBIJCgF4",
"GAEgASgFEgkKAXkYAiABKAVCBwoFdmFsdWUqVQoMTmVnYXRpdmVFbnVtEhYK",
"Ek5FR0FUSVZFX0VOVU1fWkVSTxAAEhYKCUZpdmVCZWxvdxD7//////////8B",
"EhUKCE1pbnVzT25lEP///////////wEqLgoORGVwcmVjYXRlZEVudW0SEwoP",
"REVQUkVDQVRFRF9aRVJPEAASBwoDb25lEAFCHaoCGlVuaXRUZXN0Lklzc3Vl",
"cy5UZXN0UHJvdG9zYgZwcm90bzM="));
"ChV1bml0dGVzdF9pc3N1ZXMucHJvdG8SD3VuaXR0ZXN0X2lzc3VlcxocZ29v",
"Z2xlL3Byb3RvYnVmL3N0cnVjdC5wcm90byInCghJc3N1ZTMwNxobCgpOZXN0",
"ZWRPbmNlGg0KC05lc3RlZFR3aWNlIrABChNOZWdhdGl2ZUVudW1NZXNzYWdl",
"EiwKBXZhbHVlGAEgASgOMh0udW5pdHRlc3RfaXNzdWVzLk5lZ2F0aXZlRW51",
"bRIxCgZ2YWx1ZXMYAiADKA4yHS51bml0dGVzdF9pc3N1ZXMuTmVnYXRpdmVF",
"bnVtQgIQABI4Cg1wYWNrZWRfdmFsdWVzGAMgAygOMh0udW5pdHRlc3RfaXNz",
"dWVzLk5lZ2F0aXZlRW51bUICEAEiEQoPRGVwcmVjYXRlZENoaWxkIrkCChdE",
"ZXByZWNhdGVkRmllbGRzTWVzc2FnZRIaCg5QcmltaXRpdmVWYWx1ZRgBIAEo",
"BUICGAESGgoOUHJpbWl0aXZlQXJyYXkYAiADKAVCAhgBEjoKDE1lc3NhZ2VW",
"YWx1ZRgDIAEoCzIgLnVuaXR0ZXN0X2lzc3Vlcy5EZXByZWNhdGVkQ2hpbGRC",
"AhgBEjoKDE1lc3NhZ2VBcnJheRgEIAMoCzIgLnVuaXR0ZXN0X2lzc3Vlcy5E",
"ZXByZWNhdGVkQ2hpbGRCAhgBEjYKCUVudW1WYWx1ZRgFIAEoDjIfLnVuaXR0",
"ZXN0X2lzc3Vlcy5EZXByZWNhdGVkRW51bUICGAESNgoJRW51bUFycmF5GAYg",
"AygOMh8udW5pdHRlc3RfaXNzdWVzLkRlcHJlY2F0ZWRFbnVtQgIYASIZCglJ",
"dGVtRmllbGQSDAoEaXRlbRgBIAEoBSJECg1SZXNlcnZlZE5hbWVzEg0KBXR5",
"cGVzGAEgASgFEhIKCmRlc2NyaXB0b3IYAiABKAUaEAoOU29tZU5lc3RlZFR5",
"cGUioAEKFVRlc3RKc29uRmllbGRPcmRlcmluZxITCgtwbGFpbl9pbnQzMhgE",
"IAEoBRITCglvMV9zdHJpbmcYAiABKAlIABISCghvMV9pbnQzMhgFIAEoBUgA",
"EhQKDHBsYWluX3N0cmluZxgBIAEoCRISCghvMl9pbnQzMhgGIAEoBUgBEhMK",
"CW8yX3N0cmluZxgDIAEoCUgBQgQKAm8xQgQKAm8yIksKDFRlc3RKc29uTmFt",
"ZRIMCgRuYW1lGAEgASgJEhkKC2Rlc2NyaXB0aW9uGAIgASgJUgRkZXNjEhIK",
"BGd1aWQYAyABKAlSBGV4aWQifwoMT25lb2ZNZXJnaW5nEg4KBHRleHQYASAB",
"KAlIABI2CgZuZXN0ZWQYAiABKAsyJC51bml0dGVzdF9pc3N1ZXMuT25lb2ZN",
"ZXJnaW5nLk5lc3RlZEgAGh4KBk5lc3RlZBIJCgF4GAEgASgFEgkKAXkYAiAB",
"KAVCBwoFdmFsdWUiawoWTnVsbFZhbHVlT3V0c2lkZVN0cnVjdBIWCgxzdHJp",
"bmdfdmFsdWUYASABKAlIABIwCgpudWxsX3ZhbHVlGAIgASgOMhouZ29vZ2xl",
"LnByb3RvYnVmLk51bGxWYWx1ZUgAQgcKBXZhbHVlKlUKDE5lZ2F0aXZlRW51",
"bRIWChJORUdBVElWRV9FTlVNX1pFUk8QABIWCglGaXZlQmVsb3cQ+///////",
"////ARIVCghNaW51c09uZRD///////////8BKi4KDkRlcHJlY2F0ZWRFbnVt",
"EhMKD0RFUFJFQ0FURURfWkVSTxAAEgcKA29uZRABQh2qAhpVbml0VGVzdC5J",
"c3N1ZXMuVGVzdFByb3Rvc2IGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.StructReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::UnitTest.Issues.TestProtos.NegativeEnum), typeof(global::UnitTest.Issues.TestProtos.DeprecatedEnum), }, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Issue307), global::UnitTest.Issues.TestProtos.Issue307.Parser, null, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce), global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce.Parser, null, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce.Types.NestedTwice), global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce.Types.NestedTwice.Parser, null, null, null, null, null)})}),
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.NegativeEnumMessage), global::UnitTest.Issues.TestProtos.NegativeEnumMessage.Parser, new[]{ "Value", "Values", "PackedValues" }, null, null, null, null),
@ -63,7 +66,8 @@ namespace UnitTest.Issues.TestProtos {
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ReservedNames), global::UnitTest.Issues.TestProtos.ReservedNames.Parser, new[]{ "Types_", "Descriptor_" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.ReservedNames.Types.SomeNestedType), global::UnitTest.Issues.TestProtos.ReservedNames.Types.SomeNestedType.Parser, null, null, null, null, null)}),
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.TestJsonFieldOrdering), global::UnitTest.Issues.TestProtos.TestJsonFieldOrdering.Parser, new[]{ "PlainInt32", "O1String", "O1Int32", "PlainString", "O2Int32", "O2String" }, new[]{ "O1", "O2" }, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.TestJsonName), global::UnitTest.Issues.TestProtos.TestJsonName.Parser, new[]{ "Name", "Description", "Guid" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofMerging), global::UnitTest.Issues.TestProtos.OneofMerging.Parser, new[]{ "Text", "Nested" }, new[]{ "Value" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofMerging.Types.Nested), global::UnitTest.Issues.TestProtos.OneofMerging.Types.Nested.Parser, new[]{ "X", "Y" }, null, null, null, null)})
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofMerging), global::UnitTest.Issues.TestProtos.OneofMerging.Parser, new[]{ "Text", "Nested" }, new[]{ "Value" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.OneofMerging.Types.Nested), global::UnitTest.Issues.TestProtos.OneofMerging.Types.Nested.Parser, new[]{ "X", "Y" }, null, null, null, null)}),
new pbr::GeneratedClrTypeInfo(typeof(global::UnitTest.Issues.TestProtos.NullValueOutsideStruct), global::UnitTest.Issues.TestProtos.NullValueOutsideStruct.Parser, new[]{ "StringValue", "NullValue" }, new[]{ "Value" }, null, null, null)
}));
}
#endregion
@ -2884,6 +2888,226 @@ namespace UnitTest.Issues.TestProtos {
}
public sealed partial class NullValueOutsideStruct : pb::IMessage<NullValueOutsideStruct>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<NullValueOutsideStruct> _parser = new pb::MessageParser<NullValueOutsideStruct>(() => new NullValueOutsideStruct());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pb::MessageParser<NullValueOutsideStruct> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public static pbr::MessageDescriptor Descriptor {
get { return global::UnitTest.Issues.TestProtos.UnittestIssuesReflection.Descriptor.MessageTypes[9]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public NullValueOutsideStruct() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public NullValueOutsideStruct(NullValueOutsideStruct other) : this() {
switch (other.ValueCase) {
case ValueOneofCase.StringValue:
StringValue = other.StringValue;
break;
case ValueOneofCase.NullValue:
NullValue = other.NullValue;
break;
}
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public NullValueOutsideStruct Clone() {
return new NullValueOutsideStruct(this);
}
/// <summary>Field number for the "string_value" field.</summary>
public const int StringValueFieldNumber = 1;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public string StringValue {
get { return valueCase_ == ValueOneofCase.StringValue ? (string) value_ : ""; }
set {
value_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
valueCase_ = ValueOneofCase.StringValue;
}
}
/// <summary>Field number for the "null_value" field.</summary>
public const int NullValueFieldNumber = 2;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public global::Google.Protobuf.WellKnownTypes.NullValue NullValue {
get { return valueCase_ == ValueOneofCase.NullValue ? (global::Google.Protobuf.WellKnownTypes.NullValue) value_ : global::Google.Protobuf.WellKnownTypes.NullValue.NullValue; }
set {
value_ = value;
valueCase_ = ValueOneofCase.NullValue;
}
}
private object value_;
/// <summary>Enum of possible cases for the "value" oneof.</summary>
public enum ValueOneofCase {
None = 0,
StringValue = 1,
NullValue = 2,
}
private ValueOneofCase valueCase_ = ValueOneofCase.None;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public ValueOneofCase ValueCase {
get { return valueCase_; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void ClearValue() {
valueCase_ = ValueOneofCase.None;
value_ = null;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override bool Equals(object other) {
return Equals(other as NullValueOutsideStruct);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public bool Equals(NullValueOutsideStruct other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (StringValue != other.StringValue) return false;
if (NullValue != other.NullValue) return false;
if (ValueCase != other.ValueCase) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override int GetHashCode() {
int hash = 1;
if (valueCase_ == ValueOneofCase.StringValue) hash ^= StringValue.GetHashCode();
if (valueCase_ == ValueOneofCase.NullValue) hash ^= NullValue.GetHashCode();
hash ^= (int) valueCase_;
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void WriteTo(pb::CodedOutputStream output) {
if (valueCase_ == ValueOneofCase.StringValue) {
output.WriteRawTag(10);
output.WriteString(StringValue);
}
if (valueCase_ == ValueOneofCase.NullValue) {
output.WriteRawTag(16);
output.WriteEnum((int) NullValue);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public int CalculateSize() {
int size = 0;
if (valueCase_ == ValueOneofCase.StringValue) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(StringValue);
}
if (valueCase_ == ValueOneofCase.NullValue) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) NullValue);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(NullValueOutsideStruct other) {
if (other == null) {
return;
}
switch (other.ValueCase) {
case ValueOneofCase.StringValue:
StringValue = other.StringValue;
break;
case ValueOneofCase.NullValue:
NullValue = other.NullValue;
break;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 10: {
StringValue = input.ReadString();
break;
}
case 16: {
value_ = input.ReadEnum();
valueCase_ = ValueOneofCase.NullValue;
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 10: {
StringValue = input.ReadString();
break;
}
case 16: {
value_ = input.ReadEnum();
valueCase_ = ValueOneofCase.NullValue;
break;
}
}
}
}
#endif
}
#endregion
}

@ -226,6 +226,13 @@ namespace Google.Protobuf
JsonFormatter.Default.Format(new TestMap { MapBoolBool = { { false, true }, { true, false } } }));
}
[Test]
public void NullValueOutsideStruct()
{
var message = new NullValueOutsideStruct { NullValue = NullValue.NullValue };
AssertJson("{ 'nullValue': null }", JsonFormatter.Default.Format(message));
}
[TestCase(1.0, "1")]
[TestCase(double.NaN, "'NaN'")]
[TestCase(double.PositiveInfinity, "'Infinity'")]

@ -36,6 +36,7 @@ using Google.Protobuf.WellKnownTypes;
using NUnit.Framework;
using ProtobufTestMessages.Proto2;
using System;
using UnitTest.Issues.TestProtos;
namespace Google.Protobuf
{
@ -974,6 +975,25 @@ namespace Google.Protobuf
Assert.AreEqual(expected, actual);
}
[Test]
public void NullValueOutsideStruct_NullLiteral()
{
string json = "{ \"nullValue\": null }";
var message = NullValueOutsideStruct.Parser.ParseJson(json);
Assert.AreEqual(NullValueOutsideStruct.ValueOneofCase.NullValue, message.ValueCase);
}
// NullValue used to only be converted to the null literal when part of a struct.
// Otherwise, it would end up as a string "NULL_VALUE" (the name of the enum value).
// We still parse that form, for compatibility.
[Test]
public void NullValueOutsideStruct_Compatibility()
{
string json = "{ \"nullValue\": \"NULL_VALUE\" }";
var message = NullValueOutsideStruct.Parser.ParseJson(json);
Assert.AreEqual(NullValueOutsideStruct.ValueOneofCase.NullValue, message.ValueCase);
}
/// <summary>
/// Various tests use strings which have quotes round them for parsing or as the result
/// of formatting, but without those quotes being specified in the tests (for the sake of readability).

@ -357,7 +357,7 @@ namespace Google.Protobuf
/// <param name="value">The value to write. May be null.</param>
public void WriteValue(TextWriter writer, object value)
{
if (value == null)
if (value == null || value is NullValue)
{
WriteNull(writer);
}

@ -37,6 +37,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@ -63,6 +64,7 @@ namespace Google.Protobuf
private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", FrameworkPortability.CompiledRegexWhereAvailable);
private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
private static readonly char[] FieldMaskPathSeparators = new[] { ',' };
private static readonly EnumDescriptor NullValueDescriptor = StructReflection.Descriptor.EnumTypes.Single(ed => ed.ClrType == typeof(NullValue));
private static readonly JsonParser defaultInstance = new JsonParser(Settings.Default);
@ -221,10 +223,11 @@ namespace Google.Protobuf
if (token.Type == JsonToken.TokenType.Null)
{
// Clear the field if we see a null token, unless it's for a singular field of type
// google.protobuf.Value.
// google.protobuf.Value or google.protobuf.NullValue.
// Note: different from Java API, which just ignores it.
// TODO: Bring it more in line? Discuss...
if (field.IsMap || field.IsRepeated || !IsGoogleProtobufValueField(field))
if (field.IsMap || field.IsRepeated ||
!(IsGoogleProtobufValueField(field) || IsGoogleProtobufNullValueField(field)))
{
field.Accessor.Clear(message);
return;
@ -314,6 +317,12 @@ namespace Google.Protobuf
field.MessageType.FullName == Value.Descriptor.FullName;
}
private static bool IsGoogleProtobufNullValueField(FieldDescriptor field)
{
return field.FieldType == FieldType.Enum &&
field.EnumType.FullName == NullValueDescriptor.FullName;
}
private object ParseSingleValue(FieldDescriptor field, JsonTokenizer tokenizer)
{
var token = tokenizer.Next();
@ -325,6 +334,10 @@ namespace Google.Protobuf
{
return Value.ForNull();
}
if (IsGoogleProtobufNullValueField(field))
{
return NullValue.NullValue;
}
return null;
}

Loading…
Cancel
Save