StructuredJson

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 serialization.
Features
- Path-based API: Use intuitive path syntax to navigate and manipulate JSON structures
- .NET Standard 2.0: Compatible with all modern .NET platforms
- System.Text.Json: Uses the modern, high-performance JSON serializer
- Type Safety: Generic methods with intelligent type conversion for strongly-typed value retrieval
- Comprehensive: Full CRUD operations with path validation and robust error handling
- Smart Type Conversion: Automatic conversion between strings and numbers, plus JsonElement handling
- Array Support: Full array manipulation with automatic null-filling for sparse arrays
- Well Documented: Complete XML documentation and extensive unit tests
Installation
Install via NuGet Package Manager:
dotnet add package StructuredJson
Or via Package Manager Console:
Install-Package StructuredJson
Quick Start
using StructuredJson;
var sj = new StructuredJson();
sj.Set("user:name", "John Doe");
sj.Set("user:age", 30);
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("user:addresses[0]:country", "Turkey");
sj.Set("user:addresses[1]:city", "Istanbul");
var name = sj.Get("user:name");
var age = sj.Get<int>("user:age");
var city = sj.Get("user:addresses[0]:city");
var json = sj.ToJson();
Console.WriteLine(json);
var paths = sj.ListPaths();
foreach (var kvp in paths)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
Path Syntax
The library uses a simple and intuitive path syntax with comprehensive validation:
-
Object Properties: Use :
to separate object properties
"user:name"
→ user.name
"config:database:host"
→ config.database.host
-
Array Elements: Use [index]
to access array elements
"users[0]"
→ users[0]
"items[2]:name"
→ items[2].name
-
Complex Paths: Combine objects and arrays
"user:addresses[0]:city"
→ user.addresses[0].city
"data:items[1]:properties[0]:value"
→ data.items[1].properties[0].value
Path Validation
The library performs comprehensive path validation and throws ArgumentException
for:
- Empty or null paths
- Empty array indices (
items[]
)
- Invalid array indices (non-numeric:
items[abc]
)
- Negative array indices (
items[-1]
)
- Multiple array indices in one segment (
items[0][1]
)
- Invalid path segments
API Reference
Constructors
var sj = new StructuredJson();
var sj = new StructuredJson(jsonString);
Core Methods
Set(string path, object? value)
Sets a value at the specified path. Creates nested structures and arrays automatically.
sj.Set("user:name", "John");
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("items[5]", "value");
Throws: ArgumentException
for invalid paths
Get(string path)
Gets a value from the specified path. Returns null
if path doesn't exist.
var value = sj.Get("user:name");
Throws: ArgumentException
for invalid paths
Get(string path)
Gets a strongly-typed value with intelligent type conversion.
var age = sj.Get<int>("user:age");
var isActive = sj.Get<bool>("user:isActive");
var name = sj.Get<string>("user:name");
sj.Set("stringNumber", "42");
var number = sj.Get<int>("stringNumber");
sj.Set("numberAsString", 123);
var text = sj.Get<string>("numberAsString");
Type Conversion Features:
- String ↔ Number conversions (int, long, double, decimal, float)
- JsonElement handling for complex deserialization
- Fallback to JsonSerializer for complex types
- Returns
default(T)
for failed conversions
ToJson(JsonSerializerOptions? options = null)
Converts the structure to a JSON string with optional formatting.
var json = sj.ToJson();
var compactJson = sj.ToJson(new JsonSerializerOptions { WriteIndented = false });
ListPaths()
Returns all paths and their values as a dictionary. Handles sparse arrays intelligently.
var paths = sj.ListPaths();
HasPath(string path)
Checks if a path exists in the structure with safe error handling.
bool exists = sj.HasPath("user:name");
Remove(string path)
Removes a value at the specified path. For arrays, removes the element and shifts indices.
bool removed = sj.Remove("user:age");
bool arrayRemoved = sj.Remove("items[1]");
Clear()
Removes all data from the structure.
sj.Clear();
Examples
Working with Complex Structures
var sj = new StructuredJson();
sj.Set("user:name", "John Doe");
sj.Set("user:age", 30);
sj.Set("user:isActive", true);
sj.Set("user:addresses[0]:type", "home");
sj.Set("user:addresses[0]:street", "123 Main St");
sj.Set("user:addresses[0]:city", "Ankara");
sj.Set("user:addresses[0]:country", "Turkey");
sj.Set("user:addresses[1]:type", "work");
sj.Set("user:addresses[1]:street", "456 Business Ave");
sj.Set("user:addresses[1]:city", "Istanbul");
sj.Set("user:addresses[1]:country", "Turkey");
sj.Set("user:hobbies[0]", "reading");
sj.Set("user:hobbies[1]", "swimming");
sj.Set("user:hobbies[2]", "coding");
var json = sj.ToJson();
Loading from Existing JSON
var existingJson = """
{
"config": {
"database": {
"host": "localhost",
"port": 5432,
"name": "mydb"
},
"features": ["auth", "logging", "caching"]
}
}
""";
var sj = new StructuredJson(existingJson);
var host = sj.Get("config:database:host");
var port = sj.Get<int>("config:database:port");
var firstFeature = sj.Get("config:features[0]");
sj.Set("config:database:host", "production-server");
sj.Set("config:features[3]", "monitoring");
var updatedJson = sj.ToJson();
Advanced Array Manipulation
var sj = new StructuredJson();
sj.Set("items[5]", "value at index 5");
var nullValue = sj.Get("items[0]");
var actualValue = sj.Get("items[5]");
sj.Set("items[0]", "first item");
sj.Set("items[1]", "second item");
sj.Remove("items[1]");
var paths = sj.ListPaths();
Type Conversion Examples
var sj = new StructuredJson();
sj.Set("stringInt", "42");
sj.Set("stringDouble", "3.14");
sj.Set("stringDecimal", "99.99");
var intValue = sj.Get<int>("stringInt");
var doubleValue = sj.Get<double>("stringDouble");
var decimalValue = sj.Get<decimal>("stringDecimal");
sj.Set("numberValue", 123);
var stringValue = sj.Get<string>("numberValue");
sj.Set("complexObject", new { Name = "Test", Value = 42 });
var complexResult = sj.Get<Dictionary<string, object>>("complexObject");
Error Handling
The library provides comprehensive error handling with specific exception types:
try
{
var sj = new StructuredJson();
sj.Set("", "value");
sj.Set(null, "value");
sj.Set("items[]", "value");
sj.Set("items[abc]", "value");
sj.Set("items[-1]", "value");
sj.Set("items[0][1]", "value");
var invalid = new StructuredJson("{invalid json}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Invalid argument: {ex.Message}");
if (ex.InnerException is JsonException jsonEx)
{
Console.WriteLine($"JSON Error: {jsonEx.Message}");
}
}
bool exists = sj.HasPath("invalid[path");
bool removed = sj.Remove("invalid[path");
Performance Considerations
- Dictionary-based: Uses
Dictionary<string, object?>
internally for O(1) key lookups
- List semantics: Array operations maintain proper list behavior with appropriate performance
- System.Text.Json: Leverages high-performance JSON serialization
- Regex optimization: Path parsing uses compiled regex patterns
- Lazy evaluation: Type conversions are performed only when needed
- Memory efficient: Sparse arrays don't allocate unnecessary null elements in ListPaths()
Thread Safety
StructuredJson
is not thread-safe. If you need to access the same instance from multiple threads, implement appropriate synchronization mechanisms such as:
private readonly object _lock = new object();
private readonly StructuredJson _sj = new StructuredJson();
public void SafeSet(string path, object value)
{
lock (_lock)
{
_sj.Set(path, value);
}
}
Best Practices
- Path Validation: Always handle
ArgumentException
when working with dynamic paths
- Type Safety: Use generic
Get<T>()
methods for better type safety
- Error Handling: Use
HasPath()
to check existence before Get()
operations
- Performance: Cache
ListPaths()
results if called frequently
- Memory: Call
Clear()
when reusing instances with large datasets
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
Version 1.0.0
- Initial release
- Path-based JSON manipulation API
- Support for .NET Standard 2.0
- Intelligent type conversion system
- Comprehensive path validation
- Sparse array support
- Robust error handling
- Full XML documentation
- Comprehensive unit test coverage