Xrm.Json.Serialization
1.2026.9
dotnet add package Xrm.Json.Serialization --version 1.2026.9
NuGet\Install-Package Xrm.Json.Serialization -Version 1.2026.9
<PackageReference Include="Xrm.Json.Serialization" Version="1.2026.9" />
<PackageVersion Include="Xrm.Json.Serialization" Version="1.2026.9" />
<PackageReference Include="Xrm.Json.Serialization" />
paket add Xrm.Json.Serialization --version 1.2026.9
#r "nuget: Xrm.Json.Serialization, 1.2026.9"
#:package Xrm.Json.Serialization@1.2026.9
#addin nuget:?package=Xrm.Json.Serialization&version=1.2026.9
#tool nuget:?package=Xrm.Json.Serialization&version=1.2026.9
Xrm.Json.Serialization
Compact JSON serialization library for Microsoft Dynamics 365/CRM/Dataverse entities using Newtonsoft.Json.
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
- ✅ Fast in bulk - contracts are resolved once per process, not once per entity
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
Install-Package Xrm.Json.Serialization
.NET CLI
dotnet add package Xrm.Json.Serialization
Package Reference
<PackageReference Include="Xrm.Json.Serialization" Version="1.2026.3.0" />
Usage
Basic Serialization
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
{
ContractResolver = new XrmContractResolver(),
Formatting = Formatting.Indented
};
// Serialize to JSON
string json = JsonConvert.SerializeObject(account, settings);
Console.WriteLine(json);
Deserialization
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<Entity>(json, settings);
Console.WriteLine($"Account: {account["name"]}");
Console.WriteLine($"Revenue: {((Money)account["revenue"]).Value}");
Entity Collection Serialization
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:
// 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:
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);
Plugin Usage
This library is compatible with Dynamics 365 plugins (targets .NET Framework 4.6.2), but requires special deployment considerations:
⚠️ 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:
<ItemGroup>
<PackageReference Include="ILMerge" Version="3.0.41" />
<PackageReference Include="Xrm.Json.Serialization" Version="1.1.0" />
</ItemGroup>
Build script:
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:
<ItemGroup>
<PackageReference Include="Microsoft.PowerPlatform.Dataverse.Client" Version="1.1.14" />
<PackageReference Include="Xrm.Json.Serialization" Version="1.1.0" />
</ItemGroup>
<PropertyGroup>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\PowerApps-Targets</PowerAppsTargetsPath>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
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
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}");
}
}
Azure Service Bus Integration
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
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:
public class FetchXmlPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = serviceFactory.CreateOrganizationService(context.UserId);
var fetchXml = @"
<fetch>
<entity name='account'>
<attribute name='name' />
<link-entity name='contact' from='contactid' to='primarycontactid' alias='contact'>
<attribute name='fullname' />
<attribute name='emailaddress1' />
</link-entity>
</entity>
</fetch>";
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
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
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
[FunctionName("ProcessDynamicsData")]
public static async Task<IActionResult> 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
[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
{
"_reference": "account:12345678-1234-1234-1234-123456789012"
}
Money
{
"_money": 1000000
}
OptionSetValue
{
"_option": 1
}
OptionSetValueCollection (Multi-Select Picklist)
{
"_options": [1, 2, 3]
}
AliasedValue (FetchXML Linked Entities)
{
"_aliased": "contact|fullname|John Doe"
}
BooleanManagedProperty
{
"_boolmanaged": "True|False"
}
Guid
{
"_id": "12345678-1234-1234-1234-123456789012"
}
Complete Entity Example
{
"_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"
}
}
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
Performance
Serialization is designed to stay cheap when you run it in a loop - exporting a table, filling a cache, or writing an audit trail for a batch job.
Measured on .NET Framework 4.8 x64, 100 000 entities of five attributes each:
| Time | |
|---|---|
| Xrm.Json.Serialization 1.2026.9.0 | 0.34 s |
| Plain Newtonsoft (reflective, no CRM converters) | 1.03 s |
| Xrm.Json.Serialization 1.2026.3.0 and earlier | 82.31 s |
The earlier figure was a bug, not a design limit. EntityConverter and
EntityCollectionConverter assigned serializer.ContractResolver = new XrmContractResolver()
on every call. Newtonsoft's DefaultContractResolver caches the JsonContract it builds for
each type on the instance, so a fresh instance per call discarded that cache and forced
every type on the graph - Entity, AttributeCollection, EntityReference, Money,
OptionSetValue, Guid, DateTime - to be re-resolved by reflection each time.
From 1.2026.9.0 the converters route through a single shared resolver. Output is byte-identical; there is no change to the JSON format or to the API.
If you build your own settings
Reuse one settings object rather than constructing one per call, and let the library supply the resolver:
// Do this once, at whatever passes for startup in your host.
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EntityConverter(), new EntityReferenceConverter() },
NullValueHandling = NullValueHandling.Ignore
};
var json = JsonConvert.SerializeObject(entity, Settings);
EntitySerializer already does this internally, so EntitySerializer.Serialize(entity) needs
no setup and is the fastest route for most callers.
Note: if you set
JsonConvert.DefaultSettings, remember Newtonsoft treats it as a factory and invokes it on everySerializeObject/DeserializeObjectcall. Keep the lambda cheap - return a cached instance rather than building a new one each time.
Contributing
Contributions are welcome! Please feel free to submit issues and pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Authors
- Alexey Shytikov - Original author
- Imran Akram - Maintainer
Links
Changelog
Version 1.2026.9.0 (September 2026)
- ✅ FIX: ~240x faster bulk serialization - the converters no longer allocate a new
XrmContractResolverper call, which was discarding Newtonsoft's per-instance contract cache (100 000 entities: 82.31 s → 0.34 s, byte-identical output) - ✅ TESTS: Added 9 tests pinning resolver sharing, full-type round trips and a bulk-throughput guard
- ✅ DOCS: Added a Performance section covering settings reuse and the
JsonConvert.DefaultSettingsfactory pitfall
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
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET Framework | net462 is compatible. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
-
.NETFramework 4.6.2
- Microsoft.CrmSdk.CoreAssemblies (>= 9.0.2.60)
- Newtonsoft.Json (>= 13.0.4)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Xrm.Json.Serialization:
| Package | Downloads |
|---|---|
|
Xrm.Persistent.Collections
SQLite-backed persistent collections for Microsoft Dynamics CRM/XRM applications. Provides disk-backed dictionary storage with automatic serialization of CRM entities (Entity, EntityReference, OptionSetValue, Money, etc.). Perfect for long-running job engines, caching, and state persistence that survives application restarts. Inspired by Akavache, optimized for Dynamics 365 Online and OnPrem scenarios. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2026.9 | 43 | 9/7/2026 |
| 1.2026.3.1 | 472 | 3/11/2026 |
| 1.2022.10.1 | 1,036 | 10/23/2022 |
| 1.2021.10.2 | 27,761 | 10/3/2021 |
| 1.2021.7.2 | 605 | 7/1/2021 |
| 1.2020.2.4 | 1,136 | 2/21/2020 |
| 1.2020.2.3 | 725 | 2/6/2020 |
| 1.0.42 | 1,083 | 5/27/2019 |
Version 1.2026.9.0 (September 2026):
- FIX: ~240x faster bulk serialization. The converters no longer allocate a new
XrmContractResolver per call, which was discarding Newtonsoft's per-instance
contract cache and re-resolving every type by reflection on every entity.
100 000 entities: 82.31 s -> 0.34 s, byte-identical output.
- TESTS: 9 new tests pinning resolver sharing and bulk throughput.
- DOCS: New Performance section on settings reuse and the DefaultSettings factory pitfall.
- No change to the JSON format, the public API or the target framework - drop-in upgrade.