Talk2Bits.MappingGenerator
0.0.0-alpha.0.26
See the version list below for details.
dotnet add package Talk2Bits.MappingGenerator --version 0.0.0-alpha.0.26
NuGet\Install-Package Talk2Bits.MappingGenerator -Version 0.0.0-alpha.0.26
<PackageReference Include="Talk2Bits.MappingGenerator" Version="0.0.0-alpha.0.26" />
paket add Talk2Bits.MappingGenerator --version 0.0.0-alpha.0.26
#r "nuget: Talk2Bits.MappingGenerator, 0.0.0-alpha.0.26"
// Install Talk2Bits.MappingGenerator as a Cake Addin
#addin nuget:?package=Talk2Bits.MappingGenerator&version=0.0.0-alpha.0.26&prerelease
// Install Talk2Bits.MappingGenerator as a Cake Tool
#tool nuget:?package=Talk2Bits.MappingGenerator&version=0.0.0-alpha.0.26&prerelease
MappingGenerator
MappingGenerator is C# source generator that allows generating object mapping code on compilation stage.
Having source code for your mappings generated provides the following benefits:
- Your mappings are always up to date.
- You see code for all your mappings. Nothing is hidden.
- All debugging features are available. You can step-in to your mappings, set breakpoints etc.
- If mapping can't be done or has issues you get compiler errors rather than runtime errors.
Table of contents
- How do I get started?
- Features
- Basic mapping
- Keeping eye on missing mappings
- Ignore destination property
- Override property matching behavior
- Provide custom mapping for destination property
- Executing post mapping logic
- Providing custom destination object constructor
- Mapping constructor parameters
- Mapping init only properties
- Reusing generated mappings
- Custom type converters
- Open generics
- Arrays and collections
How do I get started?
Install Talk2Bits.MappingGenerator nuget package.
Define source and destination:
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
Define mapper class:
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{ }
Rebuild your project
Use generated mapping:
var source = new Source();
var mapper = new Mapper();
var result = mapper.Map(source);
Features
Basic mapping
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{ }
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
// Properties are matched by name and type.
result.Number = source.Number;
result.Text = source.Text;
// Explicit cast generated.
result.BigNumber = (int)source.BigNumber;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Keeping eye on missing mappings
You can control how MappingGenerator behaves if it was not able to map everything in destination object with MissingMappingBehavior parameter. Options are:
- Warning (Default). Produce compilation warning.
- Ignore. Do nothing.
- Error. Produce compilation error.
For example the following code will produce compilation error because it can't find source for B.Val
:
public class A {}
public class B
{
public string Val { get; set; }
}
[MappingGenerator(typeof(A), typeof(B), MissingMappingBehavior = MissingMappingBehavior.Error)]
public partial class Mapper
{
}
Compilation error:
Mapping generator 'Mapper': Failed to resolve mapping for type 'B' property 'Val'.
Ignore destination property
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string Text { get; set; }
public long BigNumber { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
[MappingGeneratorPropertyIgnore(nameof(Destination.BigNumber))]
public partial class Mapper
{ }
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
result.Text = source.Text;
// No mapping for BigNumber property.
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Override property matching behavior
public class Source
{
public int Number { get; set; }
public string SourceText { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string DestinationText { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
[MappingGeneratorPropertyMapping(nameof(Source.SourceText), nameof(Destination.DestinationText))]
public partial class Mapper
{ }
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
// SourceText mapped to DestinationText.
result.DestinationText = source.SourceText;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Provide custom mapping for destination property
You can provide custom mapping for property by adding function:
<DESTINATION-PROPERTY-TYPE> Map<DESTINATION-PROPERTY-NAME>(Source source)
{}
public class Source
{
public int Number { get; set; }
public string SourceText { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string DestinationText { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
private string MapDestinationText(Source source)
{
return "Custom" + source.SourceText;
}
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
// User provided mapping used.
result.DestinationText = MapDestinationText(source);
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Executing post mapping logic
MappingGenerator generates partial AfterMap method which is called after mapping is done.
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string Text { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
private partial void AfterMap(Source source, Destination result);
{
result.Number = result.Number + 100;
}
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
result.Text = source.Text;
// Will call your AfterMap method.
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Providing custom destination object constructor
You can control have destination object is constructed by adding function:
<DESTINATION-TYPE> CreateDestination(Source source)
{}
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
}
public class Destination
{
public Destination(string input)
{}
public int Number { get; set; }
public string Text { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
private Destination CreateDestination(Source source)
{
return new Destination("input");
}
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
// Your CreateDestination method used.
var result = CreateDestination(source);
result.Number = source.Number;
result.Text = source.Text;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Mapping constructor parameters
MappingGenerator will try to find destination object's constructor that has parameters that can be mapped from source object.
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
}
public class Destination
{
public Destination(string text)
{
Text = text;
}
public int Number { get; set; }
public string Text { get; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
// Parameter 'text' have been mapped to 'source.Text'.
return new Destination(source.Text)
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Mapping init only properties
public class Source
{
public int Number { get; set; }
public string Text { get; set; }
}
public class Destination
{
public int Number { get; set; }
public string Text { get; init; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
// Init only property 'Text' have been mapped to 'source.Text'.
return new Destination()
{
Text = source.Text
};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Number = source.Number;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Reusing generated mappings
MappingGenerator will reuse other mappings.
public class InnerSource
{
public string InnerText { get; set; }
}
public class Source
{
public InnerSource A { get; set; }
public InnerSource B { get; set; }
}
public class InnerDestination
{
public string InnerText { get; set; }
}
public class Destination
{
public InnerDestination A { get; set; }
public InnerDestination B { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
}
[MappingGenerator(typeof(InnerSource), typeof(InnerDestination))]
public partial class InnerMapper
{
}
Generated code (removed redundant parts and added comments for clarity):
// Mapping for InnerSource => InnerDestination
partial class InnerMapper : IMapper<InnerSource, InnerDestination>
{
public Mapper()
{
}
private InnerDestination CreateDestination(Source source)
{
return new InnerDestination()
{};
}
public InnerDestination Map(InnerSource source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.InnerText = source.InnerText;
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
partial class Mapper : IMapper<Source, Destination>
{
private IMapper<InnerSource, InnerDestination> innerMapper;
// Getting InnerMapper.
public Mapper(IMapper<InnerSource, InnerDestination> innerMapper)
{
if (innerMapper == null)
throw new ArgumentNullException(nameof(innerMapper));
this.innerMapper = innerMapper;
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
// Reusing InnerMapper.
result.A = this.innerMapper(source.A);
result.B = this.innerMapper(source.B);
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Custom type converters
You can provide custom type converters by defining function:
<DESTINATION-TYPE> Convert<ANY-SUFFIX>(<SOURCE-TYPE>)
{}
Note. Custom type conversion will be used for all mapping of type <SOURCE-TYPE>
to <DESTINATION-TYPE>
.
public class Source
{
public string Number { get; set; }
}
public class Destination
{
public int Number { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{
private int Convert(string source)
{
return int.Parse(source);
}
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper : IMapper<Source, Destination>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
// Custom type converter called.
result.Number = Convert(source.Number);
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Open generics
MappingGenerator is able to handle unbounded generic types (open generics). In this case your mapper type definition should have number of generic parameters corresponding to number of unbound generic parameters in both source and destinations types.
For example:
[MappingGenerator(typeof(Source<>), typeof(Destination<>))]
public partial class Mapper<TSource, TDestination>
{}
Is equivalent to Source<TSource>
and Destination<TDestination>
[MappingGenerator(typeof(Source<,>), typeof(Destination<,>))]
public partial class Mapper<TSource1, TSource2, TDestination1, TDestination2>
{}
Is equivalent to Source<TSource1, TSource2>
and Destination<TDestination1, TDestination2>
When defining mappers for unbound generic types you need to instruct MappingGenerator how TSource
can be converted to TDestination
(remember, your mapping should be compilable). You can do it by providing type converter method:
[MappingGenerator(typeof(Source<>), typeof(Destination<>))]
public partial class Mapper<TSource, TDestination>
{
// Now mapping generator knows how to convert TSource => TDestination
private TDestination Convert(TSource source)
{
// Some conversion logic.
}
}
Or by adding generic constraints:
[MappingGenerator(typeof(Source<>), typeof(Destination<>))]
public partial class Mapper<TSource, TDestination>
where TSource : TDestination // TSource can be casted implicitly to TDestination
{
}
Complete example:
public class Source<T>
{
public T A { get; set; }
}
public class Destination<T>
{
public T A { get; set; }
}
[MappingGenerator(typeof(Source<>), typeof(Destination<>))]
public partial class Mapper<TSource, TDestination>
{
private TDestination Convert(TSource source)
{
// Some conversion logic.
}
}
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper<TSource, TDestination> : IMapper<TSource, TDestination>
{
public Mapper()
{
}
private Destination<TDestination> CreateDestination(Source<TSource> source)
{
return new Destination<TDestination>()
{};
}
public Destination<TDestination> Map(Source<TSource> source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
// Custom type converter called.
result.A = Convert(source.A);
AfterMap(source, result);
return result;
}
partial void AfterMap(Source source, Destination result);
}
Arrays and collections
You don't need to define separate mapper for collection or array type, MappingGenerator creates them for you.
Source collection types:
IEnumerable<T>
ICollection<T>
IList<T>
Collection<T>
HashSet<T>
List<T>
Arrays
When mapping to an existing collection, the destination collection is cleared first.
public class Source
{
public string Text { get; set; }
}
public class Destination
{
public string Text { get; set; }
}
[MappingGenerator(typeof(Source), typeof(Destination))]
public partial class Mapper
{ }
Generated code (removed redundant parts and added comments for clarity):
partial class Mapper :
IMapper<Source, Destination>,
IMapper<IEnumerable<Source>, List<Destination>>,
IMapper<IEnumerable<Source>, HashSet<Destination>>,
IMapper<IEnumerable<Source>, System.Collections.ObjectModel.Collection<Destination>>,
IMapper<IEnumerable<Source>, Destination[]>
{
public Mapper()
{
}
private Destination CreateDestination(Source source)
{
return new Destination()
{};
}
public Destination Map(Source source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var result = CreateDestination(source);
result.Text = source.Text;
AfterMap(source, result);
return result;
}
// !!! Explicit interface implementation.
List<Destination> IMapper<IEnumerable<Source>, List<Destination>>.Map(IEnumerable<Source> source)
{
source ??= Enumerable.Empty<Source>();
return new List<Destination>(source.Select(Map));
}
// !!! Explicit interface implementation.
HashSet<Destination> IMapper<IEnumerable<Source>, HashSet<Destination>>.Map(IEnumerable<Source> source)
{
source ??= Enumerable.Empty<Source>();
return new HashSet<Destination>(source.Select(Map));
}
// !!! Explicit interface implementation.
Collection<Destination> IMapper<IEnumerable<Source>, Collection<Destination>>.Map(IEnumerable<Source> source)
{
var result = ((IMapper<IEnumerable<Source>, List<Destination>>)this).Map(source);
// Collection is wrapper over IList.
return new Collection<Destination>(result);
}
// !!! Explicit interface implementation.
Destination[] IMapper<IEnumerable<Source>, Destination[]>.Map(IEnumerable<Source> source)
{
source ??= Enumerable.Empty<Source>();
return source.Select(Map).ToArray();
}
partial void AfterMap(Source source, Destination result);
}
Usage:
var source = new Source[] { ... };
// Collection mappers require explicit cast.
var mapper = (IMapper<IEnumerable<Source>, List<Destination>)new Mapper(source);
var result = mapper.Map(source);
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
1.0.0-preview.26 | 131 | 4/19/2022 |
1.0.0-preview.24 | 113 | 4/16/2022 |
1.0.0-preview.23 | 109 | 4/16/2022 |
1.0.0-preview.16 | 115 | 4/16/2022 |
1.0.0-preview.14 | 113 | 4/16/2022 |
1.0.0-preview.13 | 115 | 4/12/2022 |
1.0.0-preview.8 | 117 | 4/11/2022 |
1.0.0-preview.4 | 112 | 4/10/2022 |
1.0.0-preview.2 | 109 | 4/10/2022 |
1.0.0-preview | 114 | 4/10/2022 |
0.0.0-alpha.0.78 | 120 | 4/10/2022 |
0.0.0-alpha.0.77 | 113 | 4/9/2022 |
0.0.0-alpha.0.76 | 115 | 4/9/2022 |
0.0.0-alpha.0.73 | 122 | 4/7/2022 |
0.0.0-alpha.0.69 | 109 | 4/1/2022 |
0.0.0-alpha.0.64 | 106 | 4/1/2022 |
0.0.0-alpha.0.62 | 116 | 3/31/2022 |
0.0.0-alpha.0.53 | 108 | 3/30/2022 |
0.0.0-alpha.0.50 | 102 | 3/30/2022 |
0.0.0-alpha.0.47 | 106 | 3/30/2022 |
0.0.0-alpha.0.30 | 116 | 3/28/2022 |
0.0.0-alpha.0.29 | 114 | 3/28/2022 |
0.0.0-alpha.0.28 | 112 | 3/28/2022 |
0.0.0-alpha.0.27 | 115 | 3/28/2022 |
0.0.0-alpha.0.26 | 111 | 3/27/2022 |
0.0.0-alpha.0.25 | 111 | 3/27/2022 |
0.0.0-alpha.0.16 | 116 | 3/26/2022 |