Commit 6ea9bc7a authored by Jon Skeet's avatar Jon Skeet

Fixes to JsonFormatter

- Handle oneof properly
- Omit unknown enum values
parent 68fc689e
...@@ -160,10 +160,34 @@ namespace Google.Protobuf ...@@ -160,10 +160,34 @@ namespace Google.Protobuf
} }
[Test] [Test]
public void UnknownEnumValue() public void UnknownEnumValueOmitted_SingleField()
{ {
var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 100 }; var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 100 };
Assert.AreEqual("{ \"singleForeignEnum\": 100 }", JsonFormatter.Default.Format(message)); Assert.AreEqual("{ }", JsonFormatter.Default.Format(message));
}
[Test]
public void UnknownEnumValueOmitted_RepeatedField()
{
var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.FOREIGN_BAZ, (ForeignEnum) 100, ForeignEnum.FOREIGN_FOO } };
Assert.AreEqual("{ \"repeatedForeignEnum\": [ \"FOREIGN_BAZ\", \"FOREIGN_FOO\" ] }", JsonFormatter.Default.Format(message));
}
[Test]
public void UnknownEnumValueOmitted_MapField()
{
// This matches the C++ behaviour.
var message = new TestMap { MapInt32Enum = { { 1, MapEnum.MAP_ENUM_FOO }, { 2, (MapEnum) 100 }, { 3, MapEnum.MAP_ENUM_BAR } } };
Assert.AreEqual("{ \"mapInt32Enum\": { \"1\": \"MAP_ENUM_FOO\", \"3\": \"MAP_ENUM_BAR\" } }", JsonFormatter.Default.Format(message));
}
[Test]
public void UnknownEnumValueOmitted_RepeatedField_AllEntriesUnknown()
{
// *Maybe* we should hold off on writing the "[" until we find that we've got at least one value to write...
// but this is what happens at the moment, and it doesn't seem too awful.
var message = new TestAllTypes { RepeatedForeignEnum = { (ForeignEnum) 200, (ForeignEnum) 100 } };
Assert.AreEqual("{ \"repeatedForeignEnum\": [ ] }", JsonFormatter.Default.Format(message));
} }
[Test] [Test]
...@@ -213,5 +237,25 @@ namespace Google.Protobuf ...@@ -213,5 +237,25 @@ namespace Google.Protobuf
{ {
Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original)); Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original));
} }
[Test]
[TestCase(null, "{ }")]
[TestCase("x", "{ \"fooString\": \"x\" }")]
[TestCase("", "{ \"fooString\": \"\" }")]
[TestCase(null, "{ }")]
public void Oneof(string fooStringValue, string expectedJson)
{
var message = new TestOneof();
if (fooStringValue != null)
{
message.FooString = fooStringValue;
}
// We should get the same result both with and without "format default values".
var formatter = new JsonFormatter(new JsonFormatter.Settings(false));
Assert.AreEqual(expectedJson, formatter.Format(message));
formatter = new JsonFormatter(new JsonFormatter.Settings(true));
Assert.AreEqual(expectedJson, formatter.Format(message));
}
} }
} }
...@@ -136,13 +136,28 @@ namespace Google.Protobuf ...@@ -136,13 +136,28 @@ namespace Google.Protobuf
builder.Append("{ "); builder.Append("{ ");
var fields = message.Fields; var fields = message.Fields;
bool first = true; bool first = true;
// First non-oneof fields
foreach (var accessor in fields.Accessors) foreach (var accessor in fields.Accessors)
{ {
var descriptor = accessor.Descriptor;
// Oneofs are written later
if (descriptor.ContainingOneof != null)
{
continue;
}
// Omit default values unless we're asked to format them
object value = accessor.GetValue(message); object value = accessor.GetValue(message);
if (!settings.FormatDefaultValues && IsDefaultValue(accessor, value)) if (!settings.FormatDefaultValues && IsDefaultValue(accessor, value))
{ {
continue; continue;
} }
// Omit awkward (single) values such as unknown enum values
if (!descriptor.IsRepeated && !descriptor.IsMap && !CanWriteSingleValue(accessor.Descriptor, value))
{
continue;
}
// Okay, all tests complete: let's write the field value...
if (!first) if (!first)
{ {
builder.Append(", "); builder.Append(", ");
...@@ -152,6 +167,32 @@ namespace Google.Protobuf ...@@ -152,6 +167,32 @@ namespace Google.Protobuf
WriteValue(builder, accessor, value); WriteValue(builder, accessor, value);
first = false; first = false;
} }
// Now oneofs
foreach (var accessor in fields.Oneofs)
{
var fieldDescriptor = accessor.GetCaseFieldDescriptor(message);
if (fieldDescriptor == null)
{
continue;
}
var fieldAccessor = fields[fieldDescriptor];
object value = fieldAccessor.GetValue(message);
// Omit awkward (single) values such as unknown enum values
if (!fieldDescriptor.IsRepeated && !fieldDescriptor.IsMap && !CanWriteSingleValue(fieldDescriptor, value))
{
continue;
}
if (!first)
{
builder.Append(", ");
}
WriteString(builder, ToCamelCase(fieldDescriptor.Name));
builder.Append(": ");
WriteValue(builder, fieldAccessor, value);
first = false;
}
builder.Append(first ? "}" : " }"); builder.Append(first ? "}" : " }");
} }
...@@ -303,15 +344,8 @@ namespace Google.Protobuf ...@@ -303,15 +344,8 @@ namespace Google.Protobuf
} }
case FieldType.Enum: case FieldType.Enum:
EnumValueDescriptor enumValue = descriptor.EnumType.FindValueByNumber((int) value); EnumValueDescriptor enumValue = descriptor.EnumType.FindValueByNumber((int) value);
if (enumValue != null) // We will already have validated that this is a known value.
{ WriteString(builder, enumValue.Name);
WriteString(builder, enumValue.Name);
}
else
{
// ??? Need more documentation
builder.Append(((int) value).ToString("d", CultureInfo.InvariantCulture));
}
break; break;
case FieldType.Fixed64: case FieldType.Fixed64:
case FieldType.UInt64: case FieldType.UInt64:
...@@ -354,6 +388,10 @@ namespace Google.Protobuf ...@@ -354,6 +388,10 @@ namespace Google.Protobuf
bool first = true; bool first = true;
foreach (var value in list) foreach (var value in list)
{ {
if (!CanWriteSingleValue(accessor.Descriptor, value))
{
continue;
}
if (!first) if (!first)
{ {
builder.Append(", "); builder.Append(", ");
...@@ -373,6 +411,10 @@ namespace Google.Protobuf ...@@ -373,6 +411,10 @@ namespace Google.Protobuf
// This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal. // This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
foreach (DictionaryEntry pair in dictionary) foreach (DictionaryEntry pair in dictionary)
{ {
if (!CanWriteSingleValue(valueType, pair.Value))
{
continue;
}
if (!first) if (!first)
{ {
builder.Append(", "); builder.Append(", ");
...@@ -409,6 +451,21 @@ namespace Google.Protobuf ...@@ -409,6 +451,21 @@ namespace Google.Protobuf
builder.Append(first ? "}" : " }"); builder.Append(first ? "}" : " }");
} }
/// <summary>
/// Returns whether or not a singular value can be represented in JSON.
/// Currently only relevant for enums, where unknown values can't be represented.
/// For repeated/map fields, this always returns true.
/// </summary>
private bool CanWriteSingleValue(FieldDescriptor descriptor, object value)
{
if (descriptor.FieldType == FieldType.Enum)
{
EnumValueDescriptor enumValue = descriptor.EnumType.FindValueByNumber((int) value);
return enumValue != null;
}
return true;
}
/// <summary> /// <summary>
/// Writes a string (including leading and trailing double quotes) to a builder, escaping as required. /// Writes a string (including leading and trailing double quotes) to a builder, escaping as required.
/// </summary> /// </summary>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment