Serialization formats
The text editor shows a serialized representation of the selected node. By default this is XML, but you can switch to JSON or plug in your own format.
| Serializer | Package | Frameworks |
|---|---|---|
| XmlStructureSerializer (default) | NanoByte.StructureEditor | .NET Framework 4.5+, .NET 8.0+ |
| JsonStructureSerializer (System.Text.Json) | NanoByte.StructureEditor | .NET 8.0+ |
| NewtonsoftJsonStructureSerializer (Newtonsoft.Json) | NanoByte.StructureEditor.NewtonsoftJson | .NET Framework 4.5+, .NET 8.0+ |
Choosing a serializer
Set the Serializer property to change the format of the text editor:
editor.Serializer = JsonStructureSerializer.Instance;
To load and save files in the same format, use SerializingCommandManager<T> instead of CommandManager<T> (which always uses XML). Passing one to Open() also sets the editor's Serializer:
editor.Open(SerializingCommandManager<AddressBook>.Load(path, JsonStructureSerializer.Instance));
// ...
editor.CommandManager.Save(path);
Both JSON serializers accept options/settings in their constructors. Start from JsonStructureSerializer.DefaultOptions or NewtonsoftJsonStructureSerializer.DefaultSettings and adjust as needed.
Modeling the data for JSON
XML serialization attributes such as [XmlAttribute], [XmlElement] and [XmlIgnore] have no effect on JSON. Use the JSON library's own attributes (e.g., [JsonIgnore], [JsonPropertyName]) where needed.
Get-only collections
Lists exposed as get-only properties, such as public List<Contact> Contacts { get; } = [];, need to be populated in place rather than replaced:
JsonStructureSerializer.DefaultOptionssetsPreferredObjectCreationHandling = JsonObjectCreationHandling.Populatefor this. Keep it if you provide your own options.- Newtonsoft.Json populates existing collections by default.
Polymorphism
Polymorphic lists and polymorphic properties need a type discriminator in JSON, so the deserializer knows which concrete type to create.
System.Text.Json: annotate the base type with [JsonPolymorphic] and one [JsonDerivedType] per concrete type:
[JsonPolymorphic]
[JsonDerivedType(typeof(LandlineNumber), nameof(LandlineNumber))]
[JsonDerivedType(typeof(MobileNumber), nameof(MobileNumber))]
public abstract class PhoneNumber { /* ... */ }
The discriminator is written as a $type property. By default, it must be the first property of the object. On .NET 9+ you can set AllowOutOfOrderMetadataProperties = true to relax this.
Newtonsoft.Json: enable TypeNameHandling.Auto. Always combine it with a SerializationBinder that only allows your own types. Otherwise, loading untrusted JSON can instantiate arbitrary types.
editor.Serializer = new NewtonsoftJsonStructureSerializer(new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto,
SerializationBinder = new KnownTypesBinder(typeof(LandlineNumber), typeof(MobileNumber))
});
class KnownTypesBinder(params Type[] knownTypes) : ISerializationBinder
{
public Type BindToType(string? assemblyName, string typeName)
=> knownTypes.FirstOrDefault(x => x.Name == typeName)
?? throw new JsonSerializationException($"Unknown type: {typeName}");
public void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
{
assemblyName = null;
typeName = serializedType.Name;
}
}
When you select a single list element or property value in the tree, the text editor serializes it as its concrete type. So the discriminator only appears when you view the containing object.
Custom formats
Implement IStructureSerializer to support other formats (e.g., YAML):
FormatNameselects the syntax highlighting of the text editor (e.g.,XML,JSON).Serialize()andDeserialize()convert between objects and text.Deserialize()should throw on invalid input.TryGetErrorLocation()extracts a line and column from exceptions thrown byDeserialize(). The text editor uses this to underline the error.