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: +[![Build and Test](https://github.com/imranakram/Xrm.Json.Serialization/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/imranakram/Xrm.Json.Serialization/actions/workflows/build-and-test.yml) +[![NuGet](https://img.shields.io/nuget/v/Xrm.Json.Serialization.svg)](https://www.nuget.org/packages/Xrm.Json.Serialization) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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(value, new BasicsConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Object_Can_Serialize() - { - // Arrange - var value = new object(); - var expected = "\"{}\""; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void String_Can_Deserialize() - { - // Arrange - var expected = "testString"; - var value = $"{{\"testProp\":\"{expected}\"}}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new BasicsConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void String_Can_Serialize() - { - // Arrange - var value = "test\"String"; - var expected = $"\"test\"String\""; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System.Globalization; + using 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(value, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Object_Can_Serialize() + { + // Arrange + var value = new object(); + var expected = "\"{}\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_Can_Deserialize() + { + // Arrange + var expected = "testString"; + var value = $"{{\"testProp\":\"{expected}\"}}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_Can_Serialize() + { + // Arrange + var value = "test\"String"; + var expected = "\"test\\\"String\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_With_Multiple_Quotes_Can_Serialize() + { + // Arrange + var value = "He said \"Hello\" and she replied \"Hi\""; + var expected = "\"He said \\\"Hello\\\" and she replied \\\"Hi\\\"\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_With_Backslash_Can_Serialize() + { + // Arrange + var value = "C:\\Users\\Test\\File.txt"; + var expected = "\"C:\\\\Users\\\\Test\\\\File.txt\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_With_Newline_Can_Serialize() + { + // Arrange + var value = "Line1\nLine2"; + var expected = "\"Line1\\nLine2\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void String_With_Tab_Can_Serialize() + { + // Arrange + var value = "Column1\tColumn2"; + var expected = "\"Column1\\tColumn2\""; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BasicsConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization.Tests/BooleanManagedPropertyConverterTests.cs b/Xrm.Json.Serialization.Tests/BooleanManagedPropertyConverterTests.cs new file mode 100644 index 0000000..5c57d89 --- /dev/null +++ b/Xrm.Json.Serialization.Tests/BooleanManagedPropertyConverterTests.cs @@ -0,0 +1,139 @@ +namespace Xrm.Json.Serialization.Tests +{ + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class BooleanManagedPropertyConverterTests + { + #region Public Methods + + [Fact] + public void BooleanManagedProperty_True_CanChange_Can_Serialize() + { + // Arrange + var value = new BooleanManagedProperty(true) { CanBeChanged = true }; + var expected = "{\"_boolmanaged\":\"True|True\"}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BooleanManagedPropertyConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void BooleanManagedProperty_True_CanChange_Can_Deserialize() + { + // Arrange + var json = "{\"_boolmanaged\":\"True|True\"}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new BooleanManagedPropertyConverter()); + + // Assert + Assert.True(actual.Value); + Assert.True(actual.CanBeChanged); + } + + [Fact] + public void BooleanManagedProperty_True_CannotChange_Can_Serialize() + { + // Arrange + var value = new BooleanManagedProperty(true) { CanBeChanged = false }; + var expected = "{\"_boolmanaged\":\"True|False\"}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BooleanManagedPropertyConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void BooleanManagedProperty_True_CannotChange_Can_Deserialize() + { + // Arrange + var json = "{\"_boolmanaged\":\"True|False\"}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new BooleanManagedPropertyConverter()); + + // Assert + Assert.True(actual.Value); + Assert.False(actual.CanBeChanged); + } + + [Fact] + public void BooleanManagedProperty_False_CanChange_Can_Serialize() + { + // Arrange + var value = new BooleanManagedProperty(false) { CanBeChanged = true }; + var expected = "{\"_boolmanaged\":\"False|True\"}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BooleanManagedPropertyConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void BooleanManagedProperty_False_CanChange_Can_Deserialize() + { + // Arrange + var json = "{\"_boolmanaged\":\"False|True\"}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new BooleanManagedPropertyConverter()); + + // Assert + Assert.False(actual.Value); + Assert.True(actual.CanBeChanged); + } + + [Fact] + public void BooleanManagedProperty_False_CannotChange_Can_Serialize() + { + // Arrange + var value = new BooleanManagedProperty(false) { CanBeChanged = false }; + var expected = "{\"_boolmanaged\":\"False|False\"}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new BooleanManagedPropertyConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void BooleanManagedProperty_False_CannotChange_Can_Deserialize() + { + // Arrange + var json = "{\"_boolmanaged\":\"False|False\"}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new BooleanManagedPropertyConverter()); + + // Assert + Assert.False(actual.Value); + Assert.False(actual.CanBeChanged); + } + + [Fact] + public void BooleanManagedProperty_DefaultCanBeChanged_Can_Deserialize() + { + // Arrange - only value, no CanBeChanged + var json = "{\"_boolmanaged\":\"True\"}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new BooleanManagedPropertyConverter()); + + // Assert + Assert.True(actual.Value); + Assert.True(actual.CanBeChanged); // Default is true + } + + #endregion Public Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/CombinedTypesTests.cs b/Xrm.Json.Serialization.Tests/CombinedTypesTests.cs similarity index 94% rename from src/Innofactor.Xrm.Json.Serialization.Tests/CombinedTypesTests.cs rename to Xrm.Json.Serialization.Tests/CombinedTypesTests.cs index 1389733..e54f58c 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/CombinedTypesTests.cs +++ b/Xrm.Json.Serialization.Tests/CombinedTypesTests.cs @@ -1,83 +1,83 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class CombinedTypesTests - { - #region Public Methods - - [Fact] - public void Entity_Can_Deserialize_With_Mixed_Types() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var someGuid = Guid.NewGuid(); - var refEntName = "refEnt"; - var refEntId = Guid.NewGuid(); - - var expected = new Entity(name, id); - expected.Attributes.Add("someString", "testString"); - expected.Attributes.Add("someGuid", someGuid); - expected.Attributes.Add(refEntName, new EntityReference(refEntName, refEntId)); - expected.Attributes.Add("attribute1", new OptionSetValue(1)); - - var value = "{" + - $"\"_reference\":\"{name}:{id.ToString()}\"," + - "\"someString\":\"testString\"," + - $"\"someGuid\":{{\"_id\":\"{someGuid.ToString()}\"}}," + - $"\"{refEntName}\":{{\"_reference\":\"{refEntName}:{refEntId}\"}}," + - "\"attribute1\":{\"_option\":1}" + - "}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new EntityConverter()); - - // Assert - Assert.Equal(expected.LogicalName, actual.LogicalName); - Assert.Equal(expected.Id, actual.Id); - Assert.Equal(expected.Attributes["someString"], actual.Attributes["someString"]); - Assert.Equal(expected.Attributes["someGuid"], actual.Attributes["someGuid"]); - Assert.Equal((expected.Attributes[refEntName] as EntityReference).Id, (expected.Attributes[refEntName] as EntityReference).Id); - Assert.Equal((expected.Attributes[refEntName] as EntityReference).LogicalName, (expected.Attributes[refEntName] as EntityReference).LogicalName); - Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); - } - - [Fact] - public void Entity_Can_Serialize_With_Mixed_Types() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var value = new Entity(name, id); - - var someGuid = Guid.NewGuid(); - var refEntName = "refEnt"; - var refEntId = Guid.NewGuid(); - value.Attributes.Add("someString", "testString"); - value.Attributes.Add("someGuid", someGuid); - value.Attributes.Add(refEntName, new EntityReference(refEntName, refEntId)); - value.Attributes.Add("attribute1", new OptionSetValue(1)); - - var expected = "{" + - $"\"_reference\":\"{name}:{id.ToString()}\"," + - "\"someString\":\"testString\"," + - $"\"someGuid\":{{\"_id\":\"{someGuid.ToString()}\"}}," + - $"\"{refEntName}\":{{\"_reference\":\"{refEntName}:{refEntId}\"}}," + - "\"attribute1\":{\"_option\":1}" + - "}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System; + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class CombinedTypesTests + { + #region Public Methods + + [Fact] + public void Entity_Can_Deserialize_With_Mixed_Types() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var someGuid = Guid.NewGuid(); + var refEntName = "refEnt"; + var refEntId = Guid.NewGuid(); + + var expected = new Entity(name, id); + expected.Attributes.Add("someString", "testString"); + expected.Attributes.Add("someGuid", someGuid); + expected.Attributes.Add(refEntName, new EntityReference(refEntName, refEntId)); + expected.Attributes.Add("attribute1", new OptionSetValue(1)); + + var value = "{" + + $"\"_reference\":\"{name}:{id.ToString()}\"," + + "\"someString\":\"testString\"," + + $"\"someGuid\":{{\"_id\":\"{someGuid.ToString()}\"}}," + + $"\"{refEntName}\":{{\"_reference\":\"{refEntName}:{refEntId}\"}}," + + "\"attribute1\":{\"_option\":1}" + + "}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new EntityConverter()); + + // Assert + Assert.Equal(expected.LogicalName, actual.LogicalName); + Assert.Equal(expected.Id, actual.Id); + Assert.Equal(expected.Attributes["someString"], actual.Attributes["someString"]); + Assert.Equal(expected.Attributes["someGuid"], actual.Attributes["someGuid"]); + Assert.Equal((expected.Attributes[refEntName] as EntityReference).Id, (expected.Attributes[refEntName] as EntityReference).Id); + Assert.Equal((expected.Attributes[refEntName] as EntityReference).LogicalName, (expected.Attributes[refEntName] as EntityReference).LogicalName); + Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); + } + + [Fact] + public void Entity_Can_Serialize_With_Mixed_Types() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var value = new Entity(name, id); + + var someGuid = Guid.NewGuid(); + var refEntName = "refEnt"; + var refEntId = Guid.NewGuid(); + value.Attributes.Add("someString", "testString"); + value.Attributes.Add("someGuid", someGuid); + value.Attributes.Add(refEntName, new EntityReference(refEntName, refEntId)); + value.Attributes.Add("attribute1", new OptionSetValue(1)); + + var expected = "{" + + $"\"_reference\":\"{name}:{id.ToString()}\"," + + "\"someString\":\"testString\"," + + $"\"someGuid\":{{\"_id\":\"{someGuid.ToString()}\"}}," + + $"\"{refEntName}\":{{\"_reference\":\"{refEntName}:{refEntId}\"}}," + + "\"attribute1\":{\"_option\":1}" + + "}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs b/Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs similarity index 91% rename from src/Innofactor.Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs rename to Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs index 0c3cf7e..83de72c 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/DateTimeConverterTests.cs @@ -1,41 +1,41 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using Newtonsoft.Json; - using Xunit; - - public class DateTimeConverterTests - { - #region Public Methods - - [Fact] - public void DateTime_Can_Deserialize() - { - // Arrange - var expected = DateTime.Now; - var value = $"{{\"_moment\":\"{DateTimeConverter.Format(expected)}\"}}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new DateTimeConverter()); - - // Assert - Assert.Equal(expected.ToString(), actual.ToString()); - } - - [Fact] - public void DateTime_Can_Serialize() - { - // Arrange - var value = DateTime.Now; - var expected = $"{{\"_moment\":\"{DateTimeConverter.Format(value)}\"}}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new DateTimeConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System; + using Newtonsoft.Json; + using Xunit; + + public class DateTimeConverterTests + { + #region Public Methods + + [Fact] + public void DateTime_Can_Deserialize() + { + // Arrange + var expected = DateTime.Now; + var value = $"{{\"_moment\":\"{DateTimeConverter.Format(expected)}\"}}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new DateTimeConverter()); + + // Assert + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void DateTime_Can_Serialize() + { + // Arrange + var value = DateTime.Now; + var expected = $"{{\"_moment\":\"{DateTimeConverter.Format(value)}\"}}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new DateTimeConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs b/Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs similarity index 92% rename from src/Innofactor.Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs rename to Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs index c9a9df3..b314065 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/EntityCollectionConverterTests.cs @@ -1,56 +1,56 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class EntityCollectionConverterTests - { - #region Public Methods - - [Fact] - public void EntityCollection_Can_Deserialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var entity = new Entity(name, id); - entity.Attributes.Add("attribute1", new OptionSetValue(1)); - var expected = new EntityCollection(); - expected.Entities.Add(entity); - var value = $"[{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}}}}]"; ; - - // Act - var actual = JsonConvert.DeserializeObject(value, new EntityCollectionConverter()); - - // Assert - Assert.Equal(expected.Entities.Count, actual.Entities.Count); - //Assert.Equal(expected.Id, actual.Id); - //Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); - } - - [Fact] - public void EntityCollection_Can_Serialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var entity = new Entity(name, id); - entity.Attributes.Add("attribute1", new OptionSetValue(1)); - var value = new EntityCollection(); - value.Entities.Add(entity); - - var expected = $"[{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}}}}]"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityCollectionConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System; + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class EntityCollectionConverterTests + { + #region Public Methods + + [Fact] + public void EntityCollection_Can_Deserialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var entity = new Entity(name, id); + entity.Attributes.Add("attribute1", new OptionSetValue(1)); + var expected = new EntityCollection(); + expected.Entities.Add(entity); + var value = $"[{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}}}}]"; ; + + // Act + var actual = JsonConvert.DeserializeObject(value, new EntityCollectionConverter()); + + // Assert + Assert.Equal(expected.Entities.Count, actual.Entities.Count); + //Assert.Equal(expected.Id, actual.Id); + //Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); + } + + [Fact] + public void EntityCollection_Can_Serialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var entity = new Entity(name, id); + entity.Attributes.Add("attribute1", new OptionSetValue(1)); + var value = new EntityCollection(); + value.Entities.Add(entity); + + var expected = $"[{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}}}}]"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityCollectionConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityConverterTests.cs b/Xrm.Json.Serialization.Tests/EntityConverterTests.cs similarity index 94% rename from src/Innofactor.Xrm.Json.Serialization.Tests/EntityConverterTests.cs rename to Xrm.Json.Serialization.Tests/EntityConverterTests.cs index f906a6d..ecc65c6 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/EntityConverterTests.cs @@ -1,90 +1,90 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class EntityConverterTests - { - #region Public Methods - - [Fact] - public void Entity_Can_Deserialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var expected = new Entity(name, id); - expected.Attributes.Add("attribute1", new OptionSetValue(1)); - expected.Attributes.Add("attribute2", 2); - expected.Attributes.Add("attribute3", 13.37d); - var value = $"{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}},\"attribute2\":2,\"attribute3\":13.37}}"; ; - - // Act - var actual = JsonConvert.DeserializeObject(value, new EntityConverter()); - - // Assert - Assert.Equal(expected.LogicalName, actual.LogicalName); - Assert.Equal(expected.Id, actual.Id); - Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); - Assert.Equal((int)expected.Attributes["attribute2"], (int)actual.Attributes["attribute2"]); - Assert.Equal((double)expected.Attributes["attribute3"], (double)actual.Attributes["attribute3"]); - } - - [Fact] - public void Entity_Can_Remove_Empty_Attribute_During_Serialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var value = new Entity(name, id); - value.Attributes.Add("attribute1", null); - var expected = $"{{\"_reference\":\"{name}:{id}\"}}"; ; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Entity_Can_Report_Incorrect_Attribute_During_Deserialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var value = $"{{\"_reference\":\"{name}:{id}\",\"attribute1\":}}"; // Intentional error: value of `attribute1` is missing! - - // Act - var ex = Assert.Throws(() => JsonConvert.DeserializeObject(value, new EntityConverter())); - - // Assert - Assert.Contains("attribute1", ex.Message); - } - - [Fact] - public void Entity_Can_Serialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var value = new Entity(name, id); - value.Attributes.Add("attribute1", new OptionSetValue(1)); - value.Attributes.Add("attribute2", 2); - value.Attributes.Add("attribute3", 13.37d); - value.Attributes.Add("attribute4", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - var expected = $"{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}},\"attribute2\":2,\"attribute3\":13.37,\"attribute4\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System; + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class EntityConverterTests + { + #region Public Methods + + [Fact] + public void Entity_Can_Deserialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var expected = new Entity(name, id); + expected.Attributes.Add("attribute1", new OptionSetValue(1)); + expected.Attributes.Add("attribute2", 2); + expected.Attributes.Add("attribute3", 13.37d); + var value = $"{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}},\"attribute2\":2,\"attribute3\":13.37}}"; ; + + // Act + var actual = JsonConvert.DeserializeObject(value, new EntityConverter()); + + // Assert + Assert.Equal(expected.LogicalName, actual.LogicalName); + Assert.Equal(expected.Id, actual.Id); + Assert.Equal((expected.Attributes["attribute1"] as OptionSetValue).Value, (actual.Attributes["attribute1"] as OptionSetValue).Value); + Assert.Equal((int)expected.Attributes["attribute2"], (int)actual.Attributes["attribute2"]); + Assert.Equal((double)expected.Attributes["attribute3"], (double)actual.Attributes["attribute3"]); + } + + [Fact] + public void Entity_Can_Remove_Empty_Attribute_During_Serialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var value = new Entity(name, id); + value.Attributes.Add("attribute1", null); + var expected = $"{{\"_reference\":\"{name}:{id}\"}}"; ; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Entity_Can_Report_Incorrect_Attribute_During_Deserialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var value = $"{{\"_reference\":\"{name}:{id}\",\"attribute1\":}}"; // Intentional error: value of `attribute1` is missing! + + // Act + var ex = Assert.Throws(() => JsonConvert.DeserializeObject(value, new EntityConverter())); + + // Assert + Assert.Contains("attribute1", ex.Message); + } + + [Fact] + public void Entity_Can_Serialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var value = new Entity(name, id); + value.Attributes.Add("attribute1", new OptionSetValue(1)); + value.Attributes.Add("attribute2", 2); + value.Attributes.Add("attribute3", 13.37d); + value.Attributes.Add("attribute4", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + var expected = $"{{\"_reference\":\"{name}:{id.ToString()}\",\"attribute1\":{{\"_option\":1}},\"attribute2\":2,\"attribute3\":13.37,\"attribute4\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs b/Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs similarity index 90% rename from src/Innofactor.Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs rename to Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs index c4296d0..b98c85f 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/EntityReferenceConverterTests.cs @@ -1,48 +1,48 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class EntityReferenceConverterTests - { - #region Public Methods - - [Fact] - public void EntityReference_Can_Deserialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var expected = new EntityReference(name, id); - var value = $"{{\"_reference\":\"{name}:{id.ToString()}\"}}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new EntityReferenceConverter()); - - // Assert - Assert.Equal(expected.LogicalName, actual.LogicalName); - Assert.Equal(expected.Id, actual.Id); - } - - [Fact] - public void EntityReference_Can_Serialize() - { - // Arrange - var name = "test"; - var id = Guid.NewGuid(); - var value = new EntityReference(name, id); - var expected = $"{{\"_reference\":\"{name}:{id.ToString()}\"}}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityReferenceConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using System; + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class EntityReferenceConverterTests + { + #region Public Methods + + [Fact] + public void EntityReference_Can_Deserialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var expected = new EntityReference(name, id); + var value = $"{{\"_reference\":\"{name}:{id.ToString()}\"}}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new EntityReferenceConverter()); + + // Assert + Assert.Equal(expected.LogicalName, actual.LogicalName); + Assert.Equal(expected.Id, actual.Id); + } + + [Fact] + public void EntityReference_Can_Serialize() + { + // Arrange + var name = "test"; + var id = Guid.NewGuid(); + var value = new EntityReference(name, id); + var expected = $"{{\"_reference\":\"{name}:{id.ToString()}\"}}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new EntityReferenceConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/GuidConverterTests.cs b/Xrm.Json.Serialization.Tests/GuidConverterTests.cs similarity index 91% rename from src/Innofactor.Xrm.Json.Serialization.Tests/GuidConverterTests.cs rename to Xrm.Json.Serialization.Tests/GuidConverterTests.cs index c6b833e..3f74734 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/GuidConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/GuidConverterTests.cs @@ -1,41 +1,41 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using Newtonsoft.Json; - using Xunit; - - public class GuidConverterTests - { - [Fact] - public void Guid_Can_Deserialize() - { - // Arrange - var expected = Guid.NewGuid(); - var value = $"{{\"_id\":\"{expected.ToString()}\"}}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new GuidConverter()); - - // Assert - Assert.Equal(expected.ToString(), actual.ToString()); - } - - [Fact] - public void Guid_Can_Serialize() - { - // Arrange - var value = Guid.NewGuid(); - var expected = $"{{\"_id\":\"{value.ToString()}\"}}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new GuidConverter()); - - // Assert - Assert.Equal(expected, actual); - } - } -} +namespace Xrm.Json.Serialization.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Newtonsoft.Json; + using Xunit; + + public class GuidConverterTests + { + [Fact] + public void Guid_Can_Deserialize() + { + // Arrange + var expected = Guid.NewGuid(); + var value = $"{{\"_id\":\"{expected.ToString()}\"}}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new GuidConverter()); + + // Assert + Assert.Equal(expected.ToString(), actual.ToString()); + } + + [Fact] + public void Guid_Can_Serialize() + { + // Arrange + var value = Guid.NewGuid(); + var expected = $"{{\"_id\":\"{value.ToString()}\"}}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new GuidConverter()); + + // Assert + Assert.Equal(expected, actual); + } + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/MoneyConverterTests.cs b/Xrm.Json.Serialization.Tests/MoneyConverterTests.cs similarity index 87% rename from src/Innofactor.Xrm.Json.Serialization.Tests/MoneyConverterTests.cs rename to Xrm.Json.Serialization.Tests/MoneyConverterTests.cs index d9b9482..d786a4a 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/MoneyConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/MoneyConverterTests.cs @@ -1,42 +1,42 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class MoneyConverterTests - { - #region Public Methods - - [Fact] - public void Money_Can_Deserialize() - { - // Arrange - var expected = new Money(9.95m); - var value = "{\"_money\":9.95}"; - - // Act - var actual = JsonConvert.DeserializeObject(value, new MoneyConverter()); - - // Assert - Assert.Equal(expected.Value, actual.Value); - } - - [Fact] - public void Money_Can_Serialize() - { - // Arrange - var value = new Money(9.95m); - var expected = "{\"_money\":9.95}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new MoneyConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class MoneyConverterTests + { + #region Public Methods + + [Fact] + public void Money_Can_Deserialize() + { + // Arrange + var expected = new Money(9.95m); + var value = "{\"_money\":9.95}"; + + // Act + var actual = JsonConvert.DeserializeObject(value, new MoneyConverter()); + + // Assert + Assert.Equal(expected.Value, actual.Value); + } + + [Fact] + public void Money_Can_Serialize() + { + // Arrange + var value = new Money(9.95m); + var expected = "{\"_money\":9.95}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new MoneyConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization.Tests/OptionSetValueCollectionConverterTests.cs b/Xrm.Json.Serialization.Tests/OptionSetValueCollectionConverterTests.cs new file mode 100644 index 0000000..5efffef --- /dev/null +++ b/Xrm.Json.Serialization.Tests/OptionSetValueCollectionConverterTests.cs @@ -0,0 +1,142 @@ +namespace Xrm.Json.Serialization.Tests +{ + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class OptionSetValueCollectionConverterTests + { + #region Public Methods + + [Fact] + public void OptionSetValueCollection_Empty_Can_Serialize() + { + // Arrange + var value = new OptionSetValueCollection(); + var expected = "{\"_options\":[]}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetValueCollectionConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void OptionSetValueCollection_Empty_Can_Deserialize() + { + // Arrange + var json = "{\"_options\":[]}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new OptionSetValueCollectionConverter()); + + // Assert + Assert.NotNull(actual); + Assert.Empty(actual); + } + + [Fact] + public void OptionSetValueCollection_Single_Can_Serialize() + { + // Arrange + var value = new OptionSetValueCollection { new OptionSetValue(1) }; + var expected = "{\"_options\":[1]}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetValueCollectionConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void OptionSetValueCollection_Single_Can_Deserialize() + { + // Arrange + var json = "{\"_options\":[1]}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new OptionSetValueCollectionConverter()); + + // Assert + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal(1, actual[0].Value); + } + + [Fact] + public void OptionSetValueCollection_Multiple_Can_Serialize() + { + // Arrange + var value = new OptionSetValueCollection + { + new OptionSetValue(1), + new OptionSetValue(2), + new OptionSetValue(3) + }; + var expected = "{\"_options\":[1,2,3]}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetValueCollectionConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void OptionSetValueCollection_Multiple_Can_Deserialize() + { + // Arrange + var json = "{\"_options\":[1,2,3]}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new OptionSetValueCollectionConverter()); + + // Assert + Assert.NotNull(actual); + Assert.Equal(3, actual.Count); + Assert.Equal(1, actual[0].Value); + Assert.Equal(2, actual[1].Value); + Assert.Equal(3, actual[2].Value); + } + + [Fact] + public void OptionSetValueCollection_LargeNumbers_Can_Serialize() + { + // Arrange + var value = new OptionSetValueCollection + { + new OptionSetValue(100000000), + new OptionSetValue(200000000), + new OptionSetValue(300000000) + }; + var expected = "{\"_options\":[100000000,200000000,300000000]}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetValueCollectionConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void OptionSetValueCollection_LargeNumbers_Can_Deserialize() + { + // Arrange + var json = "{\"_options\":[100000000,200000000,300000000]}"; + + // Act + var actual = JsonConvert.DeserializeObject(json, new OptionSetValueCollectionConverter()); + + // Assert + Assert.NotNull(actual); + Assert.Equal(3, actual.Count); + Assert.Equal(100000000, actual[0].Value); + Assert.Equal(200000000, actual[1].Value); + Assert.Equal(300000000, actual[2].Value); + } + + #endregion Public Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs b/Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs similarity index 87% rename from src/Innofactor.Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs rename to Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs index dd52dd5..a4cf012 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs +++ b/Xrm.Json.Serialization.Tests/OptionSetValueConverterTests.cs @@ -1,42 +1,42 @@ -namespace Innofactor.Xrm.Json.Serialization.Tests -{ - using Innofactor.Xrm.Json.Serialization; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Xunit; - - public class OptionSetValueConverterTests - { - #region Public Methods - - [Fact] - public void OptionSetValue_Can_Deserialize() - { - // Arrange - var value = "{\"_option\":100}"; - var expected = new OptionSetValue(100); - - // Act - var actual = JsonConvert.DeserializeObject(value, new OptionSetConverter()); - - // Assert - Assert.Equal(expected.Value, actual.Value); - } - - [Fact] - public void OptionSetValue_Can_Serialize() - { - // Arrange - var value = new OptionSetValue(100); - var expected = "{\"_option\":100}"; - - // Act - var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetConverter()); - - // Assert - Assert.Equal(expected, actual); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization.Tests +{ + using Xrm.Json.Serialization; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Xunit; + + public class OptionSetValueConverterTests + { + #region Public Methods + + [Fact] + public void OptionSetValue_Can_Deserialize() + { + // Arrange + var value = "{\"_option\":100}"; + var expected = new OptionSetValue(100); + + // Act + var actual = JsonConvert.DeserializeObject(value, new OptionSetConverter()); + + // Assert + Assert.Equal(expected.Value, actual.Value); + } + + [Fact] + public void OptionSetValue_Can_Serialize() + { + // Arrange + var value = new OptionSetValue(100); + var expected = "{\"_option\":100}"; + + // Act + var actual = JsonConvert.SerializeObject(value, Formatting.None, new OptionSetConverter()); + + // Assert + Assert.Equal(expected, actual); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs b/Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs similarity index 68% rename from src/Innofactor.Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs rename to Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs index ddad459..68b2eca 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs +++ b/Xrm.Json.Serialization.Tests/Properties/AssemblyInfo.cs @@ -1,20 +1,20 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Innofactor.Xrm.Json.Serialization.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Innofactor.Xrm.Json.Serialization.Tests")] -[assembly: AssemblyCopyright("Copyright © Innofactor AB 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("ffc3df0d-024d-4193-80eb-16755a283f8e")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Xrm.Json.Serialization.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Xrm.Json.Serialization.Tests")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("ffc3df0d-024d-4193-80eb-16755a283f8e")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Xrm.Json.Serialization.Tests/Xrm.Json.Serialization.Tests.csproj b/Xrm.Json.Serialization.Tests/Xrm.Json.Serialization.Tests.csproj new file mode 100644 index 0000000..d198144 --- /dev/null +++ b/Xrm.Json.Serialization.Tests/Xrm.Json.Serialization.Tests.csproj @@ -0,0 +1,186 @@ + + + + + + + + Debug + AnyCPU + {FFC3DF0D-024D-4193-80EB-16755A283F8E} + Library + Properties + Xrm.Json.Serialization.Tests + Xrm.Json.Serialization.Tests + v4.6.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.3\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.60\lib\net462\Microsoft.Crm.Sdk.Proxy.dll + + + + ..\packages\Microsoft.IdentityModel.7.0.0\lib\net35\microsoft.identitymodel.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.18.3.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.18.3.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll + + + ..\packages\Microsoft.TestPlatform.ObjectModel.18.3.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll + + + ..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.60\lib\net462\Microsoft.Xrm.Sdk.dll + + + ..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll + + + + ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll + + + ..\packages\System.Collections.Immutable.10.0.3\lib\net462\System.Collections.Immutable.dll + + + + + + + + ..\packages\System.IO.Pipelines.10.0.3\lib\net462\System.IO.Pipelines.dll + + + ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll + + + ..\packages\System.Reflection.Metadata.10.0.3\lib\net462\System.Reflection.Metadata.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + + + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Duplex.dll + + + ..\packages\System.ServiceModel.Http.10.0.652802\lib\net462\System.ServiceModel.Http.dll + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Primitives.dll + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Security.dll + + + + ..\packages\System.Text.Encodings.Web.10.0.3\lib\net462\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.10.0.3\lib\net462\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll + + + + + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.9.3\lib\netstandard1.1\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.9.3\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.9.3\lib\net452\xunit.execution.desktop.dll + + + + + + + + + + + + + + + + + + + + + Always + + + + + {19d18e8d-e967-4cbe-8822-617bd1feeaa4} + Xrm.Json.Serialization + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/Xrm.Json.Serialization.Tests/packages.config b/Xrm.Json.Serialization.Tests/packages.config new file mode 100644 index 0000000..64126ec --- /dev/null +++ b/Xrm.Json.Serialization.Tests/packages.config @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/xunit.runner.json b/Xrm.Json.Serialization.Tests/xunit.runner.json similarity index 93% rename from src/Innofactor.Xrm.Json.Serialization.Tests/xunit.runner.json rename to Xrm.Json.Serialization.Tests/xunit.runner.json index cb48467..df1c3d5 100644 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/xunit.runner.json +++ b/Xrm.Json.Serialization.Tests/xunit.runner.json @@ -1,3 +1,3 @@ -{ - "methodDisplay": "method" +{ + "methodDisplay": "method" } \ No newline at end of file diff --git a/Xrm.Json.Serialization.nuspec b/Xrm.Json.Serialization.nuspec new file mode 100644 index 0000000..f4d87a4 --- /dev/null +++ b/Xrm.Json.Serialization.nuspec @@ -0,0 +1,40 @@ + + + + Xrm.Json.Serialization + 1.2026.3.0 + Xrm.Json.Serialization + Alexey Shytikov and Imran Akram + Imran Akram + https://github.com/imranakram/Xrm.Json.Serialization + false + MIT + Compact JSON serialization for Microsoft Dynamics 365/CRM/Dataverse entities. Includes support for AliasedValue (FetchXML linked entities), OptionSetValueCollection (multi-select), BooleanManagedProperty, and all standard CRM data types. Plugin compatible (.NET 4.6.2). + Copyright © 2026 + icon.png + README.md + dynamics365 crm dataverse dynamics xrm json serialization entity entityreference optionset aliasedvalue fetchxml plugin + +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 (#20) +- Target: .NET Framework 4.6.2 (plugin compatible) +- Added comprehensive plugin usage examples + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Xrm.Json.Serialization.sln b/Xrm.Json.Serialization.sln index deca8f2..122575a 100644 --- a/Xrm.Json.Serialization.sln +++ b/Xrm.Json.Serialization.sln @@ -1,15 +1,32 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29728.190 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11520.95 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Innofactor.Xrm.Json.Serialization", "src\Innofactor.Xrm.Json.Serialization\Innofactor.Xrm.Json.Serialization.csproj", "{19D18E8D-E967-4CBE-8822-617BD1FEEAA4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xrm.Json.Serialization", "Xrm.Json.Serialization\Xrm.Json.Serialization.csproj", "{19D18E8D-E967-4CBE-8822-617BD1FEEAA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Innofactor.Xrm.Json.Serialization.Tests", "src\Innofactor.Xrm.Json.Serialization.Tests\Innofactor.Xrm.Json.Serialization.Tests.csproj", "{FFC3DF0D-024D-4193-80EB-16755A283F8E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xrm.Json.Serialization.Tests", "Xrm.Json.Serialization.Tests\Xrm.Json.Serialization.Tests.csproj", "{FFC3DF0D-024D-4193-80EB-16755A283F8E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{A5DE8D43-CD6C-4DE8-A35F-DE24ABBCB9A1}" ProjectSection(SolutionItems) = preProject - src\Innofactor.Xrm.Json.Serialization\Innofactor.Xrm.Json.Serialization.nuspec = src\Innofactor.Xrm.Json.Serialization\Innofactor.Xrm.Json.Serialization.nuspec + Xrm.Json.Serialization.nuspec = Xrm.Json.Serialization.nuspec + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8B877E5D-9440-4F5A-839F-DF0D98FE1F3E}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + GITHUB-ACTIONS-SETUP.md = GITHUB-ACTIONS-SETUP.md + icon.png = icon.png + LICENSE = LICENSE + README.md = README.md + UPGRADE-GUIDE.md = UPGRADE-GUIDE.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{2B58631B-9D4B-4866-A4ED-FDE347BFBB87}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{8CD25595-B36D-4C73-A33C-25CF14E74713}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml + .github\workflows\publish-nuget.yml = .github\workflows\publish-nuget.yml EndProjectSection EndProject Global @@ -30,6 +47,9 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {8CD25595-B36D-4C73-A33C-25CF14E74713} = {2B58631B-9D4B-4866-A4ED-FDE347BFBB87} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2E3B2D97-5450-4063-BC9C-08046DE625A9} EndGlobalSection diff --git a/Xrm.Json.Serialization/AliasedValueConverter.cs b/Xrm.Json.Serialization/AliasedValueConverter.cs new file mode 100644 index 0000000..23eb069 --- /dev/null +++ b/Xrm.Json.Serialization/AliasedValueConverter.cs @@ -0,0 +1,121 @@ +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class AliasedValueConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(AliasedValue); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + if (reader.TokenType != JsonToken.PropertyName || reader.Value?.ToString() != "_aliased") + { + throw new JsonException("Expected _aliased property"); + } + + reader.Read(); + + if (reader.TokenType != JsonToken.String) + { + throw new JsonException("Expected string value for _aliased"); + } + + var serializedValue = reader.Value?.ToString() ?? string.Empty; + var parts = serializedValue.Split('|'); + if (parts.Length < 3) + { + throw new JsonException("Invalid aliased value format. Expected: entityLogicalName|attributeLogicalName|value"); + } + + var entityLogicalName = parts[0]; + var attributeLogicalName = parts[1]; + var value = DeserializeValue(parts[2], reader, serializer); + + reader.Read(); + + return new AliasedValue(entityLogicalName, attributeLogicalName, value); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var aliasedValue = value as AliasedValue; + + writer.WriteStartObject(); + writer.WritePropertyName("_aliased"); + + var serializedValue = SerializeValue(aliasedValue.Value, serializer); + writer.WriteValue($"{aliasedValue.EntityLogicalName}|{aliasedValue.AttributeLogicalName}|{serializedValue}"); + + writer.WriteEndObject(); + } + + #endregion Public Methods + + #region Private Methods + + private string SerializeValue(object value, JsonSerializer serializer) + { + if (value == null) + return string.Empty; + + if (value is EntityReference entityRef) + return $"ref:{entityRef.LogicalName}:{entityRef.Id}"; + if (value is OptionSetValue optionSet) + return $"opt:{optionSet.Value}"; + if (value is Money money) + return $"mon:{money.Value}"; + if (value is DateTime dateTime) + return $"dt:{dateTime:O}"; + if (value is Guid guid) + return $"guid:{guid}"; + if (value is bool boolean) + return $"bool:{boolean}"; + + return value.ToString(); + } + + private object DeserializeValue(string serialized, JsonReader reader, JsonSerializer serializer) + { + if (string.IsNullOrEmpty(serialized)) + return null; + + if (serialized.StartsWith("ref:")) + { + var parts = serialized.Substring(4).Split(':'); + if (parts.Length == 2) + return new EntityReference(parts[0], Guid.Parse(parts[1])); + } + else if (serialized.StartsWith("opt:")) + { + return new OptionSetValue(int.Parse(serialized.Substring(4))); + } + else if (serialized.StartsWith("mon:")) + { + return new Money(decimal.Parse(serialized.Substring(4))); + } + else if (serialized.StartsWith("dt:")) + { + return DateTime.Parse(serialized.Substring(3)); + } + else if (serialized.StartsWith("guid:")) + { + return Guid.Parse(serialized.Substring(5)); + } + else if (serialized.StartsWith("bool:")) + { + return bool.Parse(serialized.Substring(5)); + } + + return serialized; + } + + #endregion Private Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization/BasicsConverter.cs b/Xrm.Json.Serialization/BasicsConverter.cs similarity index 73% rename from src/Innofactor.Xrm.Json.Serialization/BasicsConverter.cs rename to Xrm.Json.Serialization/BasicsConverter.cs index 0587a46..3076113 100644 --- a/src/Innofactor.Xrm.Json.Serialization/BasicsConverter.cs +++ b/Xrm.Json.Serialization/BasicsConverter.cs @@ -1,90 +1,79 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using System.CodeDom; - using System.CodeDom.Compiler; - using System.IO; - using Newtonsoft.Json; - - public class BasicsConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(string) - || objectType == typeof(int) - || objectType == typeof(long) - || objectType == typeof(float) - || objectType == typeof(double) - || objectType == typeof(decimal) - || objectType == typeof(object); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - - if (objectType == typeof(string)) - { - return Finish(reader, reader.ReadAsString()); - } - - if (objectType == typeof(int) || objectType == typeof(long)) - { - // MS Dynamics CRM has no `long` type, only `int` - return Finish(reader, (int)reader.ReadAsInt32()); - } - - if (objectType == typeof(double) || objectType == typeof(float)) - { - // MS Dynamics CRM has no `float` type, only `double` - return Finish(reader, (double)reader.ReadAsDouble()); - } - - if (objectType == typeof(decimal)) - { - return Finish(reader, (decimal)reader.ReadAsDecimal()); - } - - // Default to object rep - reader.Read(); - return Finish(reader, reader.Value); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value.GetType() == typeof(string)) - { - using (var converter = new StringWriter()) - { - using (var provider = CodeDomProvider.CreateProvider("CSharp")) - { - provider.GenerateCodeFromExpression(new CodeSnippetExpression($"\"{value}\""), converter, null); - writer.WriteRawValue(converter.ToString()); - } - } - } - else if (value.GetType() == typeof(object)) - { - writer.WriteValue(JsonConvert.SerializeObject(value)); - } - else - { - writer.WriteValue(value); - } - } - - #endregion Public Methods - - #region Private Methods - - private T Finish(JsonReader reader, T value) - { - reader.Read(); - return value; - } - - #endregion Private Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Newtonsoft.Json; + + public class BasicsConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string) + || objectType == typeof(int) + || objectType == typeof(long) + || objectType == typeof(float) + || objectType == typeof(double) + || objectType == typeof(decimal) + || objectType == typeof(object); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + if (objectType == typeof(string)) + { + return Finish(reader, reader.ReadAsString()); + } + + if (objectType == typeof(int) || objectType == typeof(long)) + { + // MS Dynamics CRM has no `long` type, only `int` + return Finish(reader, (int)reader.ReadAsInt32()); + } + + if (objectType == typeof(double) || objectType == typeof(float)) + { + // MS Dynamics CRM has no `float` type, only `double` + return Finish(reader, (double)reader.ReadAsDouble()); + } + + if (objectType == typeof(decimal)) + { + return Finish(reader, (decimal)reader.ReadAsDecimal()); + } + + // Default to object rep + reader.Read(); + return Finish(reader, reader.Value); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value != null && value.GetType() == typeof(object)) + { + // For generic object type, serialize using JsonConvert + writer.WriteValue(JsonConvert.SerializeObject(value)); + } + else + { + // For all other types (string, int, double, decimal, etc.) + // JsonWriter.WriteValue handles proper escaping automatically + writer.WriteValue(value); + } + } + + #endregion Public Methods + + #region Private Methods + + private T Finish(JsonReader reader, T value) + { + reader.Read(); + return value; + } + + #endregion Private Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization/BooleanManagedPropertyConverter.cs b/Xrm.Json.Serialization/BooleanManagedPropertyConverter.cs new file mode 100644 index 0000000..a827d87 --- /dev/null +++ b/Xrm.Json.Serialization/BooleanManagedPropertyConverter.cs @@ -0,0 +1,47 @@ +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class BooleanManagedPropertyConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(BooleanManagedProperty); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + if (reader.TokenType != JsonToken.PropertyName || reader.Value?.ToString() != "_boolmanaged") + { + throw new JsonException("Expected _boolmanaged property"); + } + + reader.Read(); + + var serializedValue = reader.Value?.ToString() ?? string.Empty; + var parts = serializedValue.Split('|'); + var value = parts.Length > 0 && bool.Parse(parts[0]); + var canBeChanged = parts.Length > 1 ? bool.Parse(parts[1]) : true; + + reader.Read(); + + return new BooleanManagedProperty(value) { CanBeChanged = canBeChanged }; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var managedProperty = value as BooleanManagedProperty; + + writer.WriteStartObject(); + writer.WritePropertyName("_boolmanaged"); + writer.WriteValue($"{managedProperty.Value}|{managedProperty.CanBeChanged}"); + writer.WriteEndObject(); + } + + #endregion Public Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization/DateTimeConverter.cs b/Xrm.Json.Serialization/DateTimeConverter.cs similarity index 93% rename from src/Innofactor.Xrm.Json.Serialization/DateTimeConverter.cs rename to Xrm.Json.Serialization/DateTimeConverter.cs index 6b51a1e..33be770 100644 --- a/src/Innofactor.Xrm.Json.Serialization/DateTimeConverter.cs +++ b/Xrm.Json.Serialization/DateTimeConverter.cs @@ -1,43 +1,43 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Newtonsoft.Json; - - public class DateTimeConverter : JsonConverter - { - #region Public Methods - - public static string Format(DateTime moment) - { - string offset(DateTime value) - { - var result = TimeZone.CurrentTimeZone.GetUtcOffset(value); - return ((result < TimeSpan.Zero) ? "-" : "+") + result.ToString("hhmm"); - } - - return moment.ToString("yyyy-MM-dd HH:mm:ss") + offset(moment); - } - - public override bool CanConvert(Type objectType) => - objectType == typeof(DateTime); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - var value = DateTime.Parse(reader.ReadAsString()); - reader.Read(); - - return value; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("_moment"); - writer.WriteValue(Format((DateTime)value)); - writer.WriteEndObject(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Newtonsoft.Json; + + public class DateTimeConverter : JsonConverter + { + #region Public Methods + + public static string Format(DateTime moment) + { + string offset(DateTime value) + { + var result = TimeZone.CurrentTimeZone.GetUtcOffset(value); + return ((result < TimeSpan.Zero) ? "-" : "+") + result.ToString("hhmm"); + } + + return moment.ToString("yyyy-MM-dd HH:mm:ss") + offset(moment); + } + + public override bool CanConvert(Type objectType) => + objectType == typeof(DateTime); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + var value = DateTime.Parse(reader.ReadAsString()); + reader.Read(); + + return value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("_moment"); + writer.WriteValue(Format((DateTime)value)); + writer.WriteEndObject(); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/EntityCollectionConverter.cs b/Xrm.Json.Serialization/EntityCollectionConverter.cs similarity index 93% rename from src/Innofactor.Xrm.Json.Serialization/EntityCollectionConverter.cs rename to Xrm.Json.Serialization/EntityCollectionConverter.cs index b767dda..068f51c 100644 --- a/src/Innofactor.Xrm.Json.Serialization/EntityCollectionConverter.cs +++ b/Xrm.Json.Serialization/EntityCollectionConverter.cs @@ -1,51 +1,51 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - - public class EntityCollectionConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(EntityCollection); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - - serializer.ContractResolver = new XrmContractResolver(); - - var result = new EntityCollection(); - - while (reader.TokenType != JsonToken.EndArray) - { - result.Entities.Add(serializer.Deserialize(reader)); - - // Skipping closing object definition - reader.Read(); - } - - return result; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - serializer.ContractResolver = new XrmContractResolver(); - - var collection = value as EntityCollection; - - writer.WriteStartArray(); - - foreach (var entity in collection?.Entities) - { - serializer.Serialize(writer, entity); - } - - writer.WriteEndArray(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class EntityCollectionConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(EntityCollection); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + serializer.ContractResolver = new XrmContractResolver(); + + var result = new EntityCollection(); + + while (reader.TokenType != JsonToken.EndArray) + { + result.Entities.Add(serializer.Deserialize(reader)); + + // Skipping closing object definition + reader.Read(); + } + + return result; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.ContractResolver = new XrmContractResolver(); + + var collection = value as EntityCollection; + + writer.WriteStartArray(); + + foreach (var entity in collection?.Entities) + { + serializer.Serialize(writer, entity); + } + + writer.WriteEndArray(); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/EntityConverter.cs b/Xrm.Json.Serialization/EntityConverter.cs similarity index 60% rename from src/Innofactor.Xrm.Json.Serialization/EntityConverter.cs rename to Xrm.Json.Serialization/EntityConverter.cs index 8afa345..d8db25d 100644 --- a/src/Innofactor.Xrm.Json.Serialization/EntityConverter.cs +++ b/Xrm.Json.Serialization/EntityConverter.cs @@ -1,139 +1,214 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - - public class EntityConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(Entity); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - - var reference = EntityReferenceConverter.GetReference(reader); - var entity = new Entity(reference.LogicalName, reference.Id); - - reader.Read(); - - var key = default(string); - - try - { - while (reader.TokenType != JsonToken.EndObject) - { - // Reading attribute name - key = reader.Value.ToString(); - var value = default(object); - - // Noving to next token - reader.Read(); - if (reader.TokenType == JsonToken.StartObject) - { - // Skipping to first property of the object - reader.Read(); - - switch (reader.Value) - { - case "_option": - // Skipping to property value of the object - value = new OptionSetValue((int)reader.ReadAsInt32()); - reader.Read(); - break; - - case "_reference": - // Skipping to property value of the object - value = EntityReferenceConverter.GetReference(reader); - reader.Read(); - break; - - case "_money": - // Skipping to property value of the object - value = new Money((decimal)reader.ReadAsDecimal()); - reader.Read(); - break; - - case "_moment": - // Skipping to property value of the object - value = DateTime.Parse(reader.ReadAsString()); - reader.Read(); - break; - - case "_id": - // Skipping to property value of the object - value = Guid.Parse(reader.ReadAsString()); - reader.Read(); - break; - } - } - else - { - if (reader.Value.GetType() == typeof(long)) - { - // Trying to downscale `long` to `int` if possible - var temp = (long)reader.Value; - - if (temp > int.MaxValue || temp < int.MinValue) - { - value = (long)reader.Value; - } - else - { - value = unchecked((int)temp); - } - } - else if (reader.Value.GetType() == typeof(float)) - { - // Trying to upscale `float` to `double` - value = (double)reader.Value; - } - else - { - value = serializer.Deserialize(reader); - } - } - - entity.Attributes.Add(key, value); - - // Skipping closing object definition - reader.Read(); - } - } - catch (Exception ex) - { - throw new JsonException($"Error deserializing property `{key}`", ex); - } - - return entity; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - serializer.ContractResolver = new XrmContractResolver(); - - var entity = value as Entity; - - writer.WriteStartObject(); - writer.WritePropertyName("_reference"); - writer.WriteValue($"{entity?.LogicalName}:{entity?.Id.ToString()}"); - - foreach (var attribute in entity?.Attributes) - { - if (attribute.Value != null) - { - // If attribute is set to `null` that is equivalent to removing attribute from collection - writer.WritePropertyName(attribute.Key); - serializer.Serialize(writer, attribute.Value); - } - } - - writer.WriteEndObject(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class EntityConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(Entity); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + var reference = EntityReferenceConverter.GetReference(reader); + var entity = new Entity(reference.LogicalName, reference.Id); + + reader.Read(); + + var key = default(string); + + try + { + while (reader.TokenType != JsonToken.EndObject) + { + // Reading attribute name + key = reader.Value.ToString(); + var value = default(object); + + // Noving to next token + reader.Read(); + if (reader.TokenType == JsonToken.StartObject) + { + // Skipping to first property of the object + reader.Read(); + + switch (reader.Value) + { + case "_aliased": + var aliasedStr = reader.ReadAsString(); + var aliasedParts = aliasedStr.Split('|'); + if (aliasedParts.Length >= 3) + { + var entityLogicalName = aliasedParts[0]; + var attributeLogicalName = aliasedParts[1]; + var aliasedValue = DeserializeAliasedValue(aliasedParts[2]); + value = new AliasedValue(entityLogicalName, attributeLogicalName, aliasedValue); + } + reader.Read(); + break; + + case "_options": + var optionsList = new OptionSetValueCollection(); + reader.Read(); + while (reader.TokenType != JsonToken.EndArray) + { + if (reader.TokenType == JsonToken.Integer) + { + optionsList.Add(new OptionSetValue((int)(long)reader.Value)); + } + reader.Read(); + } + value = optionsList; + reader.Read(); + break; + + case "_boolmanaged": + var boolParts = reader.ReadAsString().Split('|'); + var boolValue = boolParts.Length > 0 && bool.Parse(boolParts[0]); + var canBeChanged = boolParts.Length > 1 ? bool.Parse(boolParts[1]) : true; + value = new BooleanManagedProperty(boolValue) { CanBeChanged = canBeChanged }; + reader.Read(); + break; + + case "_option": + // Skipping to property value of the object + value = new OptionSetValue((int)reader.ReadAsInt32()); + reader.Read(); + break; + + case "_reference": + // Skipping to property value of the object + value = EntityReferenceConverter.GetReference(reader); + reader.Read(); + break; + + case "_money": + // Skipping to property value of the object + value = new Money((decimal)reader.ReadAsDecimal()); + reader.Read(); + break; + + case "_moment": + // Skipping to property value of the object + value = DateTime.Parse(reader.ReadAsString()); + reader.Read(); + break; + + case "_id": + // Skipping to property value of the object + value = Guid.Parse(reader.ReadAsString()); + reader.Read(); + break; + } + } + else + { + if (reader.Value.GetType() == typeof(long)) + { + // Trying to downscale `long` to `int` if possible + var temp = (long)reader.Value; + + if (temp > int.MaxValue || temp < int.MinValue) + { + value = (long)reader.Value; + } + else + { + value = unchecked((int)temp); + } + } + else if (reader.Value.GetType() == typeof(float)) + { + // Trying to upscale `float` to `double` + value = (double)reader.Value; + } + else + { + value = serializer.Deserialize(reader); + } + } + + entity.Attributes.Add(key, value); + + // Skipping closing object definition + reader.Read(); + } + } + catch (Exception ex) + { + throw new JsonException($"Error deserializing property `{key}`", ex); + } + + return entity; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.ContractResolver = new XrmContractResolver(); + + var entity = value as Entity; + + writer.WriteStartObject(); + writer.WritePropertyName("_reference"); + writer.WriteValue($"{entity?.LogicalName}:{entity?.Id.ToString()}"); + + foreach (var attribute in entity?.Attributes) + { + if (attribute.Value != null) + { + // If attribute is set to `null` that is equivalent to removing attribute from collection + writer.WritePropertyName(attribute.Key); + serializer.Serialize(writer, attribute.Value); + } + } + + writer.WriteEndObject(); + } + + #endregion Public Methods + + #region Private Methods + + private static object DeserializeAliasedValue(string serialized) + { + if (string.IsNullOrEmpty(serialized)) + return null; + + if (serialized.StartsWith("ref:")) + { + var parts = serialized.Substring(4).Split(':'); + if (parts.Length == 2) + return new EntityReference(parts[0], Guid.Parse(parts[1])); + } + else if (serialized.StartsWith("opt:")) + { + return new OptionSetValue(int.Parse(serialized.Substring(4))); + } + else if (serialized.StartsWith("mon:")) + { + return new Money(decimal.Parse(serialized.Substring(4))); + } + else if (serialized.StartsWith("dt:")) + { + return DateTime.Parse(serialized.Substring(3)); + } + else if (serialized.StartsWith("guid:")) + { + return Guid.Parse(serialized.Substring(5)); + } + else if (serialized.StartsWith("bool:")) + { + return bool.Parse(serialized.Substring(5)); + } + + return serialized; + } + + #endregion Private Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/EntityReferenceConverter.cs b/Xrm.Json.Serialization/EntityReferenceConverter.cs similarity index 93% rename from src/Innofactor.Xrm.Json.Serialization/EntityReferenceConverter.cs rename to Xrm.Json.Serialization/EntityReferenceConverter.cs index 8760e0c..74df52c 100644 --- a/src/Innofactor.Xrm.Json.Serialization/EntityReferenceConverter.cs +++ b/Xrm.Json.Serialization/EntityReferenceConverter.cs @@ -1,55 +1,55 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - - public class EntityReferenceConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(EntityReference); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - var value = GetReference(reader); - reader.Read(); - - return value; - } - - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var reference = value as EntityReference; - - writer.WriteStartObject(); - writer.WritePropertyName("_reference"); - writer.WriteValue($"{reference?.LogicalName}:{reference?.Id.ToString()}"); - writer.WriteEndObject(); - } - - #endregion Public Methods - - #region Internal Methods - - internal static EntityReference GetReference(JsonReader reader) - { - var chunks = reader.ReadAsString().Split(':'); - var name = string.Empty; - var id = Guid.Empty; - - if (chunks.Length > 1) - { - name = chunks[0]; - Guid.TryParse(chunks[1], out id); - } - - return new EntityReference(name, id); - } - - #endregion Internal Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class EntityReferenceConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(EntityReference); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + var value = GetReference(reader); + reader.Read(); + + return value; + } + + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var reference = value as EntityReference; + + writer.WriteStartObject(); + writer.WritePropertyName("_reference"); + writer.WriteValue($"{reference?.LogicalName}:{reference?.Id.ToString()}"); + writer.WriteEndObject(); + } + + #endregion Public Methods + + #region Internal Methods + + internal static EntityReference GetReference(JsonReader reader) + { + var chunks = reader.ReadAsString().Split(':'); + var name = string.Empty; + var id = Guid.Empty; + + if (chunks.Length > 1) + { + name = chunks[0]; + Guid.TryParse(chunks[1], out id); + } + + return new EntityReference(name, id); + } + + #endregion Internal Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization/EntitySerializer.cs b/Xrm.Json.Serialization/EntitySerializer.cs new file mode 100644 index 0000000..5c0bf83 --- /dev/null +++ b/Xrm.Json.Serialization/EntitySerializer.cs @@ -0,0 +1,49 @@ +namespace Xrm.Json.Serialization +{ + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public static class EntitySerializer + { + #region Private Fields + + private static readonly JsonSerializerSettings DefaultSettings = new JsonSerializerSettings + { + ContractResolver = new XrmContractResolver(), + NullValueHandling = NullValueHandling.Ignore + }; + + private static readonly JsonSerializerSettings IndentedSettings = new JsonSerializerSettings + { + ContractResolver = new XrmContractResolver(), + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }; + + #endregion Private Fields + + #region Public Methods + + public static string Serialize(Entity entity, bool indented = false) + { + return JsonConvert.SerializeObject(entity, indented ? IndentedSettings : DefaultSettings); + } + + public static string Serialize(EntityCollection collection, bool indented = false) + { + return JsonConvert.SerializeObject(collection, indented ? IndentedSettings : DefaultSettings); + } + + public static Entity DeserializeEntity(string json) + { + return JsonConvert.DeserializeObject(json, DefaultSettings); + } + + public static EntityCollection DeserializeCollection(string json) + { + return JsonConvert.DeserializeObject(json, DefaultSettings); + } + + #endregion Public Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization/GuidConverter.cs b/Xrm.Json.Serialization/GuidConverter.cs similarity index 91% rename from src/Innofactor.Xrm.Json.Serialization/GuidConverter.cs rename to Xrm.Json.Serialization/GuidConverter.cs index 7160baa..2ffe976 100644 --- a/src/Innofactor.Xrm.Json.Serialization/GuidConverter.cs +++ b/Xrm.Json.Serialization/GuidConverter.cs @@ -1,32 +1,32 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Newtonsoft.Json; - - public class GuidConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(Guid); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - var value = Guid.Parse(reader.ReadAsString()); - reader.Read(); - - return value; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("_id"); - writer.WriteValue(((Guid)value).ToString()); - writer.WriteEndObject(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Newtonsoft.Json; + + public class GuidConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(Guid); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + var value = Guid.Parse(reader.ReadAsString()); + reader.Read(); + + return value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("_id"); + writer.WriteValue(((Guid)value).ToString()); + writer.WriteEndObject(); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec b/Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec new file mode 100644 index 0000000..82309d0 --- /dev/null +++ b/Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec @@ -0,0 +1,22 @@ + + + + Xrm.Json.Serialization + 2.0.0 + Xrm.Json.Serialization + Alexey Shytikov, Imran Akram, and contributors + Biznamics + https://github.com/Biznamics/Xrm.Json.Serialization + false + MIT + Compact JSON serialization for Microsoft Dynamics 365/CRM/Dataverse entities using Newtonsoft.Json. Supports Entity, EntityReference, EntityCollection, OptionSetValue, Money, DateTime, Guid, and basic CLR types with CRM-optimized serialization. + Copyright © Biznamics 2025 + dynamics365 crm dataverse json serialization xrm entity newtonsoft + + + + + + + + \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/MoneyConverter.cs b/Xrm.Json.Serialization/MoneyConverter.cs similarity index 91% rename from src/Innofactor.Xrm.Json.Serialization/MoneyConverter.cs rename to Xrm.Json.Serialization/MoneyConverter.cs index ce1cca2..97e12ab 100644 --- a/src/Innofactor.Xrm.Json.Serialization/MoneyConverter.cs +++ b/Xrm.Json.Serialization/MoneyConverter.cs @@ -1,33 +1,33 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - - public class MoneyConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(Money); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - var value = new Money((decimal)reader.ReadAsDecimal()); - reader.Read(); - - return value; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("_money"); - writer.WriteValue((value as Money).Value); - writer.WriteEndObject(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class MoneyConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(Money); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + var value = new Money((decimal)reader.ReadAsDecimal()); + reader.Read(); + + return value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("_money"); + writer.WriteValue((value as Money).Value); + writer.WriteEndObject(); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization/OptionSetValueCollectionConverter.cs b/Xrm.Json.Serialization/OptionSetValueCollectionConverter.cs new file mode 100644 index 0000000..ac9f85f --- /dev/null +++ b/Xrm.Json.Serialization/OptionSetValueCollectionConverter.cs @@ -0,0 +1,71 @@ +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class OptionSetValueCollectionConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(OptionSetValueCollection); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + + if (reader.TokenType != JsonToken.PropertyName || reader.Value?.ToString() != "_options") + { + throw new JsonException("Expected _options property"); + } + + reader.Read(); + + if (reader.TokenType != JsonToken.StartArray) + { + throw new JsonException("Expected array for option set values"); + } + + var collection = new OptionSetValueCollection(); + + reader.Read(); + + while (reader.TokenType != JsonToken.EndArray) + { + if (reader.TokenType == JsonToken.Integer) + { + collection.Add(new OptionSetValue((int)(long)reader.Value)); + } + + reader.Read(); + } + + reader.Read(); + + return collection; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var collection = value as OptionSetValueCollection; + + writer.WriteStartObject(); + writer.WritePropertyName("_options"); + writer.WriteStartArray(); + + if (collection != null) + { + foreach (var optionValue in collection) + { + writer.WriteValue(optionValue.Value); + } + } + + writer.WriteEndArray(); + writer.WriteEndObject(); + } + + #endregion Public Methods + } +} diff --git a/src/Innofactor.Xrm.Json.Serialization/OptionSetValueConverter.cs b/Xrm.Json.Serialization/OptionSetValueConverter.cs similarity index 92% rename from src/Innofactor.Xrm.Json.Serialization/OptionSetValueConverter.cs rename to Xrm.Json.Serialization/OptionSetValueConverter.cs index 6f7ab89..ff51027 100644 --- a/src/Innofactor.Xrm.Json.Serialization/OptionSetValueConverter.cs +++ b/Xrm.Json.Serialization/OptionSetValueConverter.cs @@ -1,33 +1,33 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - - public class OptionSetConverter : JsonConverter - { - #region Public Methods - - public override bool CanConvert(Type objectType) => - objectType == typeof(OptionSetValue); - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - reader.Read(); - var value = new OptionSetValue((int)reader.ReadAsInt32()); - reader.Read(); - - return value; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("_option"); - writer.WriteValue((value as OptionSetValue).Value); - writer.WriteEndObject(); - } - - #endregion Public Methods - } +namespace Xrm.Json.Serialization +{ + using System; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + + public class OptionSetConverter : JsonConverter + { + #region Public Methods + + public override bool CanConvert(Type objectType) => + objectType == typeof(OptionSetValue); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + reader.Read(); + var value = new OptionSetValue((int)reader.ReadAsInt32()); + reader.Read(); + + return value; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName("_option"); + writer.WriteValue((value as OptionSetValue).Value); + writer.WriteEndObject(); + } + + #endregion Public Methods + } } \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/Properties/AssemblyInfo.cs b/Xrm.Json.Serialization/Properties/AssemblyInfo.cs similarity index 73% rename from src/Innofactor.Xrm.Json.Serialization/Properties/AssemblyInfo.cs rename to Xrm.Json.Serialization/Properties/AssemblyInfo.cs index 188df0d..fc94166 100644 --- a/src/Innofactor.Xrm.Json.Serialization/Properties/AssemblyInfo.cs +++ b/Xrm.Json.Serialization/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Innofactor.Xrm.Json.Serialization")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Innofactor.Xrm.Json.Serialization")] -[assembly: AssemblyCopyright("Copyright © Innofactor AB 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("19d18e8d-e967-4cbe-8822-617bd1feeaa4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Xrm.Json.Serialization")] +[assembly: AssemblyDescription("Compact JSON serialization for Microsoft Dynamics 365/CRM/Dataverse entities")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("ImranAkram")] +[assembly: AssemblyProduct("Xrm.Json.Serialization")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("19d18e8d-e967-4cbe-8822-617bd1feeaa4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.0.0.0")] diff --git a/src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.csproj b/Xrm.Json.Serialization/Xrm.Json.Serialization.csproj similarity index 52% rename from src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.csproj rename to Xrm.Json.Serialization/Xrm.Json.Serialization.csproj index 71e8ad7..68e9692 100644 --- a/src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.csproj +++ b/Xrm.Json.Serialization/Xrm.Json.Serialization.csproj @@ -1,117 +1,129 @@ - - - - - Debug - AnyCPU - {19D18E8D-E967-4CBE-8822-617BD1FEEAA4} - Library - Properties - Innofactor.Xrm.Json.Serialization - Innofactor.Xrm.Json.Serialization - v4.6.2 - 512 - true - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - ..\..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.46\lib\net462\Microsoft.Crm.Sdk.Proxy.dll - - - ..\..\packages\Microsoft.IdentityModel.7.0.0\lib\net35\microsoft.identitymodel.dll - - - ..\..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.46\lib\net462\Microsoft.Xrm.Sdk.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - - ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - - - - - ..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll - - - - ..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - ..\..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll - - - ..\..\packages\System.Text.Json.6.0.6\lib\net461\System.Text.Json.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + + + Debug + AnyCPU + {19D18E8D-E967-4CBE-8822-617BD1FEEAA4} + Library + Properties + Xrm.Json.Serialization + Xrm.Json.Serialization + v4.6.2 + 512 + true + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.3\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.60\lib\net462\Microsoft.Crm.Sdk.Proxy.dll + + + ..\packages\Microsoft.IdentityModel.7.0.0\lib\net35\microsoft.identitymodel.dll + + + ..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.60\lib\net462\Microsoft.Xrm.Sdk.dll + + + ..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll + + + + ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll + + + + + + + ..\packages\System.IO.Pipelines.10.0.3\lib\net462\System.IO.Pipelines.dll + + + ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll + + + + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Duplex.dll + + + ..\packages\System.ServiceModel.Http.10.0.652802\lib\net462\System.ServiceModel.Http.dll + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Primitives.dll + + + ..\packages\System.ServiceModel.Primitives.10.0.652802\lib\net462\System.ServiceModel.Security.dll + + + + ..\packages\System.Text.Encodings.Web.10.0.3\lib\net462\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.10.0.3\lib\net462\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.6.2\lib\net462\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/XrmContractResolver.cs b/Xrm.Json.Serialization/XrmContractResolver.cs similarity index 82% rename from src/Innofactor.Xrm.Json.Serialization/XrmContractResolver.cs rename to Xrm.Json.Serialization/XrmContractResolver.cs index f26f240..6680dfc 100644 --- a/src/Innofactor.Xrm.Json.Serialization/XrmContractResolver.cs +++ b/Xrm.Json.Serialization/XrmContractResolver.cs @@ -1,50 +1,53 @@ -namespace Innofactor.Xrm.Json.Serialization -{ - using System; - using System.Collections.Generic; - using Microsoft.Xrm.Sdk; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; - - internal class XrmContractResolver : DefaultContractResolver - { - #region Private Fields - - private readonly Dictionary converters; - - #endregion Private Fields - - #region Public Constructors - - public XrmContractResolver() - { - converters = new Dictionary() - { - { typeof(DateTime), new DateTimeConverter()}, - { typeof(Entity), new EntityConverter() }, - { typeof(EntityCollection), new EntityCollectionConverter() }, - { typeof(EntityReference), new EntityReferenceConverter() }, - { typeof(Guid), new GuidConverter() }, - { typeof(Money), new MoneyConverter() }, - { typeof(OptionSetValue), new OptionSetConverter()} - }; - } - - #endregion Public Constructors - - #region Protected Methods - - protected override JsonConverter ResolveContractConverter(Type objectType) - { - if (!converters.TryGetValue(objectType, out var matchingConverter)) - { - return new BasicsConverter(); - } - - return matchingConverter; - } - - #endregion Protected Methods - - } +namespace Xrm.Json.Serialization +{ + using System; + using System.Collections.Generic; + using Microsoft.Xrm.Sdk; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + internal class XrmContractResolver : DefaultContractResolver + { + #region Private Fields + + private readonly Dictionary converters; + + #endregion Private Fields + + #region Public Constructors + + public XrmContractResolver() + { + converters = new Dictionary() + { + { typeof(AliasedValue), new AliasedValueConverter() }, + { typeof(BooleanManagedProperty), new BooleanManagedPropertyConverter() }, + { typeof(DateTime), new DateTimeConverter()}, + { typeof(Entity), new EntityConverter() }, + { typeof(EntityCollection), new EntityCollectionConverter() }, + { typeof(EntityReference), new EntityReferenceConverter() }, + { typeof(Guid), new GuidConverter() }, + { typeof(Money), new MoneyConverter() }, + { typeof(OptionSetValue), new OptionSetConverter()}, + { typeof(OptionSetValueCollection), new OptionSetValueCollectionConverter() } + }; + } + + #endregion Public Constructors + + #region Protected Methods + + protected override JsonConverter ResolveContractConverter(Type objectType) + { + if (!converters.TryGetValue(objectType, out var matchingConverter)) + { + return new BasicsConverter(); + } + + return matchingConverter; + } + + #endregion Protected Methods + + } } \ No newline at end of file diff --git a/Xrm.Json.Serialization/packages.config b/Xrm.Json.Serialization/packages.config new file mode 100644 index 0000000..31addf0 --- /dev/null +++ b/Xrm.Json.Serialization/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 3e7c1dd..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -version: 1.0.{build} -image: - - Visual Studio 2017 -configuration: Release -platform: Any CPU -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '{version}' - assembly_file_version: '{version}' - assembly_informational_version: '{version}' -before_build: -- cmd: nuget restore -build: - parallel: true - verbosity: minimal -after_build: -- cmd: nuget pack src\Innofactor.Xrm.Json.Serialization\Innofactor.Xrm.Json.Serialization.csproj -Properties Configuration=Release;Platform=AnyCPU -Version %APPVEYOR_BUILD_VERSION% -artifacts: -- path: '*.nupkg' -deploy: -- provider: NuGet - api_key: - secure: plVZVEG/g8+AKx8nujq5O2I7zbAXSnZBnL/kQXA4aJ+5NOqCEkdWvSj3zvUsgxtU \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..1ba8d25 Binary files /dev/null and b/icon.png differ diff --git a/src/.editorconfig b/src/.editorconfig deleted file mode 100644 index 2feab68..0000000 --- a/src/.editorconfig +++ /dev/null @@ -1,68 +0,0 @@ -root = true - -# CSharp code style settings: -[*.cs] -csharp_add_imports_to_deepest_scope = true -csharp_force_attribute_style = separate -csharp_indent_switch_labels = true -csharp_indent_type_constraints = true -csharp_int_align_assignments = true:error -csharp_int_align_fields = true:error -csharp_int_align_properties = true:error -csharp_int_align_variables = true:error -csharp_keep_existing_attribute_arrangement = true -csharp_keep_user_linebreaks = true -csharp_preferred_modifier_order = public,protected,internal,private,new,abstract,virtual,override,sealed,static,readonly,extern,unsafe,volatile,async:error -csharp_qualified_using_at_nested_scope = true -csharp_space_after_cast = false -csharp_space_before_trailing_comment = true -csharp_stick_comment = false -csharp_style_var_for_built_in_types = true -csharp_style_var_when_type_is_apparent = true -csharp_wrap_before_arrow_with_expressions = false -csharp_wrap_parameters_style = chop_if_long -dotnet_sort_system_directives_first = true - -# New lines -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true - -# Placing -csharp_place_accessor_attribute_on_same_line = false -csharp_place_accessorholder_attribute_on_same_line = false -csharp_place_constructor_initializer_on_same_line = false -csharp_place_expr_accessor_on_single_line = false -csharp_place_expr_method_on_single_line = false -csharp_place_expr_property_on_single_line = false -csharp_place_field_attribute_on_same_line = false -csharp_place_method_attribute_on_same_line = false -csharp_place_simple_blocks_on_single_line = false -csharp_place_type_attribute_on_same_line = false -csharp_place_type_constraints_on_same_line = false - -# Expressions -csharp_style_expression_bodied_accessors = true -csharp_style_expression_bodied_constructors = true -csharp_style_expression_bodied_indexers = true -csharp_style_expression_bodied_methods = true:suggestion -csharp_style_expression_bodied_operators = true:suggestion -csharp_style_expression_bodied_properties = true:suggestion - -# Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion - -# Comments -xmldoc_indent_text = remove_indent -xmldoc_linebreaks_inside_tags_for_elements_with_child_elements = true - -indent_size = 4 -max_line_length = 200 -use_spaces = true diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/Innofactor.Xrm.Json.Serialization.Tests.csproj b/src/Innofactor.Xrm.Json.Serialization.Tests/Innofactor.Xrm.Json.Serialization.Tests.csproj deleted file mode 100644 index 11dbacf..0000000 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/Innofactor.Xrm.Json.Serialization.Tests.csproj +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - Debug - AnyCPU - {FFC3DF0D-024D-4193-80EB-16755A283F8E} - Library - Properties - Innofactor.Xrm.Json.Serialization.Tests - Innofactor.Xrm.Json.Serialization.Tests - v4.6.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll - - - ..\..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.46\lib\net462\Microsoft.Crm.Sdk.Proxy.dll - - - ..\..\packages\Microsoft.IdentityModel.7.0.0\lib\net35\microsoft.identitymodel.dll - - - ..\..\packages\Microsoft.CrmSdk.CoreAssemblies.9.0.2.46\lib\net462\Microsoft.Xrm.Sdk.dll - - - ..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - - - - ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - - - - - ..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll - - - - ..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - ..\..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll - - - ..\..\packages\System.Text.Json.6.0.6\lib\net461\System.Text.Json.dll - - - ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll - - - ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - - - - - - ..\..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll - - - ..\..\packages\xunit.assert.2.4.2\lib\netstandard1.1\xunit.assert.dll - - - ..\..\packages\xunit.extensibility.core.2.4.2\lib\net452\xunit.core.dll - - - ..\..\packages\xunit.extensibility.execution.2.4.2\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - - - Always - - - - - - - - {19d18e8d-e967-4cbe-8822-617bd1feeaa4} - Innofactor.Xrm.Json.Serialization - - - - - - - \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization.Tests/packages.config b/src/Innofactor.Xrm.Json.Serialization.Tests/packages.config deleted file mode 100644 index 89f697b..0000000 --- a/src/Innofactor.Xrm.Json.Serialization.Tests/packages.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec b/src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec deleted file mode 100644 index e0d2bc0..0000000 --- a/src/Innofactor.Xrm.Json.Serialization/Innofactor.Xrm.Json.Serialization.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - Xrm.Json.Serialization - 1.0.0 - Xrm.Json.Serialization - Alexey Shytikov and Imran Akram - Innofactor AB - https://github.com/Innofactor/Xrm.Json.Serialization - false - https://licenses.nuget.org/MIT - Compact JSON serialization for Microsoft Dynamics 365/CRM/Dataverse entities - Copyright © Innofactor AB 2022 - https://www.innofactor.com/globalassets/other-sizes/innofactor-small-logo.png - - \ No newline at end of file diff --git a/src/Innofactor.Xrm.Json.Serialization/packages.config b/src/Innofactor.Xrm.Json.Serialization/packages.config deleted file mode 100644 index a729a8a..0000000 --- a/src/Innofactor.Xrm.Json.Serialization/packages.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file