How to Obfuscate .NET Applications
That Use JSON and XML Serialization
Protect C# and VB.NET codebases utilizing System.Text.Json, Newtonsoft.Json, or XmlSerializer without corrupting DTO property bindings or polymorphic payloads.
Direct Answer
To protect .NET applications that use JSON or XML serialization, un-attributed Data Transfer Object (DTO) property names and polymorphic type discriminators must be explicitly excluded from identifier scrambling so runtime string keys match serialized payloads. Alternatively, decorate properties with explicit contract attributes (such as [JsonPropertyName] or [JsonProperty]), which allows Opaquer .NET Obfuscator to scramble the C# property symbols while preserving the string metadata required by serializers. Meanwhile, all internal engines, custom converters, and business logic retain maximum string encryption and control-flow protection.
1 The Problem
Modern .NET serialization engines-including System.Text.Json, Newtonsoft.Json (Json.NET), and System.Xml.Serialization.XmlSerializer-rely directly on metadata reflection to read and write object graphs.
The Disconnect Between Source Symbols and Payload Keys
Consider a simple C# property declaration versus what a JSON parser looks for at runtime:
When an obfuscator naively scrambles metadata symbols without rules targeting DTO properties, it renames CustomerName in assembly metadata to a non-descriptive token like a.
- Un-attributed Properties: The serializer reflects over the class, finds property
a, and searches incoming JSON for key"a". Because the JSON payload contains"CustomerName", the parser skips binding. The property remainsnullor default. - Polymorphic Types: Polymorphic inheritance mechanisms-such as
JsonDerivedTypein System.Text.Json,$typein Newtonsoft.Json, or[XmlInclude]in XML-rely on exact type names or explicit discriminator strings. Scrambling class names breaks polymorphic type instantiation.
Runtime JSON Deserialization Pipeline
2 Serialization DTO Code Examples
The following practical scenario demonstrates DTO contracts using System.Text.Json, Newtonsoft.Json, and System.Xml.Serialization, including polymorphic inheritance.
using System;
using System.Text.Json.Serialization;
using System.Xml.Serialization;
using Newtonsoft.Json;
namespace MyApp.Contracts
{
// 1. System.Text.Json Polymorphic Base Contract
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(StandardCustomerDto), typeDiscriminator: "standard")]
[JsonDerivedType(typeof(PremiumCustomerDto), typeDiscriminator: "premium")]
public abstract class CustomerBaseDto
{
// Explicit attribute allows C# symbol renaming without breaking JSON key "customer_id"
[JsonPropertyName("customer_id")]
[JsonProperty("customer_id")]
[XmlElement("CustomerID")]
public int CustomerId { get; set; }
// Un-attributed property: MUST remain stable if JSON key is "CustomerName"
public string CustomerName { get; set; }
}
public class StandardCustomerDto : CustomerBaseDto
{
public double MonthlyCreditLimit { get; set; }
}
public class PremiumCustomerDto : CustomerBaseDto
{
[JsonPropertyName("vip_support_level")]
public int VipSupportLevel { get; set; }
}
// 2. System.Xml.Serialization Contract
[XmlRoot("OrderEnvelope")]
[XmlInclude(typeof(StandardCustomerDto))]
[XmlInclude(typeof(PremiumCustomerDto))]
public class OrderEnvelope
{
[XmlAttribute("OrderId")]
public string OrderId { get; set; }
[XmlElement("CustomerPayload")]
public CustomerBaseDto Customer { get; set; }
}
}
Imports System
Imports System.Text.Json.Serialization
Imports System.Xml.Serialization
Imports Newtonsoft.Json
Namespace MyApp.Contracts
' 1. System.Text.Json Polymorphic Base Contract
<JsonPolymorphic(TypeDiscriminatorPropertyName:="$type")>
<JsonDerivedType(GetType(StandardCustomerDto), typeDiscriminator:="standard")>
<JsonDerivedType(GetType(PremiumCustomerDto), typeDiscriminator:="premium")>
Public MustInherit Class CustomerBaseDto
' Explicit attribute allows VB symbol renaming without breaking JSON key "customer_id"
<JsonPropertyName("customer_id")>
<JsonProperty("customer_id")>
<XmlElement("CustomerID")>
Public Property CustomerId As Integer
' Un-attributed property: MUST remain stable if JSON key is "CustomerName"
Public Property CustomerName As String
End Class
Public Class StandardCustomerDto
Inherits CustomerBaseDto
Public Property MonthlyCreditLimit As Double
End Class
Public Class PremiumCustomerDto
Inherits CustomerBaseDto
<JsonPropertyName("vip_support_level")>
Public Property VipSupportLevel As Integer
End Class
' 2. System.Xml.Serialization Contract
<XmlRoot("OrderEnvelope")>
<XmlInclude(GetType(StandardCustomerDto))>
<XmlInclude(GetType(PremiumCustomerDto))>
Public Class OrderEnvelope
<XmlAttribute("OrderId")>
Public Property OrderId As String
<XmlElement("CustomerPayload")>
Public Property Customer As CustomerBaseDto
End Class
End Namespace
3 What Happens Under Naive Obfuscation
Applying default, indiscriminate symbol renaming across DTO assemblies causes severe runtime failures:
Un-attributed properties renamed to a or b fail key lookup during deserialization. Values default silently to null or 0 without throwing exceptions.
Scrambling concrete polymorphic class names breaks JsonDerivedType or Newtonsoft's $type resolver, throwing NotSupportedException or JsonException.
XmlSerializer generates dynamic C# code at runtime. Scrambled type names inside [XmlInclude] cause schema validation and deserialization crashes.
4 Step-by-Step Opaquer Configuration
Follow this workflow in Opaquer .NET Obfuscator to protect serialization-heavy assemblies while ensuring 100% contract stability.
Audit DTOs, Contracts, and Polymorphic Types
Categorize your serialization models into two groups:
• Explicit Contracts: Properties with explicit attributes ([JsonPropertyName], [JsonProperty], [XmlElement]).
• Un-attributed Contracts: DTOs relying on direct property symbol names for key matching.
Open Assembly in Opaquer .NET Obfuscator
Launch Opaquer. Click Open File and select your assembly. Opaquer parses metadata and constructs an interactive tree under the Settings Tab.
Locate DTO Namespaces in Assembly Tree
Expand namespace nodes (e.g., MyApp.Contracts). If all DTOs reside in a dedicated contract namespace, you can manage renaming exemptions globally at the namespace level.
Configure Member Rename Exemptions
Apply exemptions based on attribute usage:
Uncheck Rename Properties and Rename Types for DTO classes to keep symbol metadata intact.
Leave symbol renaming Enabled. Opaquer will scramble the C# property names, but the attribute string metadata ("customer_id") remains intact for the serializer.
Keep String Encryption and Control Flow Active for Internal Engines
Excluding DTO names does not compromise security. Enable Strings Encryption and High-Intensity Control Flow in Opaquer. Business logic, custom JsonConverter implementations, and core processing algorithms receive maximum protection.
5 Before/After Visual Decompiler View (ILSpy)
Observe how decompilers view protected DTO models and processing engines when using Opaquer's selective controls:
6 Testing the Obfuscated Application
Implement automated round-trip testing against protected build artifacts before production deployment:
Serialize a populated object to JSON/XML, deserialize back into an object instance, and assert that all properties match original values.
Feed raw JSON strings from external API specifications directly into deserializers to verify contract compliance.
Test payloads containing derived types (e.g., "premium") to verify concrete subclass resolution.
7 Common Failures and Fixes Matrix
| Symptom / Error | Root Cause | Solution in Opaquer |
|---|---|---|
| CustomerName property is null after deserialization | Un-attributed property was renamed by identifier scrambling. | Uncheck Rename Properties for DTO class in Opaquer tree view. |
| JsonException: Could not resolve polymorphic type | Concrete subclass class name scrambled; discriminator lookup failed. | Specify explicit type discriminator strings in [JsonDerivedType] or exclude subclass names. |
| XmlException: Type 'a' was not expected | XmlSerializer encountered renamed class inside [XmlInclude]. |
Exclude XML model class names from renaming under assembly tree. |
| Newtonsoft $type deserialization crash | Full assembly qualified name in $type property was mutated. |
Implement custom SerializationBinder or exclude DTO types. |
8 Production Recommendations
Decorate properties with [JsonPropertyName("name")] or [JsonProperty("name")]. This enables Opaquer to scramble C# property symbol names while leaving runtime key strings intact.
Place all data transfer contracts into MyApp.Contracts. In Opaquer, exclude this namespace from symbol renaming while applying full string encryption and control flow protection to MyApp.Engine.
Save exclusion rules in an Opaquer project file (opaquer_rules.opq) and integrate into Azure Pipelines or GitHub Actions:
Opaquer.exe /input:MyApp.dll /output:Protected/MyApp.dll /config:opaquer_rules.opq
9 Frequently Asked Questions
[JsonPropertyName("customer_name")]). Because serializers read the attribute's string payload instead of reflecting on the property symbol name, Opaquer can safely scramble the underlying C# property identifier without affecting runtime key matching.