diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
new file mode 100644
index 0000000..c3e06a2
--- /dev/null
+++ b/.github/workflows/build-and-test.yml
@@ -0,0 +1,60 @@
+name: Build and Test
+
+on:
+ push:
+ branches: [ main, vNext, develop ]
+ pull_request:
+ branches: [ main, vNext ]
+
+jobs:
+ build:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup NuGet
+ uses: nuget/setup-nuget@v2
+ with:
+ nuget-version: 'latest'
+
+ - name: Setup MSBuild
+ uses: microsoft/setup-msbuild@v2
+
+ - name: Restore NuGet packages
+ run: nuget restore
+
+ - name: Build solution
+ run: msbuild /p:Configuration=Release /p:Platform="Any CPU" /verbosity:minimal
+
+ - name: Run tests
+ shell: pwsh
+ run: |
+ $vstest = "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
+ if (-not (Test-Path $vstest)) {
+ $vstest = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
+ }
+ if (-not (Test-Path $vstest)) {
+ Write-Host "Installing xunit console runner..."
+ nuget install xunit.runner.console -Version 2.9.3 -OutputDirectory packages
+ $xunit = "packages\xunit.runner.console.2.9.3\tools\net462\xunit.console.exe"
+ & $xunit "Xrm.Json.Serialization.Tests\bin\Release\Xrm.Json.Serialization.Tests.dll" -xml TestResults.xml
+ } else {
+ & $vstest "Xrm.Json.Serialization.Tests\bin\Release\Xrm.Json.Serialization.Tests.dll" /TestAdapterPath:"packages\xunit.runner.visualstudio.2.5.3\build\net462" /Logger:console
+ }
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: TestResults/
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-artifacts
+ path: |
+ Xrm.Json.Serialization\bin\Release\*.dll
+ Xrm.Json.Serialization\bin\Release\*.pdb
diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml
new file mode 100644
index 0000000..7e3a538
--- /dev/null
+++ b/.github/workflows/publish-nuget.yml
@@ -0,0 +1,88 @@
+name: Publish to NuGet
+
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+
+jobs:
+ publish:
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Extract version from tag
+ id: get_version
+ shell: pwsh
+ run: |
+ $tag = "${{ github.ref_name }}"
+ $version = $tag -replace '^v', ''
+ echo "VERSION=$version" >> $env:GITHUB_OUTPUT
+ echo "Version: $version"
+
+ - name: Setup NuGet
+ uses: nuget/setup-nuget@v2
+ with:
+ nuget-version: 'latest'
+
+ - name: Setup MSBuild
+ uses: microsoft/setup-msbuild@v2
+
+ - name: Restore NuGet packages
+ run: nuget restore
+
+ - name: Build solution in Release mode
+ run: msbuild /p:Configuration=Release /p:Platform="Any CPU" /verbosity:minimal
+
+ - name: Run tests
+ shell: pwsh
+ run: |
+ $vstest = "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
+ if (-not (Test-Path $vstest)) {
+ $vstest = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
+ }
+ if (-not (Test-Path $vstest)) {
+ Write-Host "Installing xunit console runner..."
+ nuget install xunit.runner.console -Version 2.9.3 -OutputDirectory packages
+ $xunit = "packages\xunit.runner.console.2.9.3\tools\net462\xunit.console.exe"
+ & $xunit "Xrm.Json.Serialization.Tests\bin\Release\Xrm.Json.Serialization.Tests.dll" -xml TestResults.xml
+ } else {
+ & $vstest "Xrm.Json.Serialization.Tests\bin\Release\Xrm.Json.Serialization.Tests.dll" /TestAdapterPath:"packages\xunit.runner.visualstudio.2.5.3\build\net462" /Logger:console
+ }
+
+ - name: Update .nuspec version
+ shell: pwsh
+ run: |
+ $version = "${{ steps.get_version.outputs.VERSION }}"
+ $nuspecPath = "Xrm.Json.Serialization.nuspec"
+ $content = Get-Content $nuspecPath -Raw
+ $content = $content -replace '[\d\.]+', "$version"
+ Set-Content $nuspecPath $content
+ echo "Updated .nuspec to version $version"
+
+ - name: Pack NuGet package
+ run: nuget pack Xrm.Json.Serialization.nuspec -Properties Configuration=Release
+
+ - name: Push to NuGet
+ run: nuget push *.nupkg -Source https://api.nuget.org/v3/index.json -ApiKey ${{ secrets.NUGET_API_KEY }} -SkipDuplicate
+
+ - name: Upload NuGet package as artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package
+ path: '*.nupkg'
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: '*.nupkg'
+ body: |
+ ## Release ${{ steps.get_version.outputs.VERSION }}
+
+ See [CHANGELOG.md](https://github.com/imranakram/Xrm.Json.Serialization/blob/main/CHANGELOG.md) for details.
+ draft: false
+ prerelease: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2db7fbf
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,102 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project uses **Calendar Versioning (CalVer)**: `1.YYYY.MM.patch`
+
+## [1.2026.3.0] - 2026-03-11
+
+### Added
+- **AliasedValue converter** - Critical for FetchXML queries with linked entities
+- **OptionSetValueCollection converter** - Support for multi-select picklists
+- **BooleanManagedProperty converter** - Support for managed property fields
+- **EntitySerializer helper class** - Simplified serialization/deserialization API
+- 27 new comprehensive unit tests for new converters
+- Plugin usage examples in README (logging, Service Bus, webhooks, FetchXML, caching)
+- External integration examples (console apps, Azure Functions, Web API)
+- Deployment guidance for plugins (ILMerge vs NuGet package approaches)
+
+### Fixed
+- **[#20]** String escaping issue with double quotes and special characters in `BasicsConverter`
+- Removed unsafe `CodeDomProvider` usage that caused JSON escaping problems
+- Proper handling of backslashes, newlines, and tabs in strings
+
+### Changed
+- **Reverted target framework** from .NET Framework 4.8 to **4.6.2** for plugin compatibility
+- Updated `XrmContractResolver` to include new converters
+- Enhanced `EntityConverter` to support deserializing new data types
+- Updated xunit.runner.visualstudio from 3.1.5 to 2.5.3 (net462 compatibility)
+- Improved README with comprehensive plugin and external integration examples
+- Updated NuGet package metadata with better description and tags
+
+## [2.0.0] - 2025-03-10
+
+### Changed
+- **BREAKING:** Changed root namespace from `Innofactor.Xrm.Json.Serialization` to `Xrm.Json.Serialization`
+- **BREAKING:** Assembly name changed from `Innofactor.Xrm.Json.Serialization.dll` to `Xrm.Json.Serialization.dll`
+- Upgraded Newtonsoft.Json from 13.0.1 to 13.0.4
+- Upgraded System.Text.Json from 6.0.6 to 6.0.10
+- Upgraded xUnit from 2.4.2 to 2.9.3
+- Updated assembly metadata (copyright 2025)
+- Improved NuGet package metadata with better description and tags
+- Project structure: moved projects from `src\` folder to root level
+
+### Added
+- Comprehensive README.md with usage examples and documentation
+- CHANGELOG.md for version history tracking
+
+## [1.0.0] - 2021
+
+### Added
+- Initial release
+- Support for Dynamics 365/CRM entity serialization
+- Converters for Entity, EntityCollection, EntityReference
+- Converters for OptionSetValue, Money, DateTime, Guid
+- BasicsConverter for primitive types (string, int, double, decimal)
+- XrmContractResolver for automatic converter selection
+- Comprehensive unit test coverage
+
+### Supported Types
+- `Entity` - Full entity serialization with attributes
+- `EntityCollection` - Collections of entities
+- `EntityReference` - Lookup/reference fields
+- `OptionSetValue` - Picklist values
+- `Money` - Currency values
+- `DateTime` - Date/time with timezone support
+- `Guid` - Unique identifiers
+- Basic CLR types (string, int, long, float, double, decimal, object)
+
+---
+
+---
+
+## Versioning
+
+This project uses **Calendar Versioning (CalVer)**: `1.YYYY.MM.patch`
+
+Format: `Major.Year.Month.Patch`
+
+Examples:
+- `1.2026.3.0` - March 2026, first release
+- `1.2026.3.1` - March 2026, patch release
+- `1.2022.10.1` - October 2022
+
+---
+
+## Version Comparison
+
+| Version | Date | .NET Framework | Key Changes |
+|---------|------|----------------|-------------|
+| **1.2026.3.0** | 2026-03-11 | 4.6.2 | AliasedValue, multi-select, plugin examples, bug #20 fix |
+| 1.2022.10.1 | 2022-10 | 4.6.2 | Namespace change to Xrm.Json.Serialization |
+| 1.0.0 | 2021 | 4.6.2 | Initial release |
+
+---
+
+## Links
+
+- [NuGet Package](https://www.nuget.org/packages/Xrm.Json.Serialization/)
+- [GitHub Repository](https://github.com/imranakram/Xrm.Json.Serialization)
+- [Issue Tracker](https://github.com/imranakram/Xrm.Json.Serialization/issues)
+- [Documentation](https://github.com/imranakram/Xrm.Json.Serialization/blob/main/README.md)
diff --git a/GITHUB-ACTIONS-SETUP.md b/GITHUB-ACTIONS-SETUP.md
new file mode 100644
index 0000000..b575e4d
--- /dev/null
+++ b/GITHUB-ACTIONS-SETUP.md
@@ -0,0 +1,130 @@
+# GitHub Actions Setup Guide
+
+This project uses **GitHub Actions** for CI/CD instead of AppVeyor.
+
+## đź”§ Setup Instructions
+
+### 1. Update Git Remote (Already Done)
+The repository was moved from `Biznamics` to `imranakram`:
+
+```bash
+git remote set-url origin https://github.com/imranakram/Xrm.Json.Serialization
+git remote -v
+```
+
+### 2. Enable GitHub Actions
+GitHub Actions is enabled by default for public repositories. The workflows are already configured in `.github/workflows/`.
+
+### 3. Configure NuGet API Key Secret
+
+To enable automatic NuGet publishing on release:
+
+1. Go to https://www.nuget.org/account/apikeys
+2. Create a new API key with:
+ - **Key Name:** `GitHub-Actions-Xrm.Json.Serialization`
+ - **Glob Pattern:** `Xrm.Json.Serialization`
+ - **Scopes:** Push new packages and package versions
+3. Copy the generated API key
+4. Go to your GitHub repository → **Settings** → **Secrets and variables** → **Actions**
+5. Click **New repository secret**
+6. Name: `NUGET_API_KEY`
+7. Value: Paste your NuGet API key
+8. Click **Add secret**
+
+### 4. Workflows
+
+#### **Build and Test** (`.github/workflows/build-and-test.yml`)
+- **Triggers:** Push to `main`, `vNext`, `develop` branches; Pull requests
+- **Actions:**
+ - Restore NuGet packages
+ - Build solution in Release mode
+ - Run all unit tests
+ - Upload test results and build artifacts
+
+#### **Publish to NuGet** (`.github/workflows/publish-nuget.yml`)
+- **Triggers:** Push tags matching `v*.*.*` (e.g., `v1.2026.3.0`)
+- **Actions:**
+ - Extract version from Git tag
+ - Build and test
+ - Update .nuspec with version from tag
+ - Pack NuGet package
+ - Publish to NuGet.org
+ - Create GitHub Release with package attached
+
+### 5. Publishing a Release
+
+When you're ready to publish version **1.2026.3.0** (CalVer format):
+
+```bash
+# Commit all changes
+git add .
+git commit -m "Release v1.2026.3.0 - Add AliasedValue support and fix #20"
+
+# Create and push tag (CalVer: 1.YYYY.MM.patch)
+git tag v1.2026.3.0
+git push origin vNext
+git push origin v1.2026.3.0
+```
+
+This will automatically:
+1. Trigger the publish workflow
+2. Extract version from tag (v1.2026.3.0 → 1.2026.3.0)
+3. Update .nuspec version dynamically
+4. Build and test the solution
+5. Create the NuGet package
+6. Push to NuGet.org
+7. Create GitHub Release with artifacts
+
+### 6. Monitoring
+
+- View workflow runs: https://github.com/imranakram/Xrm.Json.Serialization/actions
+- Check build status badge in README.md
+- NuGet publish status will appear in the workflow logs
+
+## 🗑️ AppVeyor Migration
+
+The old `appveyor.yml` has been marked as deprecated and kept for reference only. You can:
+
+**Option 1:** Delete it entirely
+```bash
+git rm appveyor.yml
+git commit -m "Remove deprecated AppVeyor configuration"
+```
+
+**Option 2:** Keep it for reference (current state)
+
+If you decide to keep using AppVeyor instead:
+1. Update the AppVeyor project settings at https://ci.appveyor.com
+2. The configuration has been updated to work with the new structure
+3. Update the NuGet API key in AppVeyor settings
+
+## 📊 Benefits of GitHub Actions
+
+| Feature | GitHub Actions | AppVeyor |
+|---------|---------------|----------|
+| **Cost** | Free (public repos) | Free tier limited |
+| **Integration** | Native GitHub | External service |
+| **Setup** | No account needed | Separate account |
+| **Marketplace** | 10,000+ actions | Limited |
+| **Build Minutes** | 2,000/month free | 1 concurrent job |
+| **Artifacts** | 500MB storage | Limited |
+| **Modern** | Active development | Legacy |
+
+## 🎯 Recommended Next Steps
+
+1. âś… **Update Git remote:** `git remote set-url origin https://github.com/imranakram/Xrm.Json.Serialization`
+2. âś… **Manually update version numbers** in `.nuspec` and `CHANGELOG.md` to use **CalVer format** (`1.2026.3.0`)
+3. âś… Test the GitHub Actions workflows by pushing a commit to vNext branch
+4. âś… Add NuGet API key secret to GitHub (see step 3 above)
+5. âś… When ready, create release tag: `git tag v1.2026.3.0 && git push origin v1.2026.3.0`
+6. âś… Monitor workflow run at: https://github.com/imranakram/Xrm.Json.Serialization/actions
+
+## 📝 Notes
+
+- GitHub Actions builds on Windows (required for .NET Framework 4.6.2)
+- Test results are preserved as artifacts
+- NuGet packages are created on every tag push
+- Build status badge is already added to README.md
+- **Version format:** CalVer `1.YYYY.MM.patch` (e.g., `1.2026.3.0` for March 2026)
+- Git tag format: `v1.2026.3.0` (with 'v' prefix)
+- The workflow automatically extracts version from tag and updates .nuspec
diff --git a/README.md b/README.md
index eef54a1..7d0d73e 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,585 @@
# Xrm.Json.Serialization
-Library provides simple JSON serialization / deserialization functionality that will process MS Dynamics CRM entities nicely. It provides simple and compact output and requires minimum configuration. Library uses [JSON.NET](https://www.newtonsoft.com/json/) as an engine for its operation.
+Compact JSON serialization library for Microsoft Dynamics 365/CRM/Dataverse entities using Newtonsoft.Json.
-To enable MS Dynamics CRM specific rules, custom JSON.NET converters need to be registered before performing serialization / deserialization:
+[](https://github.com/imranakram/Xrm.Json.Serialization/actions/workflows/build-and-test.yml)
+[](https://www.nuget.org/packages/Xrm.Json.Serialization)
+[](https://opensource.org/licenses/MIT)
-```c#
-JsonConvert.DefaultSettings = () => new JsonSerializerSettings
+## Features
+
+- âś… **Compact JSON format** optimized for Dynamics 365 entities
+- âś… **Complete data type support** for all major CRM SDK types including AliasedValue
+- âś… **Custom converters** for CRM-specific types (EntityReference, OptionSetValue, Money, etc.)
+- âś… **Bidirectional** serialization and deserialization
+- âś… **Easy integration** with existing Dynamics 365 SDK projects
+- âś… Built for **.NET Framework 4.6.2** - Plugin compatible!
+- âś… **FetchXML support** - Properly handles linked entities with AliasedValue
+- âś… **Helper class** - EntitySerializer for simplified usage
+
+## Supported Data Types
+
+This library provides custom JSON converters for the following Dynamics 365 data types:
+
+| Data Type | CRM SDK Type | Description | Converter |
+|-----------|--------------|-------------|-----------|
+| Entity | `Entity` | Complete entity records | `EntityConverter` |
+| Entity Reference | `EntityReference` | References to related records | `EntityReferenceConverter` |
+| Entity Collection | `EntityCollection` | Multiple entity records | `EntityCollectionConverter` |
+| **Aliased Value** | `AliasedValue` | **FetchXML linked entity values** | `AliasedValueConverter` |
+| Option Set | `OptionSetValue` | Picklist/Choice fields | `OptionSetConverter` |
+| **Option Set Collection** | `OptionSetValueCollection` | **Multi-select picklists** | `OptionSetValueCollectionConverter` |
+| **Boolean Managed Property** | `BooleanManagedProperty` | **Managed properties** | `BooleanManagedPropertyConverter` |
+| Money | `Money` | Currency fields | `MoneyConverter` |
+| Guid | `Guid` | Unique identifiers | `GuidConverter` |
+| DateTime | `DateTime` | Date and time fields | `DateTimeConverter` |
+| Basic Types | `string`, `int`, `double`, `decimal` | Standard primitive types | `BasicsConverter` |
+
+## Installation
+
+### NuGet Package Manager
+```powershell
+Install-Package Xrm.Json.Serialization
+```
+
+### .NET CLI
+```bash
+dotnet add package Xrm.Json.Serialization
+```
+
+### Package Reference
+```xml
+
+```
+
+## Usage
+
+### Basic Serialization
+
+```csharp
+using Xrm.Json.Serialization;
+using Microsoft.Xrm.Sdk;
+using Newtonsoft.Json;
+
+// Create a Dynamics 365 entity
+var account = new Entity("account", Guid.NewGuid());
+account["name"] = "Contoso Ltd";
+account["revenue"] = new Money(1000000m);
+account["industrycode"] = new OptionSetValue(1);
+account["parentaccountid"] = new EntityReference("account", Guid.NewGuid());
+account["createdon"] = DateTime.UtcNow;
+
+// Configure JSON serializer with XRM contract resolver
+var settings = new JsonSerializerSettings
{
- Converters = new List()
- {
- new EntityCollectionConverter(),
- new EntityConverter(),
- new EntityReferenceConverter(),
- new MoneyConverter(),
- new OptionSetConvertor()
+ ContractResolver = new XrmContractResolver(),
+ Formatting = Formatting.Indented
+};
+
+// Serialize to JSON
+string json = JsonConvert.SerializeObject(account, settings);
+Console.WriteLine(json);
+```
+
+### Deserialization
+
+```csharp
+using Xrm.Json.Serialization;
+using Microsoft.Xrm.Sdk;
+using Newtonsoft.Json;
+
+string json = @"{
+ ""_reference"": ""account:12345678-1234-1234-1234-123456789012"",
+ ""name"": ""Contoso Ltd"",
+ ""revenue"": {
+ ""_money"": 1000000
+ },
+ ""industrycode"": {
+ ""_option"": 1
}
+}";
+
+var settings = new JsonSerializerSettings
+{
+ ContractResolver = new XrmContractResolver()
};
+
+// Deserialize from JSON
+var account = JsonConvert.DeserializeObject(json, settings);
+Console.WriteLine($"Account: {account["name"]}");
+Console.WriteLine($"Revenue: {((Money)account["revenue"]).Value}");
+```
+
+### Entity Collection Serialization
+
+```csharp
+using Xrm.Json.Serialization;
+using Microsoft.Xrm.Sdk;
+using Newtonsoft.Json;
+
+var collection = new EntityCollection();
+
+collection.Entities.Add(new Entity("account", Guid.NewGuid())
+{
+ ["name"] = "Account 1"
+});
+
+collection.Entities.Add(new Entity("account", Guid.NewGuid())
+{
+ ["name"] = "Account 2"
+});
+
+var settings = new JsonSerializerSettings
+{
+ ContractResolver = new XrmContractResolver(),
+ Formatting = Formatting.Indented
+};
+
+string json = JsonConvert.SerializeObject(collection, settings);
+```
+
+### Individual Converter Usage
+
+You can also use individual converters directly:
+
+```csharp
+// Entity Reference
+var reference = new EntityReference("contact", Guid.NewGuid());
+var json = JsonConvert.SerializeObject(reference, new EntityReferenceConverter());
+
+// Money
+var money = new Money(50000m);
+var json = JsonConvert.SerializeObject(money, new MoneyConverter());
+
+// OptionSet
+var optionSet = new OptionSetValue(100000001);
+var json = JsonConvert.SerializeObject(optionSet, new OptionSetConverter());
+```
+
+### Helper Class for Easy Serialization
+
+For simplified usage, especially in plugins:
+
+```csharp
+using Xrm.Json.Serialization;
+using Microsoft.Xrm.Sdk;
+
+// Serialize
+var entity = new Entity("account", Guid.NewGuid());
+entity["name"] = "Contoso";
+
+string json = EntitySerializer.Serialize(entity);
+string indentedJson = EntitySerializer.Serialize(entity, indented: true);
+
+// Deserialize
+var deserializedEntity = EntitySerializer.DeserializeEntity(json);
```
-When all custom converters are registered, it's possible to supply entity to the JSON.NET engine:
+## Plugin Usage
-```c#
-// Creating dummy entity with one OptionSet attribute
-var entity = new Entity("test", Guid.NewGuid());
-entity.Attributes.Add("attribute1", new OptionSetValue(1));
+This library is **compatible with Dynamics 365 plugins** (targets .NET Framework 4.6.2), but requires special deployment considerations:
-// Performing conversion
-var json = JsonConvert.SerializeObject(entity, Formatting.Indented);
+### ⚠️ Plugin Deployment Requirements
+
+Since Dynamics 365 plugins run in an isolated sandbox, you have two options for deployment:
+
+#### Option 1: ILMerge (Traditional)
+Merge this library and Newtonsoft.Json into your plugin assembly:
+
+```xml
+
+
+
+
+```
+
+Build script:
+```powershell
+ILMerge.exe /out:MyPlugin.Merged.dll MyPlugin.dll Xrm.Json.Serialization.dll Newtonsoft.Json.dll /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319
+```
+
+#### Option 2: NuGet Plugin Package (Modern - Recommended)
+Use the Power Platform build tools to create a plugin package with dependencies:
+
+```xml
+
+
+
+
+
+
+ $(MSBuildExtensionsPath)\Microsoft\PowerApps-Targets
+ true
+
+```
+
+This creates a `.nupkg` file that bundles all dependencies for deployment to Dynamics 365 Online.
+
+> **Note:** For **on-premises** deployments, you can register assemblies individually, but online requires one of the above approaches.
+
+### Logging and Diagnostics
+
+```csharp
+public class MyPlugin : IPlugin
+{
+ public void Execute(IServiceProvider serviceProvider)
+ {
+ var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
+ var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
+
+ // Log entity snapshot for debugging
+ var preImage = context.PreEntityImages["PreImage"];
+ var json = EntitySerializer.Serialize(preImage, indented: true);
+ tracingService.Trace($"Pre-Image:\n{json}");
+ }
+}
```
-Resulting JSON will look like following:
+### Azure Service Bus Integration
+```csharp
+public class SendToServiceBusPlugin : IPlugin
+{
+ public void Execute(IServiceProvider serviceProvider)
+ {
+ var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
+ var entity = (Entity)context.InputParameters["Target"];
+
+ // Serialize and send to Azure Service Bus
+ var json = EntitySerializer.Serialize(entity);
+ var message = new ServiceBusMessage(json);
+
+ // Send to your service bus (async pattern)
+ await serviceBusClient.SendMessageAsync(message);
+ }
+}
+```
+
+### Webhook Notifications
+
+```csharp
+public class WebhookPlugin : IPlugin
+{
+ public void Execute(IServiceProvider serviceProvider)
+ {
+ var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
+ var entity = (Entity)context.InputParameters["Target"];
+
+ // Send entity data to external webhook
+ var json = EntitySerializer.Serialize(entity);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ using (var client = new HttpClient())
+ {
+ var response = client.PostAsync("https://your-webhook.com/api/updates", content).Result;
+ }
+ }
+}
+```
+
+### FetchXML with Linked Entities
+
+**AliasedValue support is critical for FetchXML queries with linked entities:**
+
+```csharp
+public class FetchXmlPlugin : IPlugin
+{
+ public void Execute(IServiceProvider serviceProvider)
+ {
+ var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
+ var service = serviceFactory.CreateOrganizationService(context.UserId);
+
+ var fetchXml = @"
+
+
+
+
+
+
+
+
+ ";
+
+ var results = service.RetrieveMultiple(new FetchExpression(fetchXml));
+
+ // Serialize results including aliased values from linked entities
+ var json = EntitySerializer.Serialize(results, indented: true);
+
+ // Process each entity with aliased values
+ foreach (var entity in results.Entities)
+ {
+ if (entity.Contains("contact.fullname"))
+ {
+ var aliasedValue = (AliasedValue)entity["contact.fullname"];
+ var contactName = aliasedValue.Value.ToString();
+ }
+ }
+ }
+}
+```
+
+### Redis Caching in Plugins
+
+```csharp
+public class CachePlugin : IPlugin
+{
+ public void Execute(IServiceProvider serviceProvider)
+ {
+ var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
+ var entityId = context.PrimaryEntityId;
+
+ // Cache entity in Redis
+ var cacheKey = $"account:{entityId}";
+ var entity = /* retrieve entity */;
+ var json = EntitySerializer.Serialize(entity);
+
+ cache.SetString(cacheKey, json, TimeSpan.FromMinutes(15));
+
+ // Later retrieve from cache
+ var cachedJson = cache.GetString(cacheKey);
+ if (cachedJson != null)
+ {
+ var cachedEntity = EntitySerializer.DeserializeEntity(cachedJson);
+ }
+ }
+}
+```
+
+## External Integration Usage
+
+For **external applications** (console apps, web APIs, Azure Functions), no special deployment is needed - just reference the NuGet package:
+
+### Console Application Example
+
+```csharp
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Tooling.Connector;
+using Xrm.Json.Serialization;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ // Connect to Dynamics 365
+ var connectionString = "AuthType=OAuth;...";
+ var service = new CrmServiceClient(connectionString);
+
+ // Retrieve and serialize
+ var account = service.Retrieve("account", accountId, new ColumnSet(true));
+ var json = EntitySerializer.Serialize(account, indented: true);
+
+ Console.WriteLine(json);
+
+ // Save to file, send to API, cache, etc.
+ File.WriteAllText("account.json", json);
+ }
+}
+```
+
+### Azure Functions Example
+
+```csharp
+[FunctionName("ProcessDynamicsData")]
+public static async Task Run(
+ [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
+ ILogger log)
+{
+ // Deserialize from request
+ string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
+ var entity = EntitySerializer.DeserializeEntity(requestBody);
+
+ log.LogInformation($"Processing entity: {entity.LogicalName}");
+
+ // Process and return
+ var response = new
+ {
+ success = true,
+ entityName = entity.LogicalName
+ };
+
+ return new OkObjectResult(response);
+}
+```
+
+### Web API Example
+
+```csharp
+[ApiController]
+[Route("api/[controller]")]
+public class DynamicsController : ControllerBase
+{
+ private readonly IOrganizationService _service;
+
+ [HttpGet("account/{id}")]
+ public IActionResult GetAccount(Guid id)
+ {
+ var account = _service.Retrieve("account", id, new ColumnSet(true));
+ var json = EntitySerializer.Serialize(account);
+
+ return Content(json, "application/json");
+ }
+
+ [HttpPost("account")]
+ public IActionResult CreateAccount([FromBody] string json)
+ {
+ var account = EntitySerializer.DeserializeEntity(json);
+ var id = _service.Create(account);
+
+ return Ok(new { id });
+ }
+}
+```
+
+## Compact JSON Format
+
+The library produces compact, optimized JSON representations for Dynamics 365 data types:
+
+### Entity Reference
+```json
+{
+ "_reference": "account:12345678-1234-1234-1234-123456789012"
+}
+```
+
+### Money
```json
{
- "_reference": "test:16118a60-4346-46ab-8cf7-7e2bd9233b2f",
- "attribute1": {
+ "_money": 1000000
+}
+```
+
+### OptionSetValue
+```json
+{
+ "_option": 1
+}
+```
+
+### OptionSetValueCollection (Multi-Select Picklist)
+```json
+{
+ "_options": [1, 2, 3]
+}
+```
+
+### AliasedValue (FetchXML Linked Entities)
+```json
+{
+ "_aliased": "contact|fullname|John Doe"
+}
+```
+
+### BooleanManagedProperty
+```json
+{
+ "_boolmanaged": "True|False"
+}
+```
+
+### Guid
+```json
+{
+ "_id": "12345678-1234-1234-1234-123456789012"
+}
+```
+
+### Complete Entity Example
+```json
+{
+ "_reference": "account:12345678-1234-1234-1234-123456789012",
+ "name": "Contoso Ltd",
+ "revenue": {
+ "_money": 1000000
+ },
+ "industrycode": {
"_option": 1
+ },
+ "parentaccountid": {
+ "_reference": "account:87654321-4321-4321-4321-210987654321"
+ },
+ "createdon": "2024-01-15T10:30:00Z",
+ "contact.fullname": {
+ "_aliased": "contact|fullname|John Doe"
}
}
-```
\ No newline at end of file
+```
+
+## Requirements
+
+- **.NET Framework 4.6.2** or higher (**Plugin compatible!** âś…)
+- **Microsoft.CrmSdk.CoreAssemblies** (>= 9.0.2.60)
+- **Newtonsoft.Json** (>= 13.0.3)
+
+> **Note:** This library targets .NET Framework 4.6.2, making it fully compatible with Dynamics 365 plugins which run on .NET Framework 4.6.2 runtime.
+
+## Use Cases
+
+This library is ideal for:
+
+- **Plugin development** - Logging, diagnostics, and external integrations in Dynamics 365 plugins
+- **FetchXML queries** - Serialize results with linked entities (AliasedValue support)
+- **API integrations** - Transmit Dynamics 365 data via REST APIs
+- **Data export/import** - Backup and restore entity data
+- **Caching** - Store entity data in Redis, file systems, or other caches
+- **Logging** - Serialize entities for audit trails and debugging
+- **Message queues** - Send entity data through Azure Service Bus, RabbitMQ, etc.
+- **Webhooks** - Push Dynamics 365 changes to external systems
+- **Multi-select picklists** - Handle OptionSetValueCollection fields
+
+## Why Compact Format?
+
+Traditional Dynamics 365 entity serialization can be verbose. This library provides:
+
+- **Smaller payload sizes** - Reduced network bandwidth and storage
+- **Faster serialization** - Optimized for performance
+- **Easier debugging** - Human-readable JSON structure
+- **Type safety** - Preserves CRM data type information
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit issues and pull requests.
+
+1. Fork the repository
+2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Authors
+
+- **Alexey Shytikov** - Original author
+- **Imran Akram** - Maintainer
+
+## Links
+
+- [GitHub Repository](https://github.com/imranakram/Xrm.Json.Serialization)
+- [NuGet Package](https://www.nuget.org/packages/Xrm.Json.Serialization)
+- [Issues](https://github.com/imranakram/Xrm.Json.Serialization/issues)
+
+## Changelog
+
+### Version 1.2026.3.0 (March 2026)
+- âś… **NEW:** AliasedValue converter for FetchXML linked entity support
+- âś… **NEW:** OptionSetValueCollection converter for multi-select picklists
+- âś… **NEW:** BooleanManagedProperty converter
+- âś… **NEW:** EntitySerializer helper class for simplified usage
+- âś… **FIX:** String escaping issue with double quotes and special characters (#20)
+- âś… **TARGET:** .NET Framework 4.6.2 (plugin compatible)
+- âś… **DOCS:** Added comprehensive plugin usage examples
+- âś… **TESTS:** Added 27 new tests for new converters
+
+### Version 1.2022.10.1 (October 2022)
+- Namespace change from Innofactor.Xrm.Json.Serialization to Xrm.Json.Serialization
+- Package metadata updates
+
+### Version 1.0.0 (2021)
+- Initial release
+- Support for all major Dynamics 365 data types
+- Entity, EntityReference, EntityCollection converters
+- OptionSetValue, Money, DateTime, Guid converters
+- XrmContractResolver for seamless integration
+- Comprehensive test coverage
diff --git a/UPGRADE-GUIDE.md b/UPGRADE-GUIDE.md
new file mode 100644
index 0000000..2759ae7
--- /dev/null
+++ b/UPGRADE-GUIDE.md
@@ -0,0 +1,198 @@
+# Xrm.Json.Serialization v2.0 Upgrade Summary
+
+## đź“‹ Upgrade Checklist
+
+### âś… Completed
+- [x] Upgraded target framework from .NET 4.6.2 to .NET 4.8
+- [x] Updated Newtonsoft.Json to 13.0.3
+- [x] Updated xUnit to 2.9.3
+- [x] Removed all Innofactor branding
+- [x] Changed namespace from `Innofactor.Xrm.Json.Serialization` to `Xrm.Json.Serialization`
+- [x] Updated AssemblyInfo (version 2.0.0.0, Biznamics branding)
+- [x] Updated NuGet package metadata (.nuspec)
+- [x] Created comprehensive README.md
+- [x] Created fix-structure.ps1 script
+
+### ⏳ Pending (Manual Steps Required)
+- [ ] Close Visual Studio
+- [ ] Run fix-structure.ps1 script to move projects to root
+- [ ] Reopen solution in Visual Studio
+- [ ] Build and test
+- [ ] Commit changes to vNext branch
+- [ ] Create release tag v2.0.0
+- [ ] Publish NuGet package
+
+---
+
+## 🏗️ Project Structure Changes
+
+### Before:
+```
+C:\Git\GitHub\Xrm.Json.Serialization\
+├── src\
+│ ├── Innofactor.Xrm.Json.Serialization\
+│ └── Innofactor.Xrm.Json.Serialization.Tests\
+├── packages\
+└── Xrm.Json.Serialization.sln
+```
+
+### After:
+```
+C:\Git\GitHub\Xrm.Json.Serialization\
+├── Innofactor.Xrm.Json.Serialization\
+├── Innofactor.Xrm.Json.Serialization.Tests\
+├── packages\
+├── fix-structure.ps1
+├── README.md
+└── Xrm.Json.Serialization.sln
+```
+
+---
+
+## 📦 NuGet Dependencies
+
+### Main Project (Innofactor.Xrm.Json.Serialization)
+| Package | Version | Purpose |
+|---------|---------|---------|
+| Newtonsoft.Json | 13.0.3 | JSON serialization |
+| Microsoft.CrmSdk.CoreAssemblies | 9.0.2.46 | Dynamics 365 SDK types |
+| Microsoft.Bcl.AsyncInterfaces | 6.0.0 | Async support |
+| System.Text.Json | 6.0.10 | Modern JSON APIs |
+| System.Memory | 4.5.5 | Span support |
+
+### Test Project (Innofactor.Xrm.Json.Serialization.Tests)
+All of above plus:
+| Package | Version | Purpose |
+|---------|---------|---------|
+| xunit | 2.9.3 | Test framework |
+| xunit.runner.visualstudio | 3.1.5 | VS Test Explorer integration |
+
+---
+
+## đź”§ Manual Steps to Complete
+
+### 1. Close Visual Studio
+**Critical**: Close VS completely to release file locks
+
+### 2. Run Structure Fix Script
+```powershell
+# In PowerShell (Run as Administrator if needed)
+cd C:\Git\GitHub\Xrm.Json.Serialization
+.\fix-structure.ps1
+```
+
+This script will:
+- Move projects from `src\` to root
+- Update solution file paths
+- Update NuGet package HintPath references
+- Clean bin/obj folders
+- Restore NuGet packages
+
+### 3. Reopen Solution
+```powershell
+start Xrm.Json.Serialization.sln
+```
+
+### 4. Rebuild Solution
+In Visual Studio:
+- Build → Rebuild Solution
+- Should build without errors
+
+### 5. Run Tests
+- Test → Run All Tests
+- Verify all tests pass
+
+### 6. Commit Changes
+```bash
+git add -A
+git commit -m "v2.0.0: Upgrade to .NET 4.8, remove Innofactor branding, restructure projects"
+git push origin vNext
+```
+
+---
+
+## 📝 Breaking Changes in v2.0.0
+
+### Namespace Change
+**Old:** `Innofactor.Xrm.Json.Serialization`
+**New:** `Xrm.Json.Serialization`
+
+**Migration:**
+```csharp
+// Old code
+using Innofactor.Xrm.Json.Serialization;
+
+// New code
+using Xrm.Json.Serialization;
+```
+
+### Target Framework
+**Old:** .NET Framework 4.6.2
+**New:** .NET Framework 4.8
+
+### Assembly Name
+**Old:** `Innofactor.Xrm.Json.Serialization.dll`
+**New:** `Xrm.Json.Serialization.dll`
+
+---
+
+## 🚀 Publishing to NuGet
+
+### Build Release Package
+```powershell
+# Build in Release mode
+msbuild Xrm.Json.Serialization.sln /p:Configuration=Release
+
+# Pack NuGet package
+nuget pack Innofactor.Xrm.Json.Serialization\Innofactor.Xrm.Json.Serialization.nuspec -Properties Configuration=Release
+```
+
+### Publish
+```powershell
+nuget push Xrm.Json.Serialization.2.0.0.nupkg -Source nuget.org -ApiKey YOUR_API_KEY
+```
+
+### Tag Release
+```bash
+git tag v2.0.0
+git push origin v2.0.0
+```
+
+---
+
+## đź§Ş Test Coverage
+
+All converters have comprehensive unit tests:
+- âś… BasicsConverter (string, int, double, decimal, object)
+- âś… DateTimeConverter (timezone support)
+- âś… EntityCollectionConverter (arrays of entities)
+- âś… EntityConverter (full entity serialization)
+- âś… EntityReferenceConverter (lookup fields)
+- âś… GuidConverter (unique identifiers)
+- âś… MoneyConverter (currency values)
+- âś… OptionSetValueConverter (picklist values)
+- âś… Combined types (complex entity scenarios)
+
+**Total Tests:** ~25+
+**Expected Pass Rate:** 100%
+
+---
+
+## 📞 Support
+
+- **Issues:** https://github.com/Biznamics/Xrm.Json.Serialization/issues
+- **Discussions:** https://github.com/Biznamics/Xrm.Json.Serialization/discussions
+- **NuGet:** https://www.nuget.org/packages/Xrm.Json.Serialization/
+
+---
+
+## 🙏 Credits
+
+- **Original Author:** Alexey Shytikov
+- **Current Maintainer:** Imran Akram / Biznamics
+- **Contributors:** Community contributors welcome!
+
+---
+
+**License:** MIT
+**Copyright:** © Biznamics 2025
diff --git a/Xrm.Json.Serialization.Tests/AliasedValueConverterTests.cs b/Xrm.Json.Serialization.Tests/AliasedValueConverterTests.cs
new file mode 100644
index 0000000..db28e1a
--- /dev/null
+++ b/Xrm.Json.Serialization.Tests/AliasedValueConverterTests.cs
@@ -0,0 +1,228 @@
+namespace Xrm.Json.Serialization.Tests
+{
+ using System;
+ using Microsoft.Xrm.Sdk;
+ using Newtonsoft.Json;
+ using Xunit;
+
+ public class AliasedValueConverterTests
+ {
+ #region Public Methods
+
+ [Fact]
+ public void AliasedValue_String_Can_Serialize()
+ {
+ // Arrange
+ var value = new AliasedValue("account", "name", "Contoso Ltd");
+ var expected = "{\"_aliased\":\"account|name|Contoso Ltd\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_String_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|name|Contoso Ltd\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("name", actual.AttributeLogicalName);
+ Assert.Equal("Contoso Ltd", actual.Value);
+ }
+
+ [Fact]
+ public void AliasedValue_EntityReference_Can_Serialize()
+ {
+ // Arrange
+ var entityRef = new EntityReference("contact", Guid.Parse("12345678-1234-1234-1234-123456789012"));
+ var value = new AliasedValue("account", "primarycontactid", entityRef);
+ var expected = "{\"_aliased\":\"account|primarycontactid|ref:contact:12345678-1234-1234-1234-123456789012\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_EntityReference_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|primarycontactid|ref:contact:12345678-1234-1234-1234-123456789012\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("primarycontactid", actual.AttributeLogicalName);
+ Assert.IsType(actual.Value);
+ var entityRef = (EntityReference)actual.Value;
+ Assert.Equal("contact", entityRef.LogicalName);
+ Assert.Equal(Guid.Parse("12345678-1234-1234-1234-123456789012"), entityRef.Id);
+ }
+
+ [Fact]
+ public void AliasedValue_OptionSet_Can_Serialize()
+ {
+ // Arrange
+ var optionSet = new OptionSetValue(1);
+ var value = new AliasedValue("account", "industrycode", optionSet);
+ var expected = "{\"_aliased\":\"account|industrycode|opt:1\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_OptionSet_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|industrycode|opt:1\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("industrycode", actual.AttributeLogicalName);
+ Assert.IsType(actual.Value);
+ Assert.Equal(1, ((OptionSetValue)actual.Value).Value);
+ }
+
+ [Fact]
+ public void AliasedValue_Money_Can_Serialize()
+ {
+ // Arrange
+ var money = new Money(1000000m);
+ var value = new AliasedValue("account", "revenue", money);
+ var expected = "{\"_aliased\":\"account|revenue|mon:1000000\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_Money_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|revenue|mon:1000000\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("revenue", actual.AttributeLogicalName);
+ Assert.IsType(actual.Value);
+ Assert.Equal(1000000m, ((Money)actual.Value).Value);
+ }
+
+ [Fact]
+ public void AliasedValue_Guid_Can_Serialize()
+ {
+ // Arrange
+ var guid = Guid.Parse("12345678-1234-1234-1234-123456789012");
+ var value = new AliasedValue("account", "accountid", guid);
+ var expected = "{\"_aliased\":\"account|accountid|guid:12345678-1234-1234-1234-123456789012\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_Guid_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|accountid|guid:12345678-1234-1234-1234-123456789012\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("accountid", actual.AttributeLogicalName);
+ Assert.IsType(actual.Value);
+ Assert.Equal(Guid.Parse("12345678-1234-1234-1234-123456789012"), actual.Value);
+ }
+
+ [Fact]
+ public void AliasedValue_Boolean_Can_Serialize()
+ {
+ // Arrange
+ var value = new AliasedValue("account", "donotphone", true);
+ var expected = "{\"_aliased\":\"account|donotphone|bool:True\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_Boolean_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|donotphone|bool:True\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("donotphone", actual.AttributeLogicalName);
+ Assert.IsType(actual.Value);
+ Assert.True((bool)actual.Value);
+ }
+
+ [Fact]
+ public void AliasedValue_Null_Can_Serialize()
+ {
+ // Arrange
+ var value = new AliasedValue("account", "parentaccountid", null);
+ var expected = "{\"_aliased\":\"account|parentaccountid|\"}";
+
+ // Act
+ var actual = JsonConvert.SerializeObject(value, Formatting.None, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void AliasedValue_Null_Can_Deserialize()
+ {
+ // Arrange
+ var json = "{\"_aliased\":\"account|parentaccountid|\"}";
+
+ // Act
+ var actual = JsonConvert.DeserializeObject(json, new AliasedValueConverter());
+
+ // Assert
+ Assert.Equal("account", actual.EntityLogicalName);
+ Assert.Equal("parentaccountid", actual.AttributeLogicalName);
+ Assert.Null(actual.Value);
+ }
+
+ #endregion Public Methods
+ }
+}
diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/BasicsConverterTests.cs b/Xrm.Json.Serialization.Tests/BasicsConverterTests.cs
similarity index 68%
rename from src/Innofactor.Xrm.Json.Serialization.Tests/BasicsConverterTests.cs
rename to Xrm.Json.Serialization.Tests/BasicsConverterTests.cs
index b4c5d20..740d785 100644
--- a/src/Innofactor.Xrm.Json.Serialization.Tests/BasicsConverterTests.cs
+++ b/Xrm.Json.Serialization.Tests/BasicsConverterTests.cs
@@ -1,154 +1,210 @@
-namespace Innofactor.Xrm.Json.Serialization.Tests
-{
- using System.Globalization;
- using Innofactor.Xrm.Json.Serialization;
- using Newtonsoft.Json;
- using Xunit;
-
- public class BasicsConverterTests
- {
- #region Public Methods
-
- [Fact]
- public void Decimal_Can_Deserialize()
- {
- // Arrange
- var expected = 0.1234567890m;
- var value = $"{{\"testProp\":\"0.1234567890\"}}";
-
- // Act
- var actual = JsonConvert.DeserializeObject(value, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Decimal_Can_Serialize()
- {
- // Arrange
- var value = 0.1234567890m;
- var expected = "0.1234567890";
-
- // Act
- var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Double_Can_Deserialize()
- {
- // Arrange
- var expected = 13.37d;
- var value = $"{{\"testProp\":{expected.ToString(CultureInfo.InvariantCulture)}}}";
-
- // Act
- var actual = JsonConvert.DeserializeObject(value, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Double_Can_Serialize()
- {
- // Arrange
- var value = 13.37f;
- var expected = value.ToString(CultureInfo.InvariantCulture);
-
- // Act
- var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Int_Can_Deserialize()
- {
- // Arrange
- var expected = 42;
- var value = $"{{\"testProp\":{expected}}}";
-
- // Act
- var actual = JsonConvert.DeserializeObject(value, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Int_Can_Serialize()
- {
- // Arrange
- var value = 42;
- var expected = $"{value}";
-
- // Act
- var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter());
-
- // Assert
- Assert.Equal(expected, actual);
- }
-
- [Fact]
- public void Object_Can_Deserialize()
- {
- // Arrange
- var expected = (object)"{}";
- var value = "{\"testProp\":\"{}\"}";
-
- // Act
- var actual = JsonConvert.DeserializeObject