aboutsummaryrefslogtreecommitdiff
path: root/csharp/src/Google.Protobuf/JsonFormatter.cs
diff options
context:
space:
mode:
authorJon Skeet <jonskeet@google.com>2015-07-31 13:22:15 +0100
committerJon Skeet <jonskeet@google.com>2015-08-03 09:26:04 +0100
commit16e272e0c4eebb1d01a2e71ef3c555ff57a22b07 (patch)
tree5b624cf24ba05ac067dc4bfa6f10b8fe4dd967c5 /csharp/src/Google.Protobuf/JsonFormatter.cs
parent80f89b4ecfad8783c1453f1bf36ecda1c2c9c664 (diff)
downloadprotobuf-16e272e0c4eebb1d01a2e71ef3c555ff57a22b07.tar.gz
protobuf-16e272e0c4eebb1d01a2e71ef3c555ff57a22b07.tar.bz2
protobuf-16e272e0c4eebb1d01a2e71ef3c555ff57a22b07.zip
Format JSON for Duration and Timestamp.
This is taking an approach of putting all the logic in JsonFormatter. That's helpful in terms of concealing the details of whether or not to wrap the value in quotes, but it does lack flexibility. I don't *think* we want to allow user-defined formatting of messages, so that much shouldn't be a problem.
Diffstat (limited to 'csharp/src/Google.Protobuf/JsonFormatter.cs')
-rw-r--r--csharp/src/Google.Protobuf/JsonFormatter.cs130
1 files changed, 120 insertions, 10 deletions
diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs
index d9783fc4..29fc80fa 100644
--- a/csharp/src/Google.Protobuf/JsonFormatter.cs
+++ b/csharp/src/Google.Protobuf/JsonFormatter.cs
@@ -122,10 +122,14 @@ namespace Google.Protobuf
{
Preconditions.CheckNotNull(message, "message");
StringBuilder builder = new StringBuilder();
- // TODO(jonskeet): Handle well-known types here.
- // Our reflection support needs improving so that we can get at the descriptor
- // to find out whether *this* message is a well-known type.
- WriteMessage(builder, message);
+ if (message.Descriptor.IsWellKnownType)
+ {
+ WriteWellKnownTypeValue(builder, message.Descriptor, message, false);
+ }
+ else
+ {
+ WriteMessage(builder, message);
+ }
return builder.ToString();
}
@@ -356,7 +360,7 @@ namespace Google.Protobuf
case FieldType.Group: // Never expect to get this, but...
if (descriptor.MessageType.IsWellKnownType)
{
- WriteWellKnownTypeValue(builder, descriptor, value);
+ WriteWellKnownTypeValue(builder, descriptor.MessageType, value, true);
}
else
{
@@ -370,20 +374,126 @@ namespace Google.Protobuf
/// <summary>
/// Central interception point for well-known type formatting. Any well-known types which
- /// don't need special handling can fall back to WriteMessage.
+ /// don't need special handling can fall back to WriteMessage. We avoid assuming that the
+ /// values are using the embedded well-known types, in order to allow for dynamic messages
+ /// in the future.
/// </summary>
- private void WriteWellKnownTypeValue(StringBuilder builder, FieldDescriptor descriptor, object value)
+ private void WriteWellKnownTypeValue(StringBuilder builder, MessageDescriptor descriptor, object value, bool inField)
{
// 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.MessageType.File == Int32Value.Descriptor.File && value != null)
+ if (descriptor.File == Int32Value.Descriptor.File && value != null)
{
- WriteSingleValue(builder, descriptor.MessageType.FindFieldByNumber(1), value);
+ WriteSingleValue(builder, descriptor.FindFieldByNumber(1), value);
+ return;
+ }
+ if (descriptor.FullName == Timestamp.Descriptor.FullName && value != null)
+ {
+ MaybeWrapInString(builder, value, WriteTimestamp, inField);
+ return;
+ }
+ if (descriptor.FullName == Duration.Descriptor.FullName && value != null)
+ {
+ MaybeWrapInString(builder, value, WriteDuration, inField);
return;
}
WriteMessage(builder, (IMessage) value);
}
+ /// <summary>
+ /// Some well-known types end up as string values... so they need wrapping in quotes, but only
+ /// when they're being used as fields within another message.
+ /// </summary>
+ private void MaybeWrapInString(StringBuilder builder, object value, Action<StringBuilder, IMessage> action, bool inField)
+ {
+ if (inField)
+ {
+ builder.Append('"');
+ action(builder, (IMessage) value);
+ builder.Append('"');
+ }
+ else
+ {
+ action(builder, (IMessage) value);
+ }
+ }
+
+ private void WriteTimestamp(StringBuilder builder, IMessage value)
+ {
+ // TODO: In the common case where this *is* using the built-in Timestamp type, we could
+ // avoid all the reflection at this point, by casting to Timestamp. In the interests of
+ // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
+ // it still works in that case.
+ int nanos = (int) value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
+ long seconds = (long) value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
+
+ // Even if the original message isn't using the built-in classes, we can still build one... and then
+ // rely on it being normalized.
+ Timestamp normalized = Timestamp.Normalize(seconds, nanos);
+ // Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
+ DateTime dateTime = normalized.ToDateTime();
+ builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", CultureInfo.InvariantCulture));
+ if (normalized.Nanos != 0)
+ {
+ builder.Append('.');
+ // Output to 3, 6 or 9 digits.
+ if (normalized.Nanos % 1000000 == 0)
+ {
+ builder.Append((normalized.Nanos / 1000000).ToString("d", CultureInfo.InvariantCulture));
+ }
+ else if (normalized.Nanos % 1000 == 0)
+ {
+ builder.Append((normalized.Nanos / 1000).ToString("d", CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ builder.Append((normalized.Nanos).ToString("d", CultureInfo.InvariantCulture));
+ }
+ }
+ builder.Append('Z');
+ }
+
+ private void WriteDuration(StringBuilder builder, IMessage value)
+ {
+ // TODO: In the common case where this *is* using the built-in Timestamp type, we could
+ // avoid all the reflection at this point, by casting to Timestamp. In the interests of
+ // avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
+ // it still works in that case.
+ int nanos = (int) value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
+ long seconds = (long) value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
+
+ // Even if the original message isn't using the built-in classes, we can still build one... and then
+ // rely on it being normalized.
+ Duration normalized = Duration.Normalize(seconds, nanos);
+
+ // The seconds part will normally provide the minus sign if we need it, but not if it's 0...
+ if (normalized.Seconds == 0 && normalized.Nanos < 0)
+ {
+ builder.Append('-');
+ }
+
+ builder.Append(normalized.Seconds.ToString("d", CultureInfo.InvariantCulture));
+ nanos = Math.Abs(normalized.Nanos);
+ if (nanos != 0)
+ {
+ builder.Append('.');
+ // Output to 3, 6 or 9 digits.
+ if (nanos % 1000000 == 0)
+ {
+ builder.Append((nanos / 1000000).ToString("d", CultureInfo.InvariantCulture));
+ }
+ else if (normalized.Nanos % 1000 == 0)
+ {
+ builder.Append((nanos / 1000).ToString("d", CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ builder.Append(nanos.ToString("d", CultureInfo.InvariantCulture));
+ }
+ }
+ builder.Append('s');
+ }
+
private void WriteList(StringBuilder builder, IFieldAccessor accessor, IList list)
{
builder.Append("[ ");
@@ -537,7 +647,7 @@ namespace Google.Protobuf
}
}
builder.Append('"');
- }
+ }
private const string Hex = "0123456789abcdef";
private static void HexEncodeUtf16CodeUnit(StringBuilder builder, char c)