Ignixa.Abstractions
The Abstractions package provides the foundational interfaces and types used throughout the Ignixa ecosystem.
Installation
dotnet add package Ignixa.Abstractions
Core Interfaces
ISourceNavigator
The primary abstraction for navigating FHIR data:
public interface ISourceNavigator
{
/// <summary>
/// Name of the element (property name in JSON)
/// </summary>
string Name { get; }
/// <summary>
/// Primitive value as string, if any
/// </summary>
string Text { get; }
/// <summary>
/// Location of this node within the tree of data
/// </summary>
string Location { get; }
/// <summary>
/// Resource type for resources, null otherwise
/// </summary>
string ResourceType { get; }
/// <summary>
/// Navigate to child elements
/// </summary>
IEnumerable<ISourceNavigator> Children(string? name = null);
/// <summary>
/// Retrieve attached metadata (e.g., source JsonNode)
/// </summary>
T? Meta<T>() where T : class;
}
IElement
Typed element interface for FHIRPath evaluation and validation:
public interface IElement
{
/// <summary>
/// Element name (e.g., "name", "birthDate", "valueQuantity")
/// </summary>
string Name { get; }
/// <summary>
/// Primitive value (typed: bool, int, decimal, string, DateTimeOffset)
/// </summary>
object? Value { get; }
/// <summary>
/// Runtime type name (e.g., "HumanName", "string", "Patient")
/// </summary>
string InstanceType { get; }
/// <summary>
/// Dotted location for error reporting (e.g., "Patient.name[0].family")
/// </summary>
string Location { get; }
/// <summary>
/// Type metadata from StructureDefinition
/// </summary>
IType? Type { get; }
/// <summary>
/// Child elements (supports choice element semantics)
/// </summary>
IReadOnlyList<IElement> Children(string? name = null);
/// <summary>
/// Retrieve attached metadata (e.g., source JsonNode)
/// </summary>
T? Meta<T>() where T : class;
}
ISourceNavigator is for raw JSON navigation (parsing). IElement is for typed operations (FHIRPath, validation). Convert with sourceNavigator.ToElement(schema).
IType
Marker interface for typed FHIR elements:
public interface IType
{
string TypeName { get; }
}
Navigation Patterns
Child Navigation
// Get all children
foreach (var child in sourceNavigator.Children())
{
Console.WriteLine($"{child.Name}: {child.Text}");
}
// Get specific child by name
var name = sourceNavigator.Children("name").FirstOrDefault();
// Indexer shorthand
var family = sourceNavigator["name"][0]["family"];
Deep Navigation
// Navigate multiple levels
var givenName = sourceNavigator["name"][0]["given"][0].Text;
// Handle missing paths safely
var telecom = sourceNavigator["telecom"]?.FirstOrDefault();
Resource Type Detection
var resourceType = sourceNavigator.ResourceType;
switch (resourceType)
{
case "Patient":
ProcessPatient(sourceNavigator);
break;
case "Observation":
ProcessObservation(sourceNavigator);
break;
}
Extension Methods
ToElement
Convert ISourceNavigator to IElement for typed operations (requires Ignixa.Serialization):
using Ignixa.Serialization.SourceNodes;
// Convert to typed element for FHIRPath and validation
var element = sourceNavigator.ToElement(schemaProvider);
// Now you can use FHIRPath
var names = element.Select("name.given");
Working with Primitive Values
Get values directly from ISourceNavigator.Text or use FHIRPath type conversions:
// Direct text access
var birthDateText = sourceNavigator.Children("birthDate").FirstOrDefault()?.Text;
// Or use FHIRPath for type conversion
var element = sourceNavigator.ToElement(schemaProvider);
var birthDate = element.Select("birthDate.toDateTime()").FirstOrDefault();
Value Objects
ResourceIdentifier
public record ResourceIdentifier(string ResourceType, string Id)
{
public static ResourceIdentifier Parse(string reference);
public string ToReference(); // "Patient/123"
}
CodeableConcept Handling
var coding = sourceNavigator["code"]["coding"][0];
var system = coding["system"].Text;
var code = coding["code"].Text;
var display = coding["display"].Text;
Custom ISourceNavigator Implementation
Implement ISourceNavigator for custom data sources:
public class MySourceNavigator : ISourceNavigator
{
private readonly JsonElement _element;
public string Name { get; }
public string Text => _element.ValueKind == JsonValueKind.String
? _element.GetString()
: string.Empty;
public string Location { get; }
public string ResourceType =>
_element.TryGetProperty("resourceType", out var rt)
? rt.GetString() ?? string.Empty
: string.Empty;
public IEnumerable<ISourceNavigator> Children(string? name = null)
{
if (_element.ValueKind != JsonValueKind.Object)
yield break;
foreach (var prop in _element.EnumerateObject())
{
if (name is null || prop.Name == name)
yield return new MySourceNavigator(prop.Name, prop.Value);
}
}
public T? Meta<T>() where T : class
{
// Return attached metadata if any
return null;
}
}
Best Practices
1. Prefer FHIRPath and ISourceNavigator over Direct JSON Access
FHIRPath and ISourceNavigator understand FHIR's business logic:
- Choice types:
value[x]elements correctly resolve tovalueQuantity,valueString, etc. - Extensions: Navigate through shadow properties and extensions
- Polymorphism: Handles contained resources and references properly
// ✅ Good: FHIRPath with full FHIR semantics
var display = element.Select("code.coding.first().display").FirstOrDefault();
// ✅ Also Good: ISourceNavigator understands FHIR structure
var display = sourceNavigator["code"]["coding"][0]["display"].Text;
// ❌ Avoid: Direct MutableNode access bypasses FHIR semantics
var node = resourceJsonNode.MutableNode;
var display = node["code"]?["coding"]?[0]?["display"]?.GetValue<string>();
Accessing JsonSourceNode.MutableNode or raw System.Text.Json.Nodes.JsonNode directly bypasses FHIR-specific handling. Use ISourceNavigator or FHIRPath for correct FHIR semantics.
2. Use Type Information
var element = sourceNavigator.ToElement(schemaProvider);
// Now you have type information
var instanceType = element.InstanceType; // e.g., "Patient", "HumanName"
var typeInfo = element.Type; // Type metadata from StructureDefinition
3. Handle Missing Data Gracefully
// Use null-conditional operators
var birthDate = sourceNavigator["birthDate"]?.Text;
// Or provide defaults
var active = sourceNavigator["active"]?.Text ?? "true";