From e7caf15577e4fc8a024403bd8586041742d9e266 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 31 Jul 2015 15:07:50 +0100 Subject: [PATCH] Initial pass at formatting Struct as JSON. This seems remarkably little code, but it appears to work. I can add tests for invalid structs at some point, once the general approach is approved. --- .../Google.Protobuf.Test/JsonFormatterTest.cs | 17 ++++ csharp/src/Google.Protobuf/JsonFormatter.cs | 84 ++++++++++++++++++- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs index 82094ea35b..f6e6488ff1 100644 --- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs @@ -368,7 +368,24 @@ namespace Google.Protobuf { var message = new TestWellKnownTypes { DurationField = new Duration() }; AssertJson("{ 'durationField': '0s' }", JsonFormatter.Default.Format(message)); + } + [Test] + public void StructSample() + { + var message = new Struct + { + Fields = + { + { "a", new Value { NullValue = new NullValue() } }, + { "b", new Value { BoolValue = false } }, + { "c", new Value { NumberValue = 10.5 } }, + { "d", new Value { StringValue = "text" } }, + { "e", new Value { ListValue = new ListValue { Values = { new Value { StringValue = "t1" }, new Value { NumberValue = 5 } } } } }, + { "f", new Value { StructValue = new Struct { Fields = { { "nested", new Value { StringValue = "value" } } } } } } + } + }; + AssertJson("{ 'a': null, 'b': false, 'c': 10.5, 'd': 'text', 'e': [ 't1', 5 ], 'f': { 'nested': 'value' } }", message.ToString()); } /// diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index 099fb6a14f..3b25beb687 100644 --- a/csharp/src/Google.Protobuf/JsonFormatter.cs +++ b/csharp/src/Google.Protobuf/JsonFormatter.cs @@ -380,23 +380,44 @@ namespace Google.Protobuf /// private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescriptor descriptor, object value, bool inField) { + if (value == null) + { + WriteNull(builder); + return; + } // For wrapper types, the value will be the (possibly boxed) "native" value, // so we can write it as if we were unconditionally writing the Value field for the wrapper type. - if (descriptor.File == Int32Value.Descriptor.File && value != null) + if (descriptor.File == Int32Value.Descriptor.File) { WriteSingleValue(builder, descriptor.FindFieldByNumber(1), value); return; } - if (descriptor.FullName == Timestamp.Descriptor.FullName && value != null) + if (descriptor.FullName == Timestamp.Descriptor.FullName) { MaybeWrapInString(builder, value, WriteTimestamp, inField); return; } - if (descriptor.FullName == Duration.Descriptor.FullName && value != null) + if (descriptor.FullName == Duration.Descriptor.FullName) { MaybeWrapInString(builder, value, WriteDuration, inField); return; } + if (descriptor.FullName == Struct.Descriptor.FullName) + { + WriteStruct(builder, (IMessage) value); + return; + } + if (descriptor.FullName == ListValue.Descriptor.FullName) + { + var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor; + WriteList(builder, fieldAccessor, (IList) fieldAccessor.GetValue(value)); + return; + } + if (descriptor.FullName == Value.Descriptor.FullName) + { + WriteStructFieldValue(builder, (IMessage) value); + return; + } WriteMessage(builder, (IMessage) value); } @@ -483,6 +504,63 @@ namespace Google.Protobuf } } + private void WriteStruct(StringBuilder builder, IMessage message) + { + builder.Append("{ "); + IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message); + bool first = true; + foreach (DictionaryEntry entry in fields) + { + string key = (string) entry.Key; + IMessage value = (IMessage) entry.Value; + if (string.IsNullOrEmpty(key) || value == null) + { + throw new InvalidOperationException("Struct fields cannot have an empty key or a null value."); + } + + if (!first) + { + builder.Append(", "); + } + WriteString(builder, key); + builder.Append(": "); + WriteStructFieldValue(builder, value); + first = false; + } + builder.Append(first ? "}" : " }"); + } + + private void WriteStructFieldValue(StringBuilder builder, IMessage message) + { + var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message); + if (specifiedField == null) + { + throw new InvalidOperationException("Value message must contain a value for the oneof."); + } + + object value = specifiedField.Accessor.GetValue(message); + + switch (specifiedField.FieldNumber) + { + case Value.BoolValueFieldNumber: + case Value.StringValueFieldNumber: + case Value.NumberValueFieldNumber: + WriteSingleValue(builder, specifiedField, value); + return; + case Value.StructValueFieldNumber: + case Value.ListValueFieldNumber: + // Structs and ListValues are nested messages, and already well-known types. + var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message); + WriteWellKnownTypeValue(builder, nestedMessage.Descriptor, nestedMessage, true); + return; + case Value.NullValueFieldNumber: + WriteNull(builder); + return; + default: + throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber); + } + } + private void WriteList(StringBuilder builder, IFieldAccessor accessor, IList list) { builder.Append("[ ");