StonyGrey.ObjectMapper is a GRPC-aware source generated object mapper. In addition to mapping POCOs, it generates extension methods that map between Protocol Buffers (Protobuf) classes generated by Grpc.Tools and domain classes provided by you.
Given a protobuf definition such as this:
package Protobuf;
enum TestEnum { One = 0; Two = 1; }
message TestMessage {
string String = 1;
int32 Int = 2;
optional int32 Optional = 3;
int32 NonOptional = 4;
bytes Guid = 5;
sint64 DateTime = 6;
oneof OneOf {
sint64 OneOfA = 7;
sint64 OneOfB = 8;
}
TestEnum TestEnum = 9;
bytes Data = 10;
optional bytes OptionalData = 11;
}
Grpc.Tools will generate a C# class (that is too verbose in nature to include here). The StonyGrey.ObjectMapper will generate extension methods that map between this Grpc.Tools created class and a domain class created by you. For example:
namespace Domain
{
public enum TestEnum { One, Two }
public class TestMessage
{
public string? String { get; set; }
public int Int { get; set; }
public int? Optional { get; set; }
public int NonOptional { get; set; }
public long OneOfA { get; set; }
public long OneOfB { get; set; }
public DateTime DateTime { get; set; }
public Guid Guid { get; set; }
public TestEnum TestEnum { get; set; }
public byte[] Data { get; set; } = Array.Empty<byte>();
public byte[]? OptionalData { get; set; }
}
}
Extension methods in a class annotated with the MappingConversion
attribute will convert between incompatible types.
namespace Domain
{
[MappingConversion]
public static partial class MappingExtensions
{
public static ByteString MapToByteString(this Guid value)
=> ByteString.CopyFrom(value.ToByteArray());
public static byte[] MapToByteArray(this ByteString value)
=> value == null ? Array.Empty<byte>() : value.ToByteArray();
...
}
}
Please see the StonyGrey.ObjectMapper.Host project for a working example.
-
Add the StonyGrey.ObjectMapper Nuget to your project.
-
Use the
Map
attribute to define the source and target classes:[assembly: Map(typeof(Domain.TestMessage), typeof(Protobuf.TestMessage))] [assembly: Map(typeof(Protobuf.TestMessage), typeof(Domain.TestMessage), ContainingNamespaceKind.Destination)]
-
Convert incompatible types in a class annotated with
MappingConversion
.
StonyGrey.ObjectMapper will generate extension methods with the following signatures:
public static DomainClass Map(this GrpcGeneratedClass self) {...}
public static GrpcGeneratedClass Map(this DomainClass self) {...}
The generated source can be found at YourProject->Dependencies->Analyzers->StonyGrey.ObjectMapper->StonyGrey.ObjectMapper.MapGenerator
StonyGrey.ObjectMapper was built to address a specific problem and as such does not cover every scenario. It was tested with proto3 and Grpc.Tools 2.45.0.
Some additional detail on what is or is not supported follows.
Everything is mapped one-to-one with the exception of bytes
which is automatically mapped to/ from Google.Protobuf.ByteString
.
Conversion is delegated to the caller but can be as simple as a cast.
Message fields that are themselves messages will need MapProtobuf
attributes declared. This allows this library to map an entire object graph.
Untested/ Unsupported.
Untested/ Unsupported.
This is supported by relying on the convention that only one of the properties will have a non-default value while all others will have the default value.
Untested/ Unsupported.
Mapping between properties is attempted in the following order:
- Simple assignment
- Conversion using the
MappingConversion
annotated class. - Map()
The souce must implement IEumerable
and the target ICollection
.
StonyGrey.ObjectMapper is based on the InlineMapping project.