
XferLang Documentation
Your complete guide to the XferLang data-interchange format and the .NET library.
Introduction to XferLang
XferLang is a modern data-interchange format designed to support data serialization, data transmission, and offline use cases such as configuration management. Unlike JSON or XML, XferLang is designed with explicit typing, human readability, and safe data embedding at its core.
XferLang is an open-source project hosted on GitHub at github.com/paulmooreparks/Xfer. The project includes the complete language specification, a production-ready .NET library, comprehensive documentation, and development tools.
Here's a quick comparison to show XferLang in action:
JSON Example:
{
"name": "John Doe",
"age": 30,
"isActive": true,
"addresses": [
{
"type": "home",
"street": "123 Main St",
"city": "Anytown"
}
],
"metadata": null
}
XferLang Equivalent:
{
name "John Doe"
age #30
isActive ~true
addresses [
{
type "home"
street "123 Main St"
city "Anytown"
}
]
metadata ?
}
Notice how XferLang eliminates commas, uses explicit type prefixes (#30 for integers, ~true for booleans, ? for null), and maintains excellent readability.
Because whitespace is flexible, the same document can be made extremely compact:
{name"Alice"age 30 isMember~true scores[*85*90*78.5]profile{email"alice@example.com"joinedDate@2023-05-05T20:00:00@}}
Design Philosophy
XferLang is built around four core principles:
1. Clarity and Readability
The syntax is designed to be human-readable without requiring separators like commas. Whitespace naturally delimits elements, making documents easier to scan and understand.
2. Explicit Typing
All values are explicitly typed using prefixes. This eliminates the type ambiguity that can occur in formats like JSON, leading to more predictable parsing and less defensive coding.
3. Elimination of Escaping
XferLang doesn't require escaping special characters in values. Instead, values use unique delimiters that can be repeated as needed to avoid conflicts.
4. Safety in Embedding
The delimiter-repetition strategy allows safe embedding of complex data without risk of delimiter collision, making it perfect for configuration files and data transmission.
Basic Syntax
XferLang documents consist of elements separated by whitespace. Each element can be a simple value or a complex structure. XferLang supports three different syntax variations for writing elements.
Comments
Comments start with </
and end with />
:
</ This is a comment />
name "John Doe" </ This is also a comment />
Whitespace
XferLang uses whitespace (spaces, tabs, newlines) to separate elements. Multiple whitespace characters are treated as a single separator:
</ These are equivalent />
name "John" age #30
name "John" age #30
name "John"
age #30
Metadata & Processing Instructions (PIs)
XferLang supports document-level metadata and extensible processing instructions (PIs) for configuration, runtime directives, and schema association. These features provide powerful control over parsing, validation, and dynamic value resolution.
General Syntax
Metadata and PIs are enclosed in angle brackets with exclamation marks, e.g., <! ... !>
, and typically appear at the top of a document.
<! name { key1 value1 key2 value2 ... } !>
Document Metadata
Use the xfer
PI to specify XferLang version and document metadata:
<! xfer "0.7.2" author "John Doe" created @2023-12-25T10:00:00Z@ !>
Schema Association (TO BE IMPLEMENTED)
Use the schema
PI to associate a document with a schema definition:
<! schema "foo" !>
Dynamic Value Resolution
The dynamicSource
PI allows you to override how dynamic values (e.g., environment variables, secrets, test data) are resolved at runtime. Example:
<! dynamicSource { key "env:NAME" } !>
- If a
dynamicSource
PI is present, you can specify per-key overrides for dynamic values. - Supported override types include environment variables (
env:NAME
), hard-coded values, and custom logic (e.g.,reverse:...
). - If a key is not present in the PI, the resolver falls back to its default logic.
Extensibility
PIs are designed to be extensible. You can define custom PIs for your application and implement corresponding logic in your parser or resolver. For dynamic value resolution, subclass DefaultDynamicSourceResolver
or implement IDynamicSourceResolver
in .NET.
Why Use Metadata & PIs?
- Specify document version, author, and creation date.
- Associate with schemas for validation.
- Enable flexible, testable, and environment-specific configuration.
- Support secrets, test data, and runtime overrides without code changes.
- Make XferLang highly extensible and adaptable for a wide range of use cases.
Syntax Variations
XferLang supports three different syntax styles for writing elements, providing flexibility for different use cases:
Implicit Syntax
For the most common types, like integers and simple keywords, no special characters are needed at all. The parser infers the type from the content.
123 </ An integer />
name "Alice" </ A key/value pair of a keyword 'name' and a string value />
Compact Syntax
For most other types, a single specifier character (or a pair for collections) denotes the type. This is the most common syntax.
~true </ A boolean />
*123.45 </ A decimal />
"Hello, World!" </ A string />
[ 1 2 3 ] </ An array of integers />
Explicit Syntax
When an element's content might be ambiguous (e.g., a string containing a quote), you can wrap the compact form in angle brackets (<
and >
). This is the most verbose but also the most powerful form, as it allows for delimiter repetition to avoid any collision.
<"Alice said, "Boo!"">
<// A comment containing </another comment/> //>
Data Types
XferLang supports a rich set of data types with explicit type indicators:
Strings
Strings are enclosed in double quotes. No escaping is needed - if your string contains quotes, use multiple quote characters. You can use compact syntax for simple strings or explicit syntax for complex content:
simple "Hello, World!"
withQuotes ""She said "Hello" to me""
multipleQuotes """This string contains "" quotes"""
For complex strings that might contain multiple quote sequences, you can use the explicit string syntax:
complexString <"This can contain "quotes" and ""multiple"" quotes">
Numbers
Numbers use explicit type prefixes in compact syntax or can be written without prefixes using implicit syntax when unambiguous:
integer #42
negative #-123
hexInteger #$FF
binaryInteger #%10101010
long &123456789
hexLong &$DEADBEEF
binaryLong &%1001010100000010111110010000000000
decimal *3.14159
double ^2.71828
You can also write integers without the prefix when the context is clear (implicit syntax):
simpleNumber 42
Hexadecimal and Binary Formatting: Integer, long, and character elements support alternative numeric representations using #$
for hexadecimal and #%
for binary (Integer), &$
/&%
for Long values, and \$
/\%
for Character values. These formats are ideal for memory addresses, color values, bitmasks, Unicode, and low-level programming contexts.
Booleans
Booleans use the ~
prefix in compact syntax:
isActive ~true
isDeleted ~false
Null Values
Null values use the ?
(question mark) specifier in compact syntax:
optionalValue ?
explicitNull <??>
Characters
Single characters use the backslash (\
) prefix and can be specified in several explicit ways: Unicode code points (\$2764
for '❤'), decimal (\65
for 'A'), hexadecimal (\$41
for 'A'), binary (\%1000001
for 'A'), and named control characters (nul
, cr
, lf
, nl
, tab
, vtab
, bksp
, ff
, bel
, quote
, apos
, backslash
, lt
, gt
).
letter \A
space \32
unicode \$1F600 </ 😀 emoji />
tab \tab
newline \lf
verticalTab \vtab
backspace \bksp
formFeed \ff
bell \bel
doubleQuote \quote
singleQuote \apos
backslash \backslash
lessThan \lt
greaterThan \gt
hexadecimal \$41 </ 'A' />
binary \%1000001 </ 'A' />
Dates and Times
Dates and times use the @
prefix with ISO 8601 format and closing @
:
birthDate @2023-12-25@
meetingTime @2023-12-25T14:30:00@
timestamp @2023-12-25T14:30:00.123Z@
Interpolated Elements
XferLang supports interpolated elements that can contain expressions and other elements. These always require explicit syntax for complex content:
greeting 'Hello, <"World">!</ This is a comment inside an eval />!'
emoji 'I <\$2764\> Xfer <\$1F600\>' </ I ❤︎ Xfer 😀 />
Xfer Element Types (Concept & Code)
XferLang supports a rich set of data types designed for clarity, explicitness, and flexibility. Each type is chosen to represent a distinct kind of value, making data both human-readable and machine-precise. The main categories are:
- Numbers: Integers, longs, decimals, and doubles for numeric values.
- Booleans: True/false values for logic and state.
- Strings & Characters: Textual data, including single characters and multi-line strings.
- Dates & Times: Date, time, and time span for temporal data.
- Nulls: Explicit representation of missing or undefined values.
- Objects: Named key-value pairs for structured records.
- Arrays & Tuples: Ordered collections, either typed (arrays) or mixed (tuples).
- Special Types: GUIDs, URIs, enums, dynamic elements, and interpolated values for advanced scenarios.
IntegerElement
Represents a 32-bit integer value.
// Concept: Used for whole numbers.
var element = new IntegerElement(42);
int value = element.Value; // 42
LongElement
Represents a 64-bit integer value.
var element = new LongElement(1234567890123L);
long value = element.Value;
BooleanElement
Represents a boolean value (true/false).
var element = new BooleanElement(true);
bool value = element.Value;
DoubleElement
Represents a double-precision floating-point value.
var element = new DoubleElement(3.14159);
double value = element.Value;
DecimalElement
Represents a high-precision decimal value.
var element = new DecimalElement(123.45m);
decimal value = element.Value;
DateTimeElement
Represents a date and time value.
var element = new DateTimeElement(DateTime.Now);
DateTime value = element.Value;
DateElement
Represents a date-only value.
var element = new DateElement(DateOnly.FromDateTime(DateTime.Now));
DateOnly value = element.Value;
TimeElement
Represents a time-only value.
var element = new TimeElement(TimeOnly.FromDateTime(DateTime.Now));
TimeOnly value = element.Value;
TimeSpanElement
Represents a time interval.
var element = new TimeSpanElement(TimeSpan.FromMinutes(90));
TimeSpan value = element.Value;
StringElement
Represents a string value.
var element = new StringElement("Hello, world!");
string value = element.Value;
CharacterElement
Represents a single character. Supported formats: literal (\A
), Unicode code point (\$2764
for '❤'), decimal (\65
for 'A'), hexadecimal (\$41
for 'A'), binary (\%1000001
for 'A'), and named control (cr
, lf
, tab
, vtab
, bksp
, ff
, bel
, quote
, apos
, backslash
, lt
, gt
).
Hexadecimal and binary formatting for character elements works the same as for integer and long elements. Example: var element = new CharacterElement('A');
, var element = new CharacterElement('\n');
, var element = new CharacterElement((char)0x2764);
var element = new CharacterElement('A');
char value = element.Value;
Guid/StringElement
Represents a GUID value as a string.
var element = new StringElement(Guid.NewGuid().ToString());
Guid value = Guid.Parse(element.Value);
Enum/StringElement
Represents an enum value as a string.
var element = new StringElement(MyEnum.Value.ToString());
MyEnum value = Enum.Parse(element.Value);
Uri/StringElement
Represents a URI value as a string.
var element = new StringElement("https://example.com");
Uri value = new Uri(element.Value);
ArrayElement
Represents a typed array of elements.
var element = new TypedArrayElement();
element.Add(new IntegerElement(1));
element.Add(new IntegerElement(2));
// ...
TupleElement
Represents an ordered collection of elements.
var element = new TupleElement();
element.Add(new IntegerElement(1));
element.Add(new StringElement("two"));
// ...
ObjectElement
Represents a set of named key-value pairs.
var element = new ObjectElement();
element.AddOrUpdate(new KeyValuePairElement(new IdentifierElement("Name"), new StringElement("Alice")));
// ...
KeyValuePairElement
Represents a single key-value pair.
var kvp = new KeyValuePairElement(new IdentifierElement("Age"), new IntegerElement(30));
NullElement
Represents a null value.
var element = new NullElement();
object? value = element.Value; // null
InterpolatedElement
Represents an interpolated string value.
var element = new InterpolatedElement("${user.Name}");
string value = element.Value;
DynamicElement
Represents a dynamic value that is replaced during parsing or deserialization.
var element = new DynamicElement("{value}");
string value = element.Value;
Complex Structures
Objects
Objects are enclosed in curly braces and contain key-value pairs:
{
name "John Doe"
age #30
address {
street "123 Main St"
city "Anytown"
zipCode "12345"
}
}
Arrays
Arrays are enclosed in square brackets and contain elements of the same type:
numbers [#1 #2 #3 #4 #5]
names ["Alice" "Bob" "Charlie"]
decimals [*85.0 *90.0 *78.5]
Tuples
Tuples are enclosed in parentheses and represent collections of elements of any type (similar to JSON arrays):
coordinates (*42.3601 *-71.0589)
nameAge ("John Doe" #30)
mixed (#1 "hello" ~true ?)
Nested Structures
Objects, arrays, and tuples can be freely nested:
users [
{
name "Alice"
age #25
roles ["admin" "user"]
}
{
name "Bob"
age #30
roles ["user"]
}
]
</ Tuple with mixed types />
userInfo ("Alice" #25 ["admin" "user"] ~true)
Tuples vs Arrays
Remember the key difference:
- Arrays
[...]
: Elements of the same type - Tuples
(...)
: Elements of any type (like JSON arrays)
Complete Examples
Configuration File
<! xfer "0.7.2"
name "Application Configuration"
version "1.2.3"
!>
database {
host "localhost"
port #5432
name "myapp"
credentials {
username "dbuser"
password '<DBPASSWORD>'
}
connectionPool {
minSize #5
maxSize #20
timeout *30.0
}
}
logging {
level "INFO"
outputs ["console" "file"]
file {
path "/var/log/myapp.log"
maxSize "10MB"
rotate ~true
}
}
features {
enableCache ~true
enableMetrics ~true
experimentalFeatures ~false
}
User Profile Data
{
id #12345
profile {
firstName "Jane"
lastName "Smith"
email "jane.smith@example.com"
birthDate @1990-05-15@
verified ~true
}
preferences {
theme "dark"
language "en-US"
notifications {
email ~true
push ~false
sms ~true
}
}
addresses [
{
type "home"
street "456 Oak Avenue"
city "Springfield"
state "IL"
zipCode "62701"
country "US"
isPrimary ~true
}
{
type "work"
street "789 Business Blvd"
city "Springfield"
state "IL"
zipCode "62702"
country "US"
isPrimary ~false
}
]
lastLogin @2023-12-25T09:30:00Z@
loginCount #247
}
Grammar Reference
For those who want to implement parsers or need the complete technical specification, here's the formal grammar for XferLang:
<document> ::= <opt_whitespace> <metadata_element>? <opt_whitespace> <body_element>* <opt_whitespace>
<metadata_element> ::= <metadata_element_explicit> | <metadata_element_compact>
<metadata_element_explicit> ::= <element_open> <metadata_specifier> <key_value_pair>+ <metadata_specifier> <element_close>
<metadata_element_compact> ::= <metadata_specifier> <key_value_pair>* <metadata_specifier>
<body_element> ::= <opt_whitespace> (
<key_value_pair>
| <string_element>
| <character_element>
| <integer_element>
| <long_element>
| <double_element>
| <decimal_element>
| <boolean_element>
| <datetime_element>
| <date_element>
| <time_element>
| <timespan_element>
| <null_element>
| <object_element>
| <array_element>
| <tuple_element>
| <comment_element>
| <dynamic_element>
| <interpolated_element>
) <opt_whitespace>
<string_element> ::= <string_element_explicit> | <string_element_compact>
<string_element_explicit> ::= <element_open> <metadata_specifier> <text> <metadata_specifier> <element_close>
<string_element_compact> ::= <metadata_specifier> <text> <metadata_specifier>
<key_value_pair> ::= <keyword_element> <opt_whitespace> <body_element>
<keyword_element> ::= <keyword_element_explicit> | <keyword_element_compact> | <keyword_element_implicit>
<keyword_element_explicit> ::= <element_open> <keyword_specifier> <text> <keyword_specifier> <element_close>
<keyword_element_compact> ::= <keyword_specifier> <text> <keyword_specifier>
<keyword_element_implicit> ::= <identifier>
<character_element> ::= <character_element_explicit> | <character_element_compact>
<character_element_explicit> ::= <element_open> <character_specifier> <opt_whitespace> <character_value> <opt_whitespace> <character_specifier> <element_close>
<character_element_compact> ::= <character_specifier> <character_value>
<integer_element> ::= <integer_element_explicit> | <integer_element_compact> | <integer_element_implicit>
<integer_element_explicit> ::= <element_open> <integer_specifier> <opt_whitespace> <integer_value> <opt_whitespace> <integer_specifier> <element_close>
<integer_element_compact> ::= <integer_specifier> <integer_value>
<integer_element_implicit> ::= <integer_value>
<long_element> ::= <long_element_explicit> | <long_element_compact>
<long_element_explicit> ::= <element_open> <long_specifier> <opt_whitespace> <long_value> <opt_whitespace> <long_specifier> <element_close>
<long_element_compact> ::= <long_specifier> <long_value>
<double_element> ::= <double_element_explicit> | <double_element_compact>
<double_element_explicit> ::= <element_open> <opt_whitespace> <double_specifier> <opt_whitespace> <decimal_value> <double_specifier> <element_close>
<double_element_compact> ::= <double_specifier> <decimal_value>
<decimal_element> ::= <decimal_element_explicit> | <decimal_element_compact>
<decimal_element_explicit> ::= <element_open> <decimal_specifier> <opt_whitespace> <decimal_value> <opt_whitespace> <decimal_specifier> <element_close>
<decimal_element_compact> ::= <decimal_specifier> <decimal_value>
<boolean_element> ::= <boolean_element_explicit> | <boolean_element_compact>
<boolean_element_explicit> ::= <element_open> <boolean_specifier> <opt_whitespace> <boolean> <opt_whitespace> <boolean_specifier> <element_close>
<boolean_element_compact> ::= <boolean_specifier> <boolean>
<datetime_element> ::= <datetime_element_explicit> | <datetime_element_compact>
<datetime_element_explicit> ::= <element_open> <datetime_specifier> <opt_whitespace> <datetime> <opt_whitespace> <datetime_specifier> <element_close>
<datetime_element_compact> ::= <datetime_specifier> <datetime> <datetime_specifier>
<date_element> ::= <date_element_explicit> | <date_element_compact>
<date_element_explicit> ::= <element_open> <date_specifier> <opt_whitespace> <date> <opt_whitespace> <date_specifier> <element_close>
<date_element_compact> ::= <date_specifier> <date> <date_specifier>
<time_element> ::= <time_element_explicit> | <time_element_compact>
<time_element_explicit> ::= <element_open> <time_specifier> <opt_whitespace> <time> <opt_whitespace> <time_specifier> <element_close>
<time_element_compact> ::= <time_specifier> <time> <time_specifier>
<timespan_element> ::= <timespan_element_explicit> | <timespan_element_compact>
<timespan_element_explicit> ::= <element_open> <timespan_specifier> <opt_whitespace> <timespan> <opt_whitespace> <timespan_specifier> <element_close>
<timespan_element_compact> ::= <timespan_specifier> <timespan> <timespan_specifier>
<null_element> ::= <null_element_explicit> | <null_element_compact>
<null_element_explicit> ::= <element_open> <null_specifier> <null_specifier> <element_close>
<null_element_compact> ::= <null_specifier>
<dynamic_element> ::= <dynamic_element_explicit> | <dynamic_element_compact>
<dynamic_element_explicit> ::= <element_open> <dynamic_specifier> <opt_whitespace> <identifier> <opt_whitespace> <dynamic_specifier> <element_close>
<dynamic_element_compact> ::= <dynamic_specifier> <identifier>
<comment_element> ::= <element_open> <comment_specifier> <text> <comment_specifier> <element_close>
<interpolated_element> ::= <interpolated_element_explicit> | <interpolated_element_compact>
<interpolated_element_explicit> ::= <element_open> <interpolated_specifier> <opt_whitespace> <interpolated_content> <opt_whitespace> <interpolated_specifier> <element_close>
<interpolated_element_compact> ::= <interpolated_specifier> <opt_whitespace> <interpolated_content> <opt_whitespace> <interpolated_specifier>
<interpolated_content> ::= (<text> | <string_element_explicit> | <character_element_explicit> | <integer_element_explicit>
| <long_element_explicit> | <double_element_explicit> | <decimal_element_explicit>
| <boolean_element_explicit> | <datetime_element_explicit> | <dynamic_element_explicit>
| <interpolated_element_explicit>)*
<object_element> ::= <object_element_explicit> | <object_element_compact>
<object_element_explicit> ::= <element_open> <object_specifier_open> <opt_whitespace> <key_value_pair>* <opt_whitespace> <object_specifier_close> <element_close>
<object_element_compact> ::= <object_specifier_open> <opt_whitespace> <key_value_pair>* <opt_whitespace> <object_specifier_close>
<array_element> ::= <array_element_explicit> | <array_element_compact>
<array_element_explicit> ::= <element_open> <array_specifier_open> <opt_whitespace> <body_element>* <opt_whitespace> <array_specifier_close> <element_close>
<array_element_compact> ::= <array_specifier_open> <opt_whitespace> <body_element>* <opt_whitespace> <array_specifier_close>
<tuple_element> ::= <tuple_element_explicit> | <tuple_element_compact>
<tuple_element_explicit> ::= <element_open> <tuple_specifier_open> <opt_whitespace> <body_element>* <opt_whitespace> <tuple_specifier_close> <element_close>
<tuple_element_compact> ::= <tuple_specifier_open> <opt_whitespace> <body_element>* <opt_whitespace> <tuple_specifier_close>
<element_open> ::= "<"
<element_close> ::= ">"
<metadata_specifier> ::= "!"+
<string_specifier> ::= "\""+
<keyword_specifier> ::= "="+
<character_specifier> ::= "\\"+
<integer_specifier> ::= "#"+
<long_specifier> ::= "&"+
<double_specifier> ::= "^"+
<decimal_specifier> ::= "*"+
<boolean_specifier> ::= "~"+
<datetime_specifier> ::= "@"+
<date_specifier> ::= "@"+
<time_specifier> ::= "@"+
<timespan_specifier> ::= "@"+
<null_specifier> ::= "?"+
<object_specifier_open> ::= "{"+
<object_specifier_close> ::= "}"+
<array_specifier_open> ::= "["+
<array_specifier_close> ::= "]"+
<tuple_specifier_open> ::= "("+
<tuple_specifier_close> ::= ")"+
<comment_specifier> ::= "/"+
<dynamic_specifier> ::= "|"+
<interpolated_specifier> ::= "'"+
<character_value> ::= <positive_integer>
| <hexadecimal> | <binary>
| "nul" | "cr" | "lf" | "nl" | "tab" | "vtab"
| "bksp" | "ff" | "bel" | "quote" | "apos"
| "backslash" | "lt" | "gt"
<integer_value> ::= <signed_integer>
| <hexadecimal>
| <binary>
| <hex_integer>
| <binary_integer>
| <dynamic_element>
<long_value> ::= <signed_integer>
| <hexadecimal>
| <binary>
| <hex_long>
| <binary_long>
| <dynamic_element>
<decimal_value> ::= <signed_decimal> | <dynamic_element>
<signed_integer> ::= ("+" | "-")? [0-9]+
<signed_decimal> ::= ("+" | "-")? [0-9]+ "." [0-9]*
<positive_integer> ::= [0-9]+
<hexadecimal> ::= "$" ([0-9] | [A-F] | [a-f])+
<binary> ::= "%" [0-1]+
<hex_integer> ::= "#$" ([0-9] | [A-F] | [a-f])+
<binary_integer> ::= "#%" [0-1]+
<hex_long> ::= "&$" ([0-9] | [A-F] | [a-f])+
<binary_long> ::= "&%" [0-1]+
<opt_whitespace> ::= <whitespace>*
<whitespace> ::= (" " | "\t" | "\n" | "\r")
<boolean> ::= "true" | "false"
<datetime> ::= [0-9]{4} "-" [0-9]{2} "-" [0-9]{2} ("T" [0-9]{2} ":" [0-9]{2} (":" [0-9]{2} ("." [0-9]+)?)?)? ("Z" | ("+" | "-") [0-9]{2} ":" [0-9]{2})? | <dynamic_element>
<date> ::= [0-9]{4} "-" [0-9]{2} "-" [0-9]{2} | <dynamic_element>
<time> ::= [0-9]{2} ":" [0-9]{2} (":" [0-9]{2} ("." [0-9]+)?)? | <dynamic_element>
<timespan> ::= ("-")?([0-9]+".")[0-9]{2}":"[0-9]{2}":"[0-9]{2}("."[0-9]+)? | <dynamic_element>
<identifier> ::= ([A-Z] | [a-z] | "_") ([A-Z] | [a-z] | "_" | [0-9])*
<text> ::= <character>*
<character> ::= /* any Unicode character except the sequence that matches the closing delimiter of the current element */
The complete grammar specification is available in the xfer.bnf file in the repository.
Getting Started with .NET
Now that you understand the XferLang format, let's explore how to use it in .NET applications.
Install from NuGet (Recommended)
The ParksComputing.Xfer.Lang library provides a powerful and flexible set of tools for working with XferLang in .NET. To get started, install the package from NuGet:
dotnet add package ParksComputing.Xfer.Lang
Build from Source
Alternatively, you can build XferLang from source by cloning the GitHub repository:
git clone https://github.com/paulmooreparks/Xfer.git
cd Xfer
dotnet build
dotnet test
The library provides similar functionality to popular JSON libraries like Newtonsoft.Json, but for the XferLang format. You can serialize .NET objects to XferLang and deserialize XferLang documents back to .NET objects.
For detailed build instructions, development setup, and contribution guidelines, see the Building XferLang section.
Serialization
Serializing a .NET object to an Xfer string is straightforward using the static XferConvert.Serialize
method.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsActive { get; set; }
}
var user = new User { Name = "John Doe", Age = 30, IsActive = true };
// Serialize the object to an Xfer string
string xferString = XferConvert.Serialize(user, Formatting.Indented);
Console.WriteLine(xferString);
// Output will look like:
// {
// Name "John Doe"
// Age 30
// IsActive ~true
// }
Deserialization
To deserialize an Xfer string back into a .NET object, use the generic XferConvert.Deserialize<T>
method.
string xferString = @"{ Name ""Jane Doe"" Age 28 IsActive ~false }";
var user = XferConvert.Deserialize<User>(xferString);
// user.Name will be "Jane Doe"
// user.Age will be 28
// user.IsActive will be false
Serializer Settings
For more control over serialization and deserialization, you can use the XferSerializerSettings
class. This allows you to configure element styles, null handling, contract resolvers, and custom converters.
Element Style Preferences
Control how elements are serialized using the StylePreference
property:
- Explicit - Maximum safety, uses angle brackets:
<"value">
- CompactWhenSafe - Compact when safe, explicit when necessary:
"value"
(default) - MinimalWhenSafe - Most compact form, including implicit syntax for integers
- ForceCompact - Always compact (use with caution)
Configuration Example
var settings = new XferSerializerSettings
{
StylePreference = ElementStylePreference.CompactWhenSafe,
PreferImplicitSyntax = true, // Use implicit syntax for integers when safe
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CustomContractResolver(),
// ... other settings
};
string xferString = XferConvert.Serialize(user, Formatting.None, settings);
Available Settings
StylePreference
- Controls element serialization stylePreferImplicitSyntax
- Use implicit syntax for integers when possibleNullValueHandling
- How to handle null values (Include/Ignore)ContractResolver
- Custom property name resolutionConverters
- Collection of custom type converters
Property Attributes
The library provides several attributes to control how properties are serialized and deserialized.
XferPropertyAttribute
The XferPropertyAttribute
allows you to customize the property name used in the Xfer document, similar to JsonPropertyName
in System.Text.Json.
public class User
{
[XferProperty("user_name")]
public string UserName { get; set; }
[XferProperty("is_active")]
public bool IsActive { get; set; }
// This property will use the default name "Age"
public int Age { get; set; }
}
This will produce Xfer like: {user_name "Alice" is_active ~true Age 25}
XferEvaluatedAttribute
The XferEvaluatedAttribute
is used internally to mark properties that have been processed during deserialization. This is typically not used by end users but may be relevant for advanced serialization scenarios.
public class MyClass
{
[XferEvaluated]
public string ProcessedProperty { get; set; }
}
Numeric Formatting Attributes
The library supports custom numeric formatting for integer and long properties using the XferNumericFormatAttribute
. This allows you to control how numeric values are serialized in hexadecimal or binary formats.
Available Formats
XferNumericFormat.Decimal
- Standard decimal representation (default)XferNumericFormat.Hexadecimal
- Hexadecimal with#$
prefix for integers,&$
for longsXferNumericFormat.Binary
- Binary with#%
prefix for integers,&%
for longs
Padding Options
MinDigits
- For hexadecimal, pads with leading zeros to minimum digit countMinBits
- For binary, pads with leading zeros to minimum bit count
Example Usage
public class ConfigurationData
{
[XferNumericFormat(XferNumericFormat.Decimal)]
public int Port { get; set; } = 8080;
[XferNumericFormat(XferNumericFormat.Hexadecimal)]
public int ColorValue { get; set; } = 0xFF5733;
[XferNumericFormat(XferNumericFormat.Binary, MinBits = 8)]
public int Flags { get; set; } = 42;
[XferNumericFormat(XferNumericFormat.Hexadecimal, MinDigits = 8)]
public long MemoryAddress { get; set; } = 0x7FF6C2E40000;
}
var config = new ConfigurationData();
string xfer = XferConvert.Serialize(config);
// Result: {Port 8080 ColorValue #$FF5733 Flags #%00101010 MemoryAddress &$7FF6C2E40000}
Safety Notes
- Numeric formatting attributes are only applied to
int
andlong
properties decimal
anddouble
types ignore formatting attributes to preserve fractional precision- Custom formatting respects the configured
ElementStylePreference
for syntax style
Writing Custom Processing Instruction (PI) Processors
XferLang.NET allows you to extend the parser and deserializer by implementing custom logic for Processing Instructions (PIs). This is useful for advanced configuration, runtime directives, schema association, and dynamic value resolution.
Conceptual Overview
- PIs are metadata blocks that can control parsing, validation, and runtime behavior.
- Custom PI processors let you interpret and act on PIs in your own way.
- Common use cases: environment overrides, feature flags, schema binding, dynamic sources.
.NET API Usage
To handle custom PIs, implement or extend the relevant resolver or processor interface. For example, to handle dynamicSource
PIs, implement IDynamicSourceResolver
or subclass DefaultDynamicSourceResolver
:
public class MyDynamicSourceResolver : DefaultDynamicSourceResolver
{
public override string? Resolve(string key)
{
// Custom logic for dynamic keys
if (key == "special")
return "custom-value";
// Fallback to default
return base.Resolve(key);
}
}
// Usage:
var settings = new XferSerializerSettings
{
DynamicSourceResolver = new MyDynamicSourceResolver()
};
var obj = XferConvert.Deserialize(xferString, settings);
Demo: Custom PI Processor
// Example: Custom PI for feature flags
public class FeatureFlagProcessor : IProcessingInstructionProcessor
{
public void Process(ProcessingInstructionElement pi, XferDocumentContext context)
{
if (pi.Name == "featureFlag")
{
var flag = pi["enabled"]?.Value;
// Apply feature flag logic
context.Items["FeatureEnabled"] = flag == "true";
}
}
}
// Register processor
var settings = new XferSerializerSettings();
settings.ProcessingInstructionProcessors.Add(new FeatureFlagProcessor());
Best Practices
- Keep PI processor logic focused and stateless when possible.
- Use context objects to share state between processors and deserialization.
- Validate PI values and handle errors gracefully.
- Document custom PI formats for maintainability.
Custom PI processors make XferLang.NET highly adaptable for configuration, runtime, and integration scenarios.
Contract Resolvers
A contract resolver allows you to customize how property names are mapped between your .NET objects and the Xfer document. For example, you can easily convert property names to camelCase.
public class CamelCaseContractResolver : DefaultContractResolver
{
public override string ResolvePropertyName(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) || !char.IsUpper(propertyName[0]))
{
return propertyName;
}
return char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
}
}
var settings = new XferSerializerSettings
{
ContractResolver = new CamelCaseContractResolver()
};
var user = new User { Name = "John Doe", Age = 30, IsActive = true };
string xferString = XferConvert.Serialize(user, Formatting.Indented, settings);
// Console.WriteLine(xferString);
// {
// name "John Doe"
// age 30
// isActive ~true
// }
Custom Converters
For complex types or when you need a custom representation, you can create your own converter by inheriting from XferConverter<T>
. This gives you full control over the serialization and deserialization process for a specific type.
// A custom converter for the Person class
public class PersonConverter : XferConverter<Person>
{
public override Element WriteXfer(Person value, XferSerializerSettings settings)
{
// Serialize to a compact string: "John Doe,42"
return new StringElement($"{value.Name},{value.Age}");
}
public override Person ReadXfer(Element element, XferSerializerSettings settings)
{
if (element is StringElement stringElement)
{
var parts = stringElement.Value.Split(',');
if (parts.Length == 2 && int.TryParse(parts[1], out int age))
{
return new Person { Name = parts[0], Age = age };
}
}
throw new InvalidOperationException("Cannot convert element to Person.");
}
}
// Add the converter to your settings
var settings = new XferSerializerSettings();
settings.Converters.Add(new PersonConverter());
var person = new Person { Name = "John Doe", Age = 42 };
string xfer = XferConvert.Serialize(person, settings); // Result: "John Doe,42"
var deserializedPerson = XferConvert.Deserialize<Person>(xfer, settings);
GitHub Repository
XferLang is an open-source project hosted on GitHub. The repository contains the complete source code, documentation, examples, and development tools.
Repository Overview
- Main Repository: https://github.com/paulmooreparks/Xfer
- Language: C# (.NET)
- License: MIT License
- Build Status: GitHub Actions CI/CD
- Issues & Feature Requests: GitHub Issues
Key Repository Contents
ParksComputing.Xfer.Lang/
- Main .NET library source codeXferTest/
- Comprehensive unit tests and examplesxferc/
- Command-line XferLang tools and REPLdocs/
- Documentation and examplesschemas/
- XferLang schema examplesxfer.bnf
- Complete grammar specification*.xfer
- Sample XferLang documents
Getting Involved
XferLang welcomes contributions! Here's how you can get involved:
- ⭐ Star the repository to show your support
- 🐛 Report bugs or request features via GitHub Issues
- 💡 Contribute code by submitting pull requests
- 📖 Improve documentation and examples
- 🗣️ Join discussions in the community
Alternative Implementations
The repository also includes experimental implementations in other languages:
xferlang-rs/
- Rust implementation (experimental)- More language implementations planned for future releases
Building XferLang
XferLang is built using standard .NET development tools. Follow these instructions to build the project from source.
Prerequisites
- .NET SDK 6.0 or later - Download from Microsoft
- Git - For cloning the repository
- IDE (Optional): Visual Studio, VS Code, or Rider
Quick Start
# Clone the repository
git clone https://github.com/paulmooreparks/Xfer.git
cd Xfer
# Build the entire solution
dotnet build
# Run the tests
dotnet test
# Create NuGet packages (optional)
dotnet pack --configuration Release
Project Structure
The XferLang solution contains several projects:
- ParksComputing.Xfer.Lang - Main library (.NET Standard 2.0)
- XferTest - Unit tests and integration tests
- xferc - Command-line tools and REPL
- XferService - Web service implementation (optional)
Development Commands
# Build in Debug mode
dotnet build --configuration Debug
# Build in Release mode
dotnet build --configuration Release
# Run specific test project
dotnet test XferTest/
# Run with verbose output
dotnet test --verbosity normal
# Run tests with code coverage
dotnet test --collect:"XPlat Code Coverage"
# Package for NuGet distribution
dotnet pack ParksComputing.Xfer.Lang/ --configuration Release --output ./nupkg
Using the Command-Line Tools
The xferc
project provides command-line tools for working with XferLang:
# Build the CLI tools
dotnet build xferc/
# Run the XferLang REPL
dotnet run --project xferc/
# Convert JSON to XferLang
dotnet run --project xferc/ -- convert input.json output.xfer
# Validate XferLang documents
dotnet run --project xferc/ -- validate document.xfer
IDE Setup
Visual Studio
Open Xfer.sln
in Visual Studio 2022 or later. The solution includes all projects and is ready for development.
VS Code
Install the C# extension and open the repository folder. VS Code will automatically detect the .NET projects.
# Install recommended extensions
code --install-extension ms-dotnettools.csharp
code --install-extension ms-dotnettools.vscode-dotnet-runtime
Contributing Guidelines
When contributing to XferLang:
- Follow the existing code style and conventions
- Add unit tests for new features
- Update documentation as needed
- Ensure all tests pass before submitting PRs
- Consider backward compatibility for API changes
Community & Resources
Here are the key resources for learning, getting help, and staying up to date with XferLang.
Official Resources
- 🏠 Official Website: XferLang Documentation
- 📂 GitHub Repository: Source Code & Issues
- 📦 NuGet Package: ParksComputing.Xfer.Lang
- 🔖 Releases: Version History & Downloads
Documentation & Examples
- 📖 Complete Documentation: This website covers all aspects of XferLang
- 💡 Code Examples: Code examples provide comprehensive usage examples
- 💡 Tests: Unit Tests also show how to use the library and provide test coverage
- 📄 Sample Documents: *.xfer files in the repository
Getting Help
- ❓ Questions: Open a GitHub Discussion
- 🐛 Bug Reports: Create an Issue with details
- 💡 Feature Requests: Suggest improvements via GitHub Issues
- 📧 Direct Contact: Reach out via GitHub for complex questions
Development Status
XferLang is actively developed with regular updates and improvements:
- ✅ Stable Core: Basic serialization/deserialization is production-ready
- 🚧 Active Development: Advanced features and optimizations ongoing
- 🔄 Regular Releases: Check releases for updates
- 📊 CI/CD: Automated testing and quality checks via GitHub Actions
Language Comparison
See how XferLang compares to other data formats:
Feature | XferLang | JSON | XML | YAML |
---|---|---|---|---|
Explicit Types | ✅ Built-in | ❌ Limited | ⚠️ Via attributes | ⚠️ Via tags |
Human Readable | ✅ Yes | ✅ Yes | ⚠️ Verbose | ✅ Yes |
Comments | ✅ Native | ❌ No | ✅ Native | ✅ Native |
Safety | ✅ High | ⚠️ Medium | ✅ High | ⚠️ Medium |