Commit 9440a2ab authored by Jon Skeet's avatar Jon Skeet

Merge pull request #582 from jskeet/csharp-json

JSON formatting in C#
parents b918dc1b 0f34daad
......@@ -40,13 +40,13 @@ namespace Google.Protobuf.Examples.AddressBook {
});
internal__static_tutorial_Person__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::Google.Protobuf.Examples.AddressBook.Person), descriptor.MessageTypes[0],
new string[] { "Name", "Id", "Email", "Phone", });
new string[] { "Name", "Id", "Email", "Phone", }, new string[] { });
internal__static_tutorial_Person_PhoneNumber__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneNumber), descriptor.MessageTypes[0].NestedTypes[0],
new string[] { "Number", "Type", });
new string[] { "Number", "Type", }, new string[] { });
internal__static_tutorial_AddressBook__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::Google.Protobuf.Examples.AddressBook.AddressBook), descriptor.MessageTypes[1],
new string[] { "Person", });
new string[] { "Person", }, new string[] { });
}
#endregion
......@@ -160,6 +160,10 @@ namespace Google.Protobuf.Examples.AddressBook {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (Name.Length != 0) {
output.WriteRawTag(10);
......@@ -330,6 +334,10 @@ namespace Google.Protobuf.Examples.AddressBook {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (Number.Length != 0) {
output.WriteRawTag(10);
......@@ -463,6 +471,10 @@ namespace Google.Protobuf.Examples.AddressBook {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
person_.WriteTo(output, _repeated_person_codec);
}
......
......@@ -734,5 +734,25 @@ namespace Google.Protobuf
var message = SampleMessages.CreateFullTestAllTypes();
Assert.Throws<InvalidCastException>(() => message.Fields[TestAllTypes.SingleBoolFieldNumber].GetValue(new TestMap()));
}
[Test]
public void Reflection_Oneof()
{
var message = new TestAllTypes();
var fields = message.Fields;
Assert.AreEqual(1, fields.Oneofs.Count);
var oneof = fields.Oneofs[0];
Assert.AreEqual("oneof_field", oneof.Descriptor.Name);
Assert.IsNull(oneof.GetCaseFieldDescriptor(message));
message.OneofString = "foo";
Assert.AreSame(fields[TestAllTypes.OneofStringFieldNumber].Descriptor, oneof.GetCaseFieldDescriptor(message));
message.OneofUint32 = 10;
Assert.AreSame(fields[TestAllTypes.OneofUint32FieldNumber].Descriptor, oneof.GetCaseFieldDescriptor(message));
oneof.Clear(message);
Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase);
}
}
}
This diff is collapsed.
......@@ -80,6 +80,7 @@
<Compile Include="GeneratedMessageTest.cs" />
<Compile Include="Collections\MapFieldTest.cs" />
<Compile Include="Collections\RepeatedFieldTest.cs" />
<Compile Include="JsonFormatterTest.cs" />
<Compile Include="SampleEnum.cs" />
<Compile Include="SampleMessages.cs" />
<Compile Include="TestProtos\MapUnittestProto3.cs" />
......
......@@ -38,7 +38,7 @@ namespace Google.Protobuf.TestProtos {
});
internal__static_protobuf_unittest_import_ImportMessage__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::Google.Protobuf.TestProtos.ImportMessage), descriptor.MessageTypes[0],
new string[] { "D", });
new string[] { "D", }, new string[] { });
}
#endregion
......@@ -124,6 +124,10 @@ namespace Google.Protobuf.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (D != 0) {
output.WriteRawTag(8);
......
......@@ -33,7 +33,7 @@ namespace Google.Protobuf.TestProtos {
});
internal__static_protobuf_unittest_import_PublicImportMessage__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::Google.Protobuf.TestProtos.PublicImportMessage), descriptor.MessageTypes[0],
new string[] { "E", });
new string[] { "E", }, new string[] { });
}
#endregion
......@@ -109,6 +109,10 @@ namespace Google.Protobuf.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (E != 0) {
output.WriteRawTag(8);
......
......@@ -53,25 +53,25 @@ namespace UnitTest.Issues.TestProtos {
});
internal__static_unittest_issues_Issue307__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.Issue307), descriptor.MessageTypes[0],
new string[] { });
new string[] { }, new string[] { });
internal__static_unittest_issues_Issue307_NestedOnce__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce), descriptor.MessageTypes[0].NestedTypes[0],
new string[] { });
new string[] { }, new string[] { });
internal__static_unittest_issues_Issue307_NestedOnce_NestedTwice__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.Issue307.Types.NestedOnce.Types.NestedTwice), descriptor.MessageTypes[0].NestedTypes[0].NestedTypes[0],
new string[] { });
new string[] { }, new string[] { });
internal__static_unittest_issues_NegativeEnumMessage__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.NegativeEnumMessage), descriptor.MessageTypes[1],
new string[] { "Value", "Values", "PackedValues", });
new string[] { "Value", "Values", "PackedValues", }, new string[] { });
internal__static_unittest_issues_DeprecatedChild__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.DeprecatedChild), descriptor.MessageTypes[2],
new string[] { });
new string[] { }, new string[] { });
internal__static_unittest_issues_DeprecatedFieldsMessage__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.DeprecatedFieldsMessage), descriptor.MessageTypes[3],
new string[] { "PrimitiveValue", "PrimitiveArray", "MessageValue", "MessageArray", "EnumValue", "EnumArray", });
new string[] { "PrimitiveValue", "PrimitiveArray", "MessageValue", "MessageArray", "EnumValue", "EnumArray", }, new string[] { });
internal__static_unittest_issues_ItemField__FieldAccessorTable =
new pb::FieldAccess.FieldAccessorTable(typeof(global::UnitTest.Issues.TestProtos.ItemField), descriptor.MessageTypes[4],
new string[] { "Item", });
new string[] { "Item", }, new string[] { });
}
#endregion
......@@ -148,6 +148,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
}
......@@ -237,6 +241,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
}
......@@ -326,6 +334,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
}
......@@ -459,6 +471,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) {
output.WriteRawTag(8);
......@@ -577,6 +593,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
}
......@@ -746,6 +766,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (PrimitiveValue != 0) {
output.WriteRawTag(8);
......@@ -918,6 +942,10 @@ namespace UnitTest.Issues.TestProtos {
return hash;
}
public override string ToString() {
return pb::JsonFormatter.Default.Format(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (Item != 0) {
output.WriteRawTag(8);
......
......@@ -89,6 +89,7 @@ namespace Google.Protobuf.Descriptors
/// <summary>
/// Finds an enum value by number. If multiple enum values have the
/// same number, this returns the first defined value with that number.
/// If there is no value for the given number, this returns <c>null</c>.
/// </summary>
public EnumValueDescriptor FindValueByNumber(int number)
{
......
......@@ -42,6 +42,7 @@ namespace Google.Protobuf.FieldAccess
public sealed class FieldAccessorTable
{
private readonly ReadOnlyCollection<IFieldAccessor> accessors;
private readonly ReadOnlyCollection<OneofAccessor> oneofs;
private readonly MessageDescriptor descriptor;
/// <summary>
......@@ -51,7 +52,7 @@ namespace Google.Protobuf.FieldAccess
/// <param name="type">The CLR type for the message.</param>
/// <param name="descriptor">The type's descriptor</param>
/// <param name="propertyNames">The Pascal-case names of all the field-based properties in the message.</param>
public FieldAccessorTable(Type type, MessageDescriptor descriptor, string[] propertyNames)
public FieldAccessorTable(Type type, MessageDescriptor descriptor, string[] propertyNames, string[] oneofPropertyNames)
{
this.descriptor = descriptor;
var accessorsArray = new IFieldAccessor[descriptor.Fields.Count];
......@@ -65,7 +66,13 @@ namespace Google.Protobuf.FieldAccess
: (IFieldAccessor) new SingleFieldAccessor(type, name, field);
}
accessors = new ReadOnlyCollection<IFieldAccessor>(accessorsArray);
// TODO(jonskeet): Oneof support
var oneofsArray = new OneofAccessor[descriptor.Oneofs.Count];
for (int i = 0; i < oneofsArray.Length; i++)
{
var oneof = descriptor.Oneofs[i];
oneofsArray[i] = new OneofAccessor(type, oneofPropertyNames[i], oneof);
}
oneofs = new ReadOnlyCollection<OneofAccessor>(oneofsArray);
}
// TODO: Validate the name here... should possibly make this type a more "general reflection access" type,
......@@ -75,6 +82,10 @@ namespace Google.Protobuf.FieldAccess
/// </summary>
public ReadOnlyCollection<IFieldAccessor> Accessors { get { return accessors; } }
public ReadOnlyCollection<OneofAccessor> Oneofs { get { return oneofs; } }
// TODO: Review this, as it's easy to get confused between FieldNumber and Index.
// Currently only used to get an accessor related to a oneof... maybe just make that simpler?
public IFieldAccessor this[int fieldNumber]
{
get
......@@ -83,17 +94,5 @@ namespace Google.Protobuf.FieldAccess
return accessors[field.Index];
}
}
internal IFieldAccessor this[FieldDescriptor field]
{
get
{
if (field.ContainingType != descriptor)
{
throw new ArgumentException("FieldDescriptor does not match message type.");
}
return accessors[field.Index];
}
}
}
}
\ No newline at end of file
......@@ -44,6 +44,8 @@ namespace Google.Protobuf.FieldAccess
/// </summary>
FieldDescriptor Descriptor { get; }
// TODO: Should the argument type for these messages by IReflectedMessage?
/// <summary>
/// Clears the field in the specified message. (For repeated fields,
/// this clears the list.)
......
......@@ -30,62 +30,57 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using Google.Protobuf.Descriptors;
using System;
using System.Reflection;
namespace Google.Protobuf.FieldAccess
{
// TODO(jonskeet): Add "new" oneof API support
/// <summary>
/// Access for an oneof
/// Reflection access for a oneof, allowing clear and "get case" actions.
/// </summary>
internal class OneofAccessor<TMessage> where TMessage : IMessage<TMessage>
public sealed class OneofAccessor
{
/*
private readonly Func<TMessage, object> caseDelegate;
private readonly Func<TBuilder, IBuilder> clearDelegate;
private MessageDescriptor descriptor;
private readonly Func<object, int> caseDelegate;
private readonly Action<object> clearDelegate;
private OneofDescriptor descriptor;
internal OneofAccessor(MessageDescriptor descriptor, string name)
internal OneofAccessor(Type type, string propertyName, OneofDescriptor descriptor)
{
this.descriptor = descriptor;
MethodInfo clearMethod = typeof(TBuilder).GetMethod("Clear" + name);
PropertyInfo caseProperty = typeof(TMessage).GetProperty(name + "Case");
if (clearMethod == null || caseProperty == null)
PropertyInfo property = type.GetProperty(propertyName + "Case");
if (property == null || !property.CanRead)
{
throw new ArgumentException("Not all required properties/methods available for oneof");
throw new ArgumentException("Not all required properties/methods available");
}
this.descriptor = descriptor;
caseDelegate = ReflectionUtil.CreateFuncObjectT<int>(property.GetGetMethod());
clearDelegate = ReflectionUtil.CreateDelegateFunc<TBuilder, IBuilder>(clearMethod);
caseDelegate = ReflectionUtil.CreateUpcastDelegate<TMessage>(caseProperty.GetGetMethod());
this.descriptor = descriptor;
MethodInfo clearMethod = type.GetMethod("Clear" + propertyName);
clearDelegate = ReflectionUtil.CreateActionObject(clearMethod);
}
/// <summary>
/// Indicates whether the specified message has set any field in the oneof.
/// </summary>
public bool Has(TMessage message)
{
return ((int) caseDelegate(message) != 0);
}
public OneofDescriptor Descriptor { get { return descriptor; } }
/// <summary>
/// Clears the oneof in the specified builder.
/// Clears the oneof in the specified message.
/// </summary>
public void Clear(TBuilder builder)
public void Clear(object message)
{
clearDelegate(builder);
clearDelegate(message);
}
/// <summary>
/// Indicates which field in the oneof is set for specified message
/// </summary>
public virtual FieldDescriptor GetOneofFieldDescriptor(TMessage message)
public FieldDescriptor GetCaseFieldDescriptor(object message)
{
int fieldNumber = (int) caseDelegate(message);
int fieldNumber = caseDelegate(message);
if (fieldNumber > 0)
{
return descriptor.FindFieldByNumber(fieldNumber);
return descriptor.ContainingType.FindFieldByNumber(fieldNumber);
}
return null;
}*/
}
}
}
......@@ -63,7 +63,20 @@ namespace Google.Protobuf.FieldAccess
Expression upcast = Expression.Convert(call, typeof(object));
return Expression.Lambda<Func<object, object>>(upcast, parameter).Compile();
}
/// <summary>
/// Creates a delegate which will cast the argument to the appropriate method target type,
/// call the method on it, then convert the result to the specified type.
/// </summary>
internal static Func<object, T> CreateFuncObjectT<T>(MethodInfo method)
{
ParameterExpression parameter = Expression.Parameter(typeof(object), "p");
Expression downcast = Expression.Convert(parameter, method.DeclaringType);
Expression call = Expression.Call(downcast, method);
Expression upcast = Expression.Convert(call, typeof(T));
return Expression.Lambda<Func<object, T>>(upcast, parameter).Compile();
}
/// <summary>
/// Creates a delegate which will execute the given method after casting the first argument to
/// the target type of the method, and the second argument to the first parameter type of the method.
......
......@@ -40,9 +40,9 @@ namespace Google.Protobuf
// TODO(jonskeet): Split these interfaces into separate files when we're happy with them.
/// <summary>
/// Reflection support for a specific message type.
/// Reflection support for accessing field values.
/// </summary>
public interface IReflectedMessage
public interface IReflectedMessage : IMessage
{
FieldAccessorTable Fields { get; }
// TODO(jonskeet): Descriptor? Or a single property which has "all you need for reflection"?
......@@ -81,7 +81,7 @@ namespace Google.Protobuf
/// the implementation class.
/// </summary>
/// <typeparam name="T">The message type.</typeparam>
public interface IMessage<T> : IMessage, IEquatable<T>, IDeepCloneable<T>, IFreezable where T : IMessage<T>
public interface IMessage<T> : IReflectedMessage, IEquatable<T>, IDeepCloneable<T>, IFreezable where T : IMessage<T>
{
/// <summary>
/// Merges the given message into this one.
......
This diff is collapsed.
......@@ -81,6 +81,7 @@
<Compile Include="FieldCodec.cs" />
<Compile Include="FrameworkPortability.cs" />
<Compile Include="Freezable.cs" />
<Compile Include="JsonFormatter.cs" />
<Compile Include="MessageExtensions.cs" />
<Compile Include="FieldAccess\FieldAccessorBase.cs" />
<Compile Include="FieldAccess\ReflectionUtil.cs" />
......
......@@ -58,6 +58,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
virtual void WriteHash(io::Printer* printer) = 0;
virtual void WriteEquals(io::Printer* printer) = 0;
// Currently unused, as we use reflection to generate JSON
virtual void WriteToString(io::Printer* printer) = 0;
protected:
......
......@@ -117,12 +117,9 @@ void MapFieldGenerator::WriteEquals(io::Printer* printer) {
variables_,
"if (!$property_name$.Equals(other.$property_name$)) return false;\n");
}
void MapFieldGenerator::WriteToString(io::Printer* printer) {
/*
variables_["field_name"] = GetFieldName(descriptor_);
printer->Print(
variables_,
"PrintField(\"$field_name$\", has$property_name$, $name$_, writer);\n");*/
// TODO: If we ever actually use ToString, we'll need to impleme this...
}
void MapFieldGenerator::GenerateCloningCode(io::Printer* printer) {
......
......@@ -151,6 +151,7 @@ void MessageGenerator::GenerateStaticVariableInitializers(io::Printer* printer)
printer->Print("\"$property_name$\", ",
"property_name", GetPropertyName(descriptor_->field(i)));
}
printer->Print("}, new string[] { ");
for (int i = 0; i < descriptor_->oneof_decl_count(); i++) {
printer->Print("\"$oneof_name$\", ",
"oneof_name",
......@@ -429,7 +430,10 @@ void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) {
printer->Outdent();
printer->Print("}\n\n");
// TODO(jonskeet): ToString.
printer->Print(
"public override string ToString() {\n"
" return pb::JsonFormatter.Default.Format(this);\n"
"}\n\n");
}
void MessageGenerator::GenerateMessageSerializationMethods(io::Printer* printer) {
......
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