Utf8Json

SerializationC#.NETJSONHigh PerformanceZero Allocation

Library

Utf8Json - Fastest and Zero Allocation JSON Serializer for C#

Overview

Utf8Json is an ultra-high-performance JSON serializer developed for C# (.NET, .NET Core, Unity, Xamarin). By directly reading and writing UTF-8 binary data, it completely eliminates string conversion overhead and achieves zero allocation. Through thorough performance optimizations including dynamic IL code generation, automata-based property matching, and custom numeric conversion algorithms, it achieves performance that significantly exceeds existing JSON libraries.

Details

Utf8Json is a performance-focused JSON serializer developed by neuecc (Yoshifumi Kawai), also known for creating MessagePackSharp and ZeroFormatter. While traditional JSON libraries require conversion to UTF-16 strings, Utf8Json processes UTF-8 byte arrays directly, achieving dramatic performance improvements.

The library's core feature is its zero-allocation design. Buffers under 64KB are obtained from a thread-local memory pool, minimizing garbage collection overhead. It dynamically generates IL code at runtime to create optimized formatters for custom types, with pre-code generation support for AOT environments.

Property matching employs an automata-based approach, matching property names directly from raw UTF-8 bytes. For numeric conversions, it implements custom itoa/atoi and dtoa/atod algorithms, enabling direct conversion without string intermediates. These optimizations achieve 2-4x performance improvements compared to existing JSON libraries.

Note that the original repository has been archived, and community forks are now recommended for use.

Advantages and Disadvantages

Advantages

  • Exceptional Speed: 2-4x performance compared to existing JSON libraries
  • Zero Allocation: Minimizes GC pressure through memory pool usage
  • Direct UTF-8 Processing: Completely eliminates string conversion overhead
  • Unity Support: Native support for Unity-specific types like Vector3, Quaternion
  • Flexible Customization: Advanced control through Resolver/Formatter system
  • AOT Environment Support: Works on Xamarin, Unity IL2CPP through pre-code generation

Disadvantages

  • Maintenance Stopped: Original repository is archived
  • Complexity: Complex internal implementation due to advanced optimizations
  • Feature Limitations: Some advanced features (circular references, etc.) unsupported due to performance priority
  • Learning Curve: Time needed to understand Resolver/Formatter system
  • Debugging Difficulty: Dynamically generated IL code is hard to debug
  • Lack of Standardization: Gap with standard library after System.Text.Json introduction

References

Code Examples

1. Basic Serialization

using Utf8Json;

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime BirthDate { get; set; }
}

class BasicExample
{
    static void Main()
    {
        var person = new Person 
        { 
            Name = "John Doe",
            Age = 30,
            BirthDate = new DateTime(1993, 5, 15)
        };

        // Object → byte[] (UTF-8)
        byte[] utf8Json = JsonSerializer.Serialize(person);
        
        // byte[] → Object
        var deserialized = JsonSerializer.Deserialize<Person>(utf8Json);
        
        // Object → String
        string jsonString = JsonSerializer.ToJsonString(person);
        Console.WriteLine(jsonString);
        
        // Write to Stream
        using (var stream = new FileStream("person.json", FileMode.Create))
        {
            JsonSerializer.Serialize(stream, person);
        }
    }
}

2. Private Member Serialization

public class Employee
{
    private string employeeId = "EMP-001";
    private double salary = 50000;
    
    public string Name { get; set; }
    public string Department { get; set; }
    
    // Accessors for private fields
    public string GetEmployeeId() => employeeId;
    public double GetSalary() => salary;
}

class PrivateMemberExample
{
    static void SerializePrivateMembers()
    {
        var employee = new Employee 
        { 
            Name = "Jane Smith",
            Department = "Engineering"
        };
        
        // Serialize including private members
        var json = JsonSerializer.ToJsonString(employee, StandardResolver.AllowPrivate);
        Console.WriteLine(json);
        // Output: {"employeeId":"EMP-001","salary":50000,"Name":"Jane Smith","Department":"Engineering"}
        
        // Deserialize with private member restoration
        var deserialized = JsonSerializer.Deserialize<Employee>(json, StandardResolver.AllowPrivate);
        Console.WriteLine($"ID: {deserialized.GetEmployeeId()}, Salary: {deserialized.GetSalary()}");
    }
}

3. Custom Resolvers and Formatters

using Utf8Json.Formatters;
using Utf8Json.Resolvers;

// Custom formatter
public class DateOnlyFormatter : IJsonFormatter<DateTime>
{
    public void Serialize(ref JsonWriter writer, DateTime value, IJsonFormatterResolver formatterResolver)
    {
        writer.WriteString(value.ToString("yyyy-MM-dd"));
    }
    
    public DateTime Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        var dateString = reader.ReadString();
        return DateTime.ParseExact(dateString, "yyyy-MM-dd", null);
    }
}

// Custom resolver
public class CustomResolver : IJsonFormatterResolver
{
    public static IJsonFormatterResolver Instance = new CustomResolver();
    
    private CustomResolver() { }
    
    public IJsonFormatter<T> GetFormatter<T>()
    {
        if (typeof(T) == typeof(DateTime))
        {
            return (IJsonFormatter<T>)new DateOnlyFormatter();
        }
        
        return StandardResolver.Default.GetFormatter<T>();
    }
}

// Usage example
class CustomFormatterExample
{
    static void UseCustomFormatter()
    {
        var data = new { Date = DateTime.Now, Name = "Test" };
        
        var json = JsonSerializer.ToJsonString(data, CustomResolver.Instance);
        Console.WriteLine(json); // {"Date":"2023-12-15","Name":"Test"}
    }
}

4. Unity Usage Example

using UnityEngine;
using Utf8Json;
using Utf8Json.Resolvers;

[System.Serializable]
public class GameData
{
    public string PlayerName { get; set; }
    public int Score { get; set; }
    public Vector3 Position { get; set; }
    public Quaternion Rotation { get; set; }
    public Color PlayerColor { get; set; }
}

public class UnityExample : MonoBehaviour
{
    void Start()
    {
        // Set Unity resolver as default
        JsonSerializer.SetDefaultResolver(StandardResolver.Default);
        
        var gameData = new GameData
        {
            PlayerName = "Player1",
            Score = 1000,
            Position = new Vector3(10f, 5f, 3f),
            Rotation = Quaternion.Euler(0, 45, 0),
            PlayerColor = Color.red
        };
        
        // Serialize object containing Unity types
        byte[] jsonBytes = JsonSerializer.Serialize(gameData);
        
        // Save to PlayerPrefs
        string base64 = System.Convert.ToBase64String(jsonBytes);
        PlayerPrefs.SetString("GameData", base64);
        
        // Load
        string loadedBase64 = PlayerPrefs.GetString("GameData");
        byte[] loadedBytes = System.Convert.FromBase64String(loadedBase64);
        var loadedData = JsonSerializer.Deserialize<GameData>(loadedBytes);
        
        Debug.Log($"Loaded: {loadedData.PlayerName}, Score: {loadedData.Score}");
    }
}

5. Dynamic JSON Processing

class DynamicJsonExample
{
    static void ProcessDynamicJson()
    {
        // Dynamic JSON deserialization
        var json = @"{
            ""name"": ""Product A"",
            ""price"": 29.99,
            ""tags"": [""new"", ""featured""],
            ""details"": {
                ""weight"": 500,
                ""dimensions"": {
                    ""width"": 10,
                    ""height"": 20,
                    ""depth"": 5
                }
            }
        }";
        
        dynamic obj = JsonSerializer.Deserialize<dynamic>(json);
        
        // Dynamic access
        string name = obj["name"];                    // "Product A"
        double price = obj["price"];                  // 29.99
        var firstTag = obj["tags"][0];               // "new"
        var weight = obj["details"]["weight"];       // 500
        var width = obj["details"]["dimensions"]["width"]; // 10
        
        Console.WriteLine($"Product: {name}, Price: ${price}");
        
        // Building dynamic objects
        var newObj = new Dictionary<string, object>
        {
            ["id"] = 123,
            ["items"] = new[] { "item1", "item2" },
            ["metadata"] = new Dictionary<string, object>
            {
                ["created"] = DateTime.Now,
                ["version"] = 1.0
            }
        };
        
        string newJson = JsonSerializer.ToJsonString(newObj);
        Console.WriteLine(newJson);
    }
}

6. Advanced Performance Optimization

using Utf8Json;
using System.Buffers;

class PerformanceOptimization
{
    // Zero-copy processing using ArraySegment
    static void ZeroCopyProcessing()
    {
        var largeObject = GenerateLargeObject();
        
        // Use internal buffer pool with ArraySegment
        ArraySegment<byte> segment = JsonSerializer.SerializeUnsafe(largeObject);
        
        try
        {
            // Process segment directly (no copy)
            ProcessJsonBytes(segment.Array, segment.Offset, segment.Count);
        }
        finally
        {
            // Important: Return ArraySegment immediately after use
            JsonSerializer.ReturnArrayToPool(segment.Array);
        }
    }
    
    // Streaming processing
    static async Task StreamingSerializationAsync()
    {
        var items = GetLargeDataset(); // Large amount of data
        
        using (var stream = new FileStream("large.json", FileMode.Create))
        using (var writer = new StreamWriter(stream))
        {
            await writer.WriteAsync("[");
            
            bool first = true;
            foreach (var item in items)
            {
                if (!first) await writer.WriteAsync(",");
                first = false;
                
                // Serialize each item individually
                var json = JsonSerializer.ToJsonString(item);
                await writer.WriteAsync(json);
            }
            
            await writer.WriteAsync("]");
        }
    }
    
    // Using pre-generated formatters (for AOT environments)
    static void UsePreGeneratedFormatter()
    {
        // Pre-generation required for AOT environments
        // Generate using Utf8Json.UniversalCodeGenerator.exe
        
        Utf8Json.Resolvers.GeneratedResolver.Instance.Register();
        
        var obj = new MyGeneratedType { Value = 42 };
        byte[] json = JsonSerializer.Serialize(obj);
    }
}