CustomAutoAdapterMapper 1.0.5

dotnet add package CustomAutoAdapterMapper --version 1.0.5
                    
NuGet\Install-Package CustomAutoAdapterMapper -Version 1.0.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CustomAutoAdapterMapper" Version="1.0.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CustomAutoAdapterMapper" Version="1.0.5" />
                    
Directory.Packages.props
<PackageReference Include="CustomAutoAdapterMapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CustomAutoAdapterMapper --version 1.0.5
                    
#r "nuget: CustomAutoAdapterMapper, 1.0.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CustomAutoAdapterMapper@1.0.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CustomAutoAdapterMapper&version=1.0.5
                    
Install as a Cake Addin
#tool nuget:?package=CustomAutoAdapterMapper&version=1.0.5
                    
Install as a Cake Tool

CustomAutoAdapterMapper

NuGet .NET Standard 2.0

A lightweight, flexible JSON-to-object mapper for C# that handles third-party API responses with mismatched property names and nested structures without requiring contract definitions.

๐ŸŽฏ Problem Statement

In organizations that integrate with multiple external systems:

  • Strongly-typed languages like C# make it difficult to map unknown or dynamic JSON structures at runtime
  • Creating contracts for every third-party API is time-consuming and requires development work for each new integration
  • Property mismatches between external APIs and internal models require custom mapping logic
  • Nested properties in JSON need to be flattened or mapped to different structures

CustomAutoAdapterMapper solves these challenges by providing a flexible, configuration-driven approach to mapping JSON strings to strongly-typed C# objects.


๐Ÿ“ฆ Installation

dotnet add package CustomAutoAdapterMapper

Or via NuGet Package Manager:

Install-Package CustomAutoAdapterMapper

๐Ÿš€ Quick Start

Basic Usage: Direct Property Mapping

When JSON property names match your C# class properties:

using CustomAutoAdapterMapper;

var jsonResponse = await httpClient.GetStringAsync("https://api.example.com/data");
var destinationCollection = new List<MyClass>();

var result = jsonResponse.MapCollection(destinationCollection, options =>
{
    options.RootKey = "entries"; // JSON property containing the array
});

Custom Property Mapping

When JSON property names differ from your C# class properties:

var result = jsonResponse.MapCollection(destinationCollection, options =>
{
    options.RootKey = "entries";
    options.Mappings = new Dictionary<string, string>
    {
        { "MyProperty", "TheirProperty" },        // Map TheirProperty -> MyProperty
        { "Description", "desc" },                // Map desc -> Description
        { "AuthType", "authentication_type" }     // Map authentication_type -> AuthType
    };
});

๐Ÿ“– Comprehensive Examples

Example 1: Simple Mapping with Variations

JSON Response from https://api.publicapis.org/entries:

{
    "count": 1427,
    "entries": [
        {
            "API": "AdoptAPet",
            "Description": "Resource to help get pets adopted",
            "Auth": "apiKey",
            "HTTPS": true,
            "Cors": "yes",
            "Link": "https://www.adoptapet.com/public/apis/pet_list.html",
            "Category": "Animals"
        },
        {
            "API": "Axolotl",
            "Description": "Collection of axolotl pictures and facts",
            "Auth": "",
            "HTTPS": true,
            "Cors": "no",
            "Link": "https://theaxolotlapi.netlify.app/",
            "Category": "Animals"
        }
    ]
}

Your C# Model (with different property names):

public class ApiEntry
{
    public string API { get; set; }
    public string DescriptionText { get; set; }  // Different name
    public string AuthType { get; set; }         // Different name
    public bool HTTPS { get; set; }
    public string Cors { get; set; }
    public string Link { get; set; }
    public string CategoryName { get; set; }     // Different name
}

Mapping Code:

var destinationCollection = new List<ApiEntry>();
var result = jsonResponse.MapCollection(destinationCollection, options =>
{
    options.RootKey = "entries";
    options.Mappings = new Dictionary<string, string>
    {
        { "DescriptionText", "Description" },
        { "AuthType", "Auth" },
        { "CategoryName", "Category" }
    };
});

Example 2: Nested Property Mapping (Dot Notation)

Map deeply nested JSON properties to flat C# properties using dot notation.

JSON Response:

{
    "entries": [
        {
            "API": "AdoptAPet",
            "Description": "Resource to help get pets adopted",
            "work": {
                "reportsToIdInCompany": 64,
                "employeeIdInCompany": 140,
                "reportsTo": {
                    "email": "manager@company.com",
                    "name": "John Doe"
                }
            }
        }
    ]
}

Your C# Model (flattened structure):

public class Employee
{
    public string API { get; set; }
    public string Description { get; set; }
    public int ManagerId { get; set; }
    public int EmployeeId { get; set; }
    public string ManagerEmail { get; set; }
    public string ManagerName { get; set; }
}

Mapping Code:

var employees = new List<Employee>();
var result = jsonResponse.MapCollection(employees, options =>
{
    options.RootKey = "entries";
    options.Mappings = new Dictionary<string, string>
    {
        { "ManagerId", "work.reportsToIdInCompany" },      // Nested property
        { "EmployeeId", "work.employeeIdInCompany" },      // Nested property
        { "ManagerEmail", "work.reportsTo.email" },        // Deeply nested
        { "ManagerName", "work.reportsTo.name" }           // Deeply nested
    };
});

Example 3: Updating Existing Collections

Use ItemKey to update an existing collection instead of creating a new one.

Scenario: You have a pre-populated list and want to update specific items based on a unique identifier.

// Pre-populated collection
var existingApis = new List<ApiEntry>
{
    new ApiEntry { API = "AdoptAPet", DescriptionText = "Old description" },
    new ApiEntry { API = "Axolotl", DescriptionText = "Old description" }
};

// Update the collection with fresh data from the API
var result = jsonResponse.MapCollection(existingApis, options =>
{
    options.RootKey = "entries";
    options.ItemKey = "API";  // Match items by the "API" property
    options.Mappings = new Dictionary<string, string>
    {
        { "DescriptionText", "Description" },
        { "AuthType", "Auth" }
    };
});

// Only mapped properties are updated; other properties remain unchanged

โš™๏ธ Configuration Options

Option Class Properties

Property Type Required Description
RootKey string โœ… Yes The JSON property name that contains the array/collection to map.
Mappings Dictionary<string, string> โš ๏ธ Optional Custom property mappings. Key = your C# property name, Value = JSON property path (supports dot notation for nested properties).
ItemKey string โš ๏ธ Conditional Unique identifier property name. Required when updating an existing non-empty collection. Used to match items between JSON and your collection.

Configuration Details

RootKey
  • Identifies which JSON property contains the array of items to map
  • Must be a valid property in the root JSON object
  • Throws RootKeyOptionNullException if not provided
  • Throws RootKeyPropertyNullException if the property doesn't exist in the JSON
Mappings
  • Optional dictionary for custom property mappings
  • Key: Your C# class property name
  • Value: JSON property path (supports nested properties with dot notation)
  • If not provided, the mapper attempts direct property name matching

Examples:

options.Mappings = new Dictionary<string, string>
{
    { "MyProperty", "their_property" },              // Simple mapping
    { "Email", "user.contact.email" },              // Nested property
    { "ManagerId", "employee.reports_to.id" }       // Deeply nested
};
ItemKey
  • Specifies a unique identifier property for matching items
  • Required when:
    • Updating an existing collection (non-empty List<T>)
    • You want to preserve existing items and only update mapped properties
  • Not required when:
    • Creating a new collection from scratch (empty or null list)
  • Throws ItemKeyOptionNullException if required but not provided

๐Ÿ” How It Works

Mapping Behavior

The mapper operates in two modes:

1. Create Mode (Empty/Null Collection)

When you pass an empty or null collection:

  • Creates new instances of your type T
  • Maps all matching properties automatically
  • Applies custom mappings from options.Mappings
  • Adds items to your collection
var newCollection = new List<MyClass>();  // Empty collection
jsonResponse.MapCollection(newCollection, options => {
    options.RootKey = "data";
    // ItemKey not required
});
2. Update Mode (Existing Collection)

When you pass a non-empty collection:

  • Matches items using ItemKey
  • Only updates properties defined in options.Mappings
  • Preserves all other properties in existing items
  • Does not add new items
var existingCollection = GetExistingData();  // Non-empty collection
jsonResponse.MapCollection(existingCollection, options => {
    options.RootKey = "data";
    options.ItemKey = "Id";  // Required!
    options.Mappings = new Dictionary<string, string> { /* ... */ };
});

Type Conversion

  • The mapper uses Newtonsoft.Json for type conversion
  • Automatically converts JSON types to C# property types
  • Supports:
    • Primitives (string, int, bool, decimal, etc.)
    • Nullable types (int?, DateTime?, etc.)
    • Complex types (nested objects)
    • Collections and arrays

โš ๏ธ Exception Handling

The library throws custom exceptions for common configuration errors:

Exception When Thrown Solution
JsonContentException The provided string is not valid JSON Ensure the input string is valid JSON
RootKeyOptionNullException RootKey is not provided in options Set options.RootKey to the JSON array property name
RootKeyPropertyNullException RootKey doesn't exist in the JSON object Verify the JSON structure and RootKey value
ItemKeyOptionNullException ItemKey is required but not provided (when updating existing collections) Set options.ItemKey to a unique identifier property
JsonReaderException JSON cannot be parsed as an object (e.g., it's a raw array) Ensure JSON is an object with a root property containing the array

Error Handling Example

try
{
    var result = jsonResponse.MapCollection(collection, options =>
    {
        options.RootKey = "entries";
    });
}
catch (JsonContentException ex)
{
    // Invalid JSON string
    Console.WriteLine($"Invalid JSON: {ex.Message}");
}
catch (RootKeyPropertyNullException ex)
{
    // RootKey doesn't exist in JSON
    Console.WriteLine($"Property not found: {ex.Message}");
}
catch (ItemKeyOptionNullException ex)
{
    // ItemKey required but not provided
    Console.WriteLine($"Missing ItemKey: {ex.Message}");
}

๐ŸŽ“ Advanced Usage

Complex Nested Structures

You can map multiple levels of nesting:

options.Mappings = new Dictionary<string, string>
{
    { "Street", "address.street" },
    { "City", "address.city" },
    { "ZipCode", "address.location.zipCode" },
    { "Country", "address.location.country.name" },
    { "CountryCode", "address.location.country.code" }
};

Combining Direct and Custom Mappings

Properties not in Mappings are mapped directly by name:

public class Product
{
    public string Id { get; set; }           // Mapped directly from JSON "Id"
    public string Name { get; set; }         // Mapped directly from JSON "Name"
    public decimal Cost { get; set; }        // Custom mapping required
}

options.Mappings = new Dictionary<string, string>
{
    { "Cost", "pricing.unitPrice" }  // Only Cost needs custom mapping
};
// Id and Name are automatically mapped if they exist in the JSON

Type Safety with Nullability

The mapper handles null values gracefully:

public class SafeModel
{
    public string Required { get; set; }     // Will be null if not in JSON
    public int? OptionalNumber { get; set; } // Nullable type
    public DateTime? OptionalDate { get; set; }
}

๐Ÿงช Testing

The library includes comprehensive unit tests covering:

  • โœ… Basic property mapping
  • โœ… Custom property mappings
  • โœ… Nested property mapping with dot notation
  • โœ… Collection creation (empty destination)
  • โœ… Collection updates (existing destination with ItemKey)
  • โœ… Exception scenarios
  • โœ… Type conversions

Run tests:

dotnet test

๐Ÿ› ๏ธ Technical Details

  • Target Framework: .NET Standard 2.0
  • Dependencies: Newtonsoft.Json (>= 13.0.3)
  • Namespace: CustomAutoAdapterMapper
  • Primary Method: MapCollection<T> (extension method on string)

๐Ÿ“ Best Practices

  1. Always set RootKey - It's required and identifies your data array
  2. Use ItemKey for updates - When updating existing collections, always specify a unique identifier
  3. Leverage dot notation - For nested properties, use "parent.child.property" syntax
  4. Handle exceptions - Wrap mapping calls in try-catch for production code
  5. Validate JSON first - Ensure external API responses are valid before mapping
  6. Use nullable types - For optional properties, use nullable types (int?, DateTime?, etc.)

๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


๐Ÿ“„ License

See license.txt for details.



๐Ÿ“ง Support

For issues, questions, or feature requests, please open an issue on GitHub

Product 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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.5 156 10/24/2025
1.0.4 114 10/24/2025
1.0.3 422 6/28/2024
1.0.2 168 4/29/2024
1.0.1 227 3/13/2024
1.0.0 217 3/13/2024

v1.0.5: Fixed nested property mapping with dot notation in both create and update modes. Added null check for incomingRecord. Improved documentation with comprehensive examples.