A powerful .NET library for creating, reading, and updating JSON objects using a path-based API. Built with Dictionary<string, object?>
as the underlying data structure and System.Text.Json
for modern, high-performance serialization.
- π Path-based API: Intuitive path syntax for navigating and manipulating JSON structures
- π Cross-platform: Full compatibility with .NET Standard 2.0, .NET 8, and .NET 9
- β‘ High Performance: Uses
System.Text.Json
for optimal serialization performance - π Smart Type Conversion: Intelligent conversion between strings, numbers, and complex types
- π Full CRUD Operations: Complete Create, Read, Update, Delete capabilities with robust error handling
- π― Sparse Array Support: Efficient handling of arrays with automatic null-filling for gaps
- π Unicode Ready: Full support for international characters, emojis, and special symbols
- π Path Validation: Comprehensive validation with meaningful error messages
- π Path Discovery: List all paths and values in your JSON structure
- π Well Documented: Complete XML documentation and extensive unit test coverage
Install via NuGet Package Manager:
dotnet add package StructuredJson
Or via Package Manager Console:
Install-Package StructuredJson
Or via PackageReference in your .csproj
:
<PackageReference Include="StructuredJson" Version="1.0.0" />
using StructuredJson;
// Create a new instance
var sj = new StructuredJson();
// Set values using intuitive path syntax
sj.Set("user:name", "John Doe");
sj.Set("user:age", 30);
sj.Set("user:isActive", true);
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("user:addresses[0]:country", "Turkey");
sj.Set("user:addresses[1]:city", "Istanbul");
// Get values with automatic type conversion
var name = sj.Get("user:name"); // "John Doe"
var age = sj.Get<int>("user:age"); // 30
var isActive = sj.Get<bool>("user:isActive"); // true
var city = sj.Get("user:addresses[0]:city"); // "Ankara"
// Convert to beautifully formatted JSON
var json = sj.ToJson();
Console.WriteLine(json);
// List all paths and values
var paths = sj.ListPaths();
foreach (var kvp in paths)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
StructuredJson uses an intuitive and powerful path syntax with comprehensive validation:
Use :
to navigate object properties:
sj.Set("user:name", "John"); // user.name
sj.Set("config:database:host", "localhost"); // config.database.host
sj.Set("app:settings:theme", "dark"); // app.settings.theme
Use [index]
to access array elements:
sj.Set("users[0]", "John"); // users[0]
sj.Set("items[2]:name", "Product"); // items[2].name
sj.Set("data[5]:values[3]", 42); // data[5].values[3]
Combine objects and arrays seamlessly:
sj.Set("user:addresses[0]:coordinates:lat", 39.9334);
sj.Set("products[1]:reviews[0]:rating", 5);
sj.Set("config:servers[2]:endpoints[0]:url", "https://api.example.com");
The library provides comprehensive path validation:
- β
"user:name"
- Valid object property - β
"items[0]"
- Valid array element - β
"data:list[5]:value"
- Valid nested path - β
"items[]"
- Invalid: empty array index - β
"items[-1]"
- Invalid: negative index - β
"items[abc]"
- Invalid: non-numeric index
// Create empty structure
var sj = new StructuredJson();
// Create from JSON string (with comprehensive validation)
var sj = new StructuredJson(jsonString);
Sets a value at the specified path, creating nested structures automatically:
sj.Set("user:name", "John");
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("items[5]", "value"); // Creates sparse array with nulls at 0-4
// Supports all data types
sj.Set("numbers:int", 42);
sj.Set("numbers:decimal", 123.45m);
sj.Set("flags:isActive", true);
sj.Set("data:nullValue", null);
sj.Set("text:unicode", "π TΓΌrkΓ§e δ½ ε₯½");
Throws: ArgumentException
for invalid paths
Retrieves values with intelligent type conversion:
// Basic retrieval
var name = sj.Get("user:name"); // Returns object
var age = sj.Get<int>("user:age"); // Returns strongly-typed int
// Smart type conversions
sj.Set("stringNumber", "42");
var number = sj.Get<int>("stringNumber"); // Returns 42 (int)
sj.Set("numberString", 123);
var text = sj.Get<string>("numberString"); // Returns "123" (string)
// Complex type handling
var user = sj.Get<UserModel>("user"); // Deserializes to custom type
Type Conversion Features:
- String β Number conversions (int, long, double, decimal, float)
- JsonElement handling for complex deserialization
- Automatic type detection and conversion
- Returns
default(T)
for failed conversions
Safely checks if a path exists:
bool exists = sj.HasPath("user:name"); // true/false
bool invalid = sj.HasPath("invalid[]path"); // false (doesn't throw)
Removes values with intelligent array handling:
bool removed = sj.Remove("user:age"); // Removes property
bool arrayRemoved = sj.Remove("items[1]"); // Removes and shifts array elements
Discovers all paths and values in your structure:
var paths = sj.ListPaths();
// Returns: Dictionary<string, object?>
// Example output:
// "user:name" -> "John"
// "user:addresses[0]:city" -> "Ankara"
// "user:addresses[1]:city" -> "Istanbul"
Converts to JSON with flexible formatting:
var prettyJson = sj.ToJson(); // Pretty-printed (default)
var compactJson = sj.ToJson(new JsonSerializerOptions {
WriteIndented = false
});
Removes all data from the structure:
sj.Clear(); // Structure becomes empty: {}
Automatically handles arrays with gaps:
var sj = new StructuredJson();
sj.Set("items[0]", "first");
sj.Set("items[5]", "sixth"); // Automatically fills [1-4] with null
var json = sj.ToJson();
// Result: {"items": ["first", null, null, null, null, "sixth"]}
Full support for international characters:
sj.Set("turkish", "TΓΌrkΓ§e karakterler: ΔΓΌΕΔ±ΓΆΓ§");
sj.Set("emoji", "π π π π»");
sj.Set("chinese", "δ½ ε₯½δΈη");
sj.Set("arabic", "Ω
Ψ±ΨΨ¨Ψ§ Ψ¨Ψ§ΩΨΉΨ§ΩΩ
");
// All perfectly preserved in JSON output
Works seamlessly with custom objects:
public class Address
{
public string City { get; set; }
public string Country { get; set; }
public double[] Coordinates { get; set; }
}
var address = new Address
{
City = "Ankara",
Country = "Turkey",
Coordinates = new[] { 39.9334, 32.8597 }
};
sj.Set("user:address", address);
var retrievedAddress = sj.Get<Address>("user:address");
Handles large-scale operations efficiently:
// Efficient for large datasets
for (int i = 0; i < 10000; i++)
{
sj.Set($"data:items[{i}]:id", i);
sj.Set($"data:items[{i}]:value", $"Item {i}");
}
// Fast path-based lookups with O(1) dictionary access
var item5000 = sj.Get("data:items[5000]:value");
var profile = new StructuredJson();
// Basic info
profile.Set("user:id", 12345);
profile.Set("user:name", "John Doe");
profile.Set("user:email", "[email protected]");
profile.Set("user:isVerified", true);
// Multiple addresses
profile.Set("user:addresses[0]:type", "home");
profile.Set("user:addresses[0]:street", "123 Main St");
profile.Set("user:addresses[0]:city", "Ankara");
profile.Set("user:addresses[0]:country", "Turkey");
profile.Set("user:addresses[1]:type", "work");
profile.Set("user:addresses[1]:street", "456 Business Ave");
profile.Set("user:addresses[1]:city", "Istanbul");
profile.Set("user:addresses[1]:country", "Turkey");
// Preferences
profile.Set("user:preferences:theme", "dark");
profile.Set("user:preferences:language", "tr-TR");
profile.Set("user:preferences:notifications:email", true);
profile.Set("user:preferences:notifications:sms", false);
// Access data
var homeAddress = profile.Get("user:addresses[0]:city"); // "Ankara"
var emailNotifications = profile.Get<bool>("user:preferences:notifications:email"); // true
var config = new StructuredJson();
// Database settings
config.Set("database:host", "localhost");
config.Set("database:port", 5432);
config.Set("database:name", "myapp");
config.Set("database:ssl", true);
// API endpoints
config.Set("api:endpoints[0]:name", "users");
config.Set("api:endpoints[0]:url", "/api/v1/users");
config.Set("api:endpoints[0]:methods[0]", "GET");
config.Set("api:endpoints[0]:methods[1]", "POST");
config.Set("api:endpoints[1]:name", "products");
config.Set("api:endpoints[1]:url", "/api/v1/products");
config.Set("api:endpoints[1]:methods[0]", "GET");
// Feature flags
config.Set("features:newDashboard", true);
config.Set("features:betaFeatures", false);
config.Set("features:maintenanceMode", false);
// Export to configuration file
File.WriteAllText("appsettings.json", config.ToJson());
var catalog = new StructuredJson();
// Product 1
catalog.Set("products[0]:id", "P001");
catalog.Set("products[0]:name", "Laptop");
catalog.Set("products[0]:price", 999.99m);
catalog.Set("products[0]:currency", "USD");
catalog.Set("products[0]:inStock", true);
catalog.Set("products[0]:specifications:cpu", "Intel i7");
catalog.Set("products[0]:specifications:ram", "16GB");
catalog.Set("products[0]:specifications:storage", "512GB SSD");
catalog.Set("products[0]:reviews[0]:rating", 5);
catalog.Set("products[0]:reviews[0]:comment", "Excellent laptop!");
catalog.Set("products[0]:reviews[1]:rating", 4);
catalog.Set("products[0]:reviews[1]:comment", "Good value for money");
// Product 2
catalog.Set("products[1]:id", "P002");
catalog.Set("products[1]:name", "Smartphone");
catalog.Set("products[1]:price", 599.99m);
catalog.Set("products[1]:inStock", false);
// Query products
var laptopPrice = catalog.Get<decimal>("products[0]:price"); // 999.99
var laptopRating = catalog.Get<int>("products[0]:reviews[0]:rating"); // 5
var phoneInStock = catalog.Get<bool>("products[1]:inStock"); // false
StructuredJson provides comprehensive error handling:
try
{
var sj = new StructuredJson();
// These will throw ArgumentException with descriptive messages:
sj.Set("", "value"); // Empty path
sj.Set("items[]", "value"); // Empty array index
sj.Set("items[abc]", "value"); // Invalid array index
sj.Set("items[-1]", "value"); // Negative array index
}
catch (ArgumentException ex)
{
Console.WriteLine($"Path error: {ex.Message}");
}
try
{
// Invalid JSON in constructor
var sj = new StructuredJson("{invalid json}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"JSON parsing error: {ex.Message}");
}
Easily explore your JSON structure:
var sj = new StructuredJson();
sj.Set("user:name", "John");
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("user:addresses[1]:city", "Istanbul");
sj.Set("settings:theme", "dark");
// List all paths
var paths = sj.ListPaths();
foreach (var kvp in paths)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// Output:
// user:name: John
// user:addresses[0]:city: Ankara
// user:addresses[1]:city: Istanbul
// settings:theme: dark
// Check if specific paths exist
bool hasUserName = sj.HasPath("user:name"); // true
bool hasUserAge = sj.HasPath("user:age"); // false
bool hasFirstAddress = sj.HasPath("user:addresses[0]"); // true
- Underlying Structure:
Dictionary<string, object?>
for O(1) key lookups - Serialization:
System.Text.Json
for modern, high-performance JSON handling - Memory Efficient: Sparse arrays don't allocate unnecessary memory
- Path Parsing: Optimized regex-based path parsing with caching
- Type Conversion: Lazy evaluation with intelligent fallback strategies
- Cross-platform: Full compatibility across Windows, macOS, and Linux
- .NET Standard 2.0: Maximum compatibility with all .NET implementations
- .NET 8.0: Latest performance optimizations and nullable reference types
- .NET 9.0: Cutting-edge features and improvements
- Cross-platform: Windows, macOS, Linux support
- Legacy Support: Works with .NET Framework 4.6.1+
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
See CHANGELOG.md for a detailed list of changes and version history.
- Bug Reports: GitHub Issues
- Feature Requests: GitHub Discussions
- Documentation: This README and XML documentation in the code
Made with β€οΈ for the .NET