Split out XmlSer stuff. Fix behaviour

This commit is contained in:
Marc Hernandez 2025-11-10 18:51:52 -08:00
parent baa65531a2
commit b41748d0f0
13 changed files with 1182 additions and 595 deletions

View File

@ -80,8 +80,6 @@ dotnet_analyzer_diagnostic.severity = none
# .NET Style Rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules
[*.{cs,csx,cake,vb,vbx}]
indent_style = tab
# "this." and "Me." qualifiers
dotnet_style_qualification_for_field = false:none
dotnet_style_qualification_for_property = false:none

View File

@ -24,7 +24,7 @@ public record CodeGenConfig : imm.Recorded<CodeGenConfig>
public ImmutableDictionary<string, ImmutableList<string>> WLFields { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
// Default member types to process
public lib.Types TypesDefault { get; init; } = lib.Types.Fields | lib.Types.Props;
public ser.Types TypesDefault { get; init; } = ser.Types.Fields | ser.Types.Props;
// How to handle backing fields (might be less relevant for code gen)
public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Regular;
@ -62,9 +62,9 @@ public class TypeStructureAnalyzer
private TypeStructureInfo BuildTypeInfo(Type type)
{
var members = new List<GenMemberMeta>();
var typesTodo = type.GetCustomAttribute<lib.Ser>(true)?.Types ?? _cfg.TypesDefault;
bool doFields = typesTodo.HasFlag(lib.Types.Fields);
bool doProps = typesTodo.HasFlag(lib.Types.Props);
var typesTodo = type.GetCustomAttribute<ser.Ser>(true)?.Types ?? _cfg.TypesDefault;
bool doFields = typesTodo.HasFlag(ser.Types.Fields);
bool doProps = typesTodo.HasFlag(ser.Types.Props);
// Track processed names to avoid duplicates (e.g., field + prop)
var processedNames = new HashSet<string>();
@ -156,8 +156,8 @@ public class TypeStructureAnalyzer
}
return (
actualMi.GetCustomAttribute<lib.Do>() != null,
actualMi.GetCustomAttribute<lib.Dont>() != null,
actualMi.GetCustomAttribute<ser.Do>() != null,
actualMi.GetCustomAttribute<ser.Dont>() != null,
propName
);
}

View File

@ -39,60 +39,6 @@ using System.Net.Sockets;
namespace lib
{
public interface I_Serialize
{
void OnSerialize() {}
object OnDeserialize( object enclosing ) => this;
}
[Flags]
public enum Types
{
Fields = 0b_0001,
Props = 0b_0010,
Implied = 0b_0100,
Explicit = 0b_1000,
None = 0b_0000,
Default = Fields,
All = Fields | Props,
}
public class Ser : Attribute
{
public Types Types { get; set; } = Types.Default;
}
public class Do : Attribute
{
}
public class Dont : Attribute
{
}
public class ChildAttribute : Attribute
{
public string[] Values { get; private set; }
public ChildAttribute( params string[] values )
{
this.Values = values;
}
}
public class ChildFieldsAttribute : ChildAttribute
{
public ChildFieldsAttribute( params string[] values ) : base( values ) { }
}
public class ChildPropsAttribute : ChildAttribute
{
public ChildPropsAttribute( params string[] values ) : base( values ) { }
}
public enum Datastructure
{
Invalid,
@ -143,7 +89,7 @@ namespace lib
public BackingFieldNaming Naming = BackingFieldNaming.Short;
public POD POD = POD.Attributes;
public Types TypesDefault = Types.Fields;
public ser.Types TypesDefault = ser.Types.Fields;
}
public class XmlFormatter2 : IFormatter
@ -328,7 +274,7 @@ namespace lib
{
object obj = DeserializeObject( elem, mi, type, existing );
if( obj is I_Serialize iser )
if( obj is ser.I_Serialize iser )
{
if( _cfg.VerboseLogging ) log.info( $"" );
obj = iser.OnDeserialize( null );
@ -561,8 +507,8 @@ namespace lib
string name = childFi.Name;
var dontAtt = childFi.GetCustomAttributes<lib.Dont>();
var doAtt = childFi.GetCustomAttributes<lib.Do>();
var dontAtt = childFi.GetCustomAttributes<ser.Dont>();
var doAtt = childFi.GetCustomAttributes<ser.Do>();
string propName = "";
@ -574,9 +520,9 @@ namespace lib
var propInfo = narrowType.GetProperty( propName );
dontAtt = propInfo.GetCustomAttributes<lib.Dont>();
dontAtt = propInfo.GetCustomAttributes<ser.Dont>();
doAtt = propInfo.GetCustomAttributes<lib.Do>();
doAtt = propInfo.GetCustomAttributes<ser.Do>();
}
if( dontAtt.Any() )
@ -639,13 +585,13 @@ namespace lib
foreach( var childPi in props )
{
string name = childPi.Name;
var dontAtt = childPi.GetCustomAttributes<lib.Dont>();
var dontAtt = childPi.GetCustomAttributes<ser.Dont>();
if( dontAtt.Any() )
{
continue;
}
var doAtt = childPi.GetCustomAttributes<lib.Do>();
var doAtt = childPi.GetCustomAttributes<ser.Do>();
name = refl.TypeToIdentifier( name );
@ -713,7 +659,7 @@ namespace lib
{
if( doImpls )
{
if( mi.GetCustomAttribute<ChildAttribute>( true ) == null )
if( mi.GetCustomAttribute<ser.ChildAttribute>( true ) == null )
return true;
}
@ -1353,8 +1299,8 @@ namespace lib
foreach( var childFi in fields )
{
string name = childFi.Name;
var dontAtt = childFi.GetCustomAttributes<lib.Dont>();
var doAtt = childFi.GetCustomAttributes<lib.Do>();
var dontAtt = childFi.GetCustomAttributes<ser.Dont>();
var doAtt = childFi.GetCustomAttributes<ser.Do>();
string propName = "";
if( name.StartsWith( "<" ) && name.EndsWith( "BackingField" ) )
@ -1365,8 +1311,8 @@ namespace lib
var propInfo = narrowType.GetProperty( propName );
dontAtt = propInfo.GetCustomAttributes<lib.Dont>();
doAtt = propInfo.GetCustomAttributes<lib.Do>();
dontAtt = propInfo.GetCustomAttributes<ser.Dont>();
doAtt = propInfo.GetCustomAttributes<ser.Do>();
}
@ -1411,7 +1357,7 @@ namespace lib
{
string name = childPi.Name;
var dontAtt = childPi.GetCustomAttributes<lib.Dont>();
var dontAtt = childPi.GetCustomAttributes<ser.Dont>();
if( dontAtt.Any() )
{
continue;
@ -1441,19 +1387,19 @@ namespace lib
}
private static void GetFilters( Types TypesDefault, MemberInfo mi, Type type, out bool filterFields, out bool filterProps, out bool doImpls, out bool doFields, out bool doProps, out HashSet<string> whitelistFields, out HashSet<string> whitelistProps )
private static void GetFilters( ser.Types TypesDefault, MemberInfo mi, Type type, out bool filterFields, out bool filterProps, out bool doImpls, out bool doFields, out bool doProps, out HashSet<string> whitelistFields, out HashSet<string> whitelistProps )
{
var custWLFields = mi?.GetCustomAttribute<ChildFieldsAttribute>( true );
var custWLProps = mi?.GetCustomAttribute<ChildPropsAttribute>( true );
var custWLFields = mi?.GetCustomAttribute<ser.ChildFieldsAttribute>( true );
var custWLProps = mi?.GetCustomAttribute<ser.ChildPropsAttribute>( true );
filterFields = custWLFields != null;
filterProps = custWLProps != null;
var typesTodo = type.GetCustomAttribute<Ser>( true )?.Types ?? TypesDefault;
var typesTodo = type.GetCustomAttribute<ser.Ser>( true )?.Types ?? TypesDefault;
doImpls = typesTodo.HasFlag( Types.Implied );
doFields = filterFields || typesTodo.HasFlag( Types.Fields );
doProps = filterProps || typesTodo.HasFlag( Types.Props );
doImpls = typesTodo.HasFlag( ser.Types.Implied );
doFields = filterFields || typesTodo.HasFlag( ser.Types.Fields );
doProps = filterProps || typesTodo.HasFlag( ser.Types.Props );
whitelistFields = new( custWLFields?.Values ?? new string[0] );
whitelistProps = new( custWLProps?.Values ?? new string[0] );
}

File diff suppressed because it is too large Load Diff

159
ser/XmlSer_Core.cs Normal file
View File

@ -0,0 +1,159 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
namespace ser;
// --- Primitive Handler ---
public partial class PrimitiveHandler : ser.ITypeHandler
{
public bool CanHandle( TypeInfo ti, XmlElement? elem )
{
var typeCode = Type.GetTypeCode( ti.Type );
var typeNotObject = Type.GetTypeCode( ti.Type ) != TypeCode.Object;
var isString = ti.Type == typeof( string );
return typeNotObject | isString;
}
}
// --- Proxy Handler ---
public partial class ProxyHandler : ITypeHandler
{
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => ti.IsProxy || ( elem?.HasAttribute( "proxy" ) ?? false );
}
// --- ISerializable Handler ---
public partial class ISerializableHandler : ITypeHandler
{
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => ti.IsISerializable;
}
// --- Collection Handler ---
public partial class CollectionHandler : ITypeHandler
{
public bool CanHandle( TypeInfo ti, XmlElement? elem ) =>
typeof( IEnumerable ).IsAssignableFrom( ti.Type );
private Type GetElementType( Type collectionType )
{
if( collectionType.IsArray )
return collectionType.GetElementType()!;
if( collectionType.IsGenericType )
{
var args = collectionType.GetGenericArguments();
if( args.Length == 1 )
return args[0];
if( args.Length == 2 )
return typeof( KeyValuePair<,> ).MakeGenericType( args );
}
return typeof( object ); // Fallback
}
private object ConvertToFinalCollection( IList list, Type expectedType, Type elemType )
{
if( expectedType.IsArray )
{
var arr = Array.CreateInstance( elemType, list.Count );
list.CopyTo( arr, 0 );
return arr;
}
if( expectedType.IsGenericType )
{
var genDef = expectedType.GetGenericTypeDefinition();
if( genDef == typeof( ImmutableArray<> ) )
{
var method = typeof( ImmutableArray ).GetMethods()
.First( m => m.Name == "ToImmutableArray" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
.MakeGenericMethod( elemType );
return method.Invoke( null, new object[] { list } )!;
}
if( genDef == typeof( ImmutableList<> ) )
{
var method = typeof( ImmutableList ).GetMethods()
.First( m => m.Name == "ToImmutableList" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType.IsGenericType && m.GetParameters()[0].ParameterType.GetGenericTypeDefinition() == typeof( IEnumerable<> ) )
.MakeGenericMethod( elemType );
return method.Invoke( null, new object[] { list } )!;
}
// Add more immutable/dictionary handlers here (using MakeImmutableDictionary etc.)
}
return list; // Default to List<T> if no specific match
}
}
// --- Object Handler (Default/Complex) ---
public partial class ObjectHandler : ITypeHandler
{
public bool CanHandle( TypeInfo ti, XmlElement? elem ) => true; // Fallback
private (XmlNode? source, bool isAttribute) FindValueSource( XmlElement parent, string name )
{
if( parent.HasAttribute( name ) )
{
return (parent.Attributes[name], true);
}
foreach( XmlNode node in parent.ChildNodes )
{
if( node.NodeType == XmlNodeType.Element && node.Name == name )
{
return (node, false);
}
}
return (null, false);
}
private bool ShouldSetValue( MemberMeta member, bool isHydrating )
{
// [Dont] members are filtered out during metadata generation.
// If a member is present in the metadata and a value is found in the XML,
// we should always set it. This handles both new creation and hydration/merge.
return true;
}
private (object? obj, long id) GetOrCreateInstance( XmlSer xml, XmlElement elem, Type type, object? existing )
{
long id = -1;
bool first = true;
// Check existing
if( existing != null && type.IsAssignableFrom( existing.GetType() ) )
{
id = xml._idGen.GetId( existing, out first );
return (existing, id);
}
// Create new
object? newObj = null;
try
{
if( type.GetConstructor( Type.EmptyTypes ) != null )
{
newObj = Activator.CreateInstance( type );
}
else
{
newObj = FormatterServices.GetUninitializedObject( type );
}
}
catch( Exception ex )
{
log.error( $"Failed to create instance of {type.Name}: {ex.Message}" );
return (null, -1);
}
id = xml._idGen.GetId( newObj, out first );
return (newObj, id);
}
}

188
ser/XmlSer_Read.cs Normal file
View File

@ -0,0 +1,188 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
namespace ser;
// --- Primitive Handler ---
public partial class PrimitiveHandler : ser.ITypeHandler
{
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
{
string val = elem.HasAttribute( "v" ) ? elem.GetAttribute( "v" ) : elem.InnerText;
if( val == "null" )
return null;
// So this is an interesting one. Why not use the expected type? Well, we know we have
// data in the XML, it just wont convert to what we want.
return xml._resolver.ConvertSimple( val, expectedType );
}
}
// --- Proxy Handler ---
public partial class ProxyHandler : ITypeHandler
{
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
{
var ti = xml._meta.Get( expectedType ); // Re-get to ensure we have proxy info
if( !elem.HasAttribute( "proxy" ) || !ti.ProxyDef.HasValue )
{
log.warn( $"Proxy read failed for {expectedType.Name}. Fallback needed." );
return null; // Should fall back or throw
}
var proxyVal = elem.GetAttribute( "proxy" );
return ti.ProxyDef.Value.fnDes( expectedType.FullName, proxyVal );
}
}
// --- ISerializable Handler ---
public partial class ISerializableHandler : ITypeHandler
{
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
{
// Create/Get instance (needs FormatterServices for ISerializable)
object obj = existing ?? FormatterServices.GetUninitializedObject( expectedType );
long id = xml.TrackIfGraph( obj, elem ); // Track it
var serInfo = new SerializationInfo( expectedType, new FormatterConverter() );
foreach( XmlNode objNode in elem.ChildNodes )
{
if( objNode is XmlElement childElem )
{
string childName = childElem.Name;
Type? childType = xml._resolver.FindType( childElem.GetAttribute( "_.t" ) );
if( childType != null )
{
var desValue = xml.ReadNode( childElem, childType, null );
serInfo.AddValue( childName, desValue, childType );
}
}
}
var context = new StreamingContext( StreamingContextStates.All ); // Or use xml.Context
var cons = expectedType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null, new[] { typeof( SerializationInfo ), typeof( StreamingContext ) }, null );
if( cons != null )
{
cons.Invoke( obj, new object[] { serInfo, context } );
}
else
{
log.error( $"ISerializable type {expectedType.Name} lacks the required constructor." );
}
if( obj is IDeserializationCallback cb )
cb.OnDeserialization( obj );
return obj;
}
}
// --- Collection Handler ---
public partial class CollectionHandler : ITypeHandler
{
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
{
// Determine element type
Type elemType = GetElementType( expectedType );
// Create a temporary list
var listType = typeof( List<> ).MakeGenericType( elemType );
var list = (IList)Activator.CreateInstance( listType )!;
xml.TrackIfGraph( list, elem ); // Track list if graph
// Populate the list
foreach( XmlNode node in elem.ChildNodes )
{
if( node is XmlElement childElem )
{
list.Add( xml.ReadNode( childElem, elemType, null ) );
}
}
// Convert to the final expected type (Array, Immutable*, List)
return ConvertToFinalCollection( list, expectedType, elemType );
}
}
// --- Object Handler (Default/Complex) ---
public partial class ObjectHandler : ITypeHandler
{
public object? ReadXml( XmlSer xml, XmlElement elem, Type expectedType, object? existing )
{
var actualType = xml._resolver.Resolve( elem, expectedType );
var ti = xml._meta.Get( actualType );
// 1. Get/Create Instance
var (obj, _) = GetOrCreateInstance( xml, elem, actualType, existing );
if( obj == null )
return null;
// Handle graph refs (if already processed)
if( xml._cfg.Structure == Datastructure.Graph && elem.HasAttribute( "ref" ) )
{
long id = long.Parse( elem.GetAttribute( "ref" ) );
if( xml._processed.TryGetValue( id, out var processedObj ) )
return processedObj;
}
// Track if it's new
xml.TrackIfGraph( obj, elem );
// 2. Hydrate
foreach( var memberMeta in ti.Members )
{
{
var (valueSource, isAttribute) = FindValueSource( elem, memberMeta.XmlName );
if( valueSource != null )
{
object? memberValue;
object? currentMemberValue = memberMeta.GetValue( obj );
if( isAttribute )
{
memberValue = xml._resolver.ConvertSimple( valueSource.Value!, memberMeta.Type );
}
else // Child Element
{
memberValue = xml.ReadNode( (XmlElement)valueSource, memberMeta.Type, currentMemberValue );
}
// Set value, respecting ser.Do/ser.Dont and pre-hydration
if( ShouldSetValue( memberMeta, existing != null ) )
{
memberMeta.SetValue( obj, memberValue );
}
}
}
}
// 3. Post-processing
if( obj is ser.I_Serialize iSer )
obj = iSer.OnDeserialize( null );
if( ti.IsImm && obj is imm.Obj immObj )
return immObj.Record( $"From XML {elem.Name}" );
return obj;
}
}

133
ser/XmlSer_Tests.cs Normal file
View File

@ -0,0 +1,133 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
//using Org.BouncyCastle.Crypto.IO;
namespace ser;
public record class SimpleImmutable( string Name, int Age ) : imm.Timed<SimpleImmutable>;
static public class Test
{
public class ExternalClass
{
public string ActualProperty { get; set; } = "test_ActualProperty_set_inline";
//public string ActualProperty_NotSerialized { get; set; } = "ActualProperty_NotSerialized";
}
[ser.Ser( Types = ser.Types.Implied )]
public partial class LeaveWithExternalClasss
{
//[ser.Do]
//public bool _cccwp_doBool = true;
[ser.ChildPropsAttribute( "ActualProperty" )]
public ExternalClass _leaf_external = new();
//public string _cccwp_doNotSerialize = "test_do_not_serialize";
}
[ser.Ser]
public class TrunkClass
{
public LeaveWithExternalClasss _trunk_leaf = new();
//public int _chf_test = 10;
//private string _chf_priv_string = "test_priv_string";
};
public static void Serialization()
{
ser.XmlCfg cfg = new()
{
Verbose = true,
};
ser.TypeMetaCache metaCache = new( cfg );
//metaCache.AddType( typeof( ClassContainsClassWithProp ), "ActualProperty" );
TrunkClass trunk = new()
{
_trunk_leaf = new()
{
_leaf_external = new()
{
ActualProperty = "ActualProperty_set_in_cons"
}
}
};
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "ActualProperty_set_in_cons" );
var memStream = new MemoryStream();
{
var xml = new ser.XmlSer( cfg, metaCache );
xml.Serialize( memStream, trunk );
}
memStream.Position = 0;
var strXml = System.Text.Encoding.UTF8.GetString( memStream.ToArray() );
var badXml = "<root>\n <prop doBool=\"True\" />\n</root>";
///*
var badXml_02 = @"""<root>
<prop doBool=""True"">
<propHolder>
<ActualProperty ActualProperty=""ActualProperty_set_in_cons"" />
<ActualProperty_NotSerialized ActualProperty_NotSerialized=""ActualProperty_NotSerialized"" />
</propHolder>
<doNotSerialize doNotSerialize=""test_do_not_serialize"" />
</prop>
</root>""";
//*/
Debug.Assert( strXml != badXml );
memStream.Position = 0;
var classHasFields2 = new TrunkClass();
//classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized = "ActualProperty_NotSerialized_set_in_test_01";
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
//Debug.Assert( classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized" );
{
var xml = new ser.XmlSer(cfg, metaCache);
classHasFields2 = xml.Deserialize<TrunkClass>( memStream );
}
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
//Debug.Assert( classHasFields2._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
memStream.Position = 0;
var classHasFields3 = new TrunkClass();
{
var xml = new ser.XmlSer(cfg, metaCache);
xml.DeserializeInto( memStream, classHasFields3 );
}
Debug.Assert( trunk._trunk_leaf._leaf_external.ActualProperty == "test_ActualProperty_set_inline" );
//Debug.Assert( classHasFields3._chf_prop._cccwp_propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
}
}

193
ser/XmlSer_Write.cs Normal file
View File

@ -0,0 +1,193 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
namespace ser;
// --- Primitive Handler ---
public partial class PrimitiveHandler : ser.ITypeHandler
{
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
{
if( obj == null )
{
writer.WriteStartElement( name );
writer.WriteAttributeString( "v", "null" );
writer.WriteEndElement();
return;
}
bool writeElements = xml._cfg.POD == POD.Elements || forceType || !( writer is XmlTextWriter );
if( !writeElements && writer is XmlTextWriter tw )
writeElements = tw.WriteState != WriteState.Element;
if( writeElements )
writer.WriteStartElement( name );
if( forceType || xml._cfg.POD == POD.Elements )
{
if( forceType )
writer.WriteAttributeString( "_.t", obj.GetType().FullName );
writer.WriteAttributeString( "v", obj.ToString() );
}
else
{
writer.WriteAttributeString( name, obj.ToString() );
}
if( writeElements )
writer.WriteEndElement();
}
}
// --- Proxy Handler ---
public partial class ProxyHandler : ITypeHandler
{
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
{
if( obj == null )
{ xml.GetHandler( typeof( object ) ).WriteXml( xml, writer, null, name, memberType, forceType ); return; }
var ti = xml._meta.Get( obj.GetType() );
if( !ti.ProxyDef.HasValue )
{ log.error( "Proxy write called without proxy def!" ); return; }
writer.WriteStartElement( name );
var proxyStr = ti.ProxyDef.Value.fnSer( obj );
// TODO: Allow arbitrary writing here
writer.WriteAttributeString( "proxy", proxyStr );
writer.WriteEndElement();
}
}
// --- ISerializable Handler ---
public partial class ISerializableHandler : ITypeHandler
{
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
{
if( obj == null )
{ /* Write null */ return; }
if( !( obj is ISerializable serObj ) )
{ /* Error */ return; }
writer.WriteStartElement( name );
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
{
if( first )
{
var serInfo = new SerializationInfo( obj.GetType(), new FormatterConverter() );
var context = new StreamingContext( StreamingContextStates.All );
serObj.GetObjectData( serInfo, context );
foreach( var member in serInfo )
{
xml.WriteNode( writer, member.Value, refl.TypeToIdentifier( member.Name ), member.ObjectType, true ); // Force type for ISer
}
}
}
writer.WriteEndElement();
}
}
// --- Collection Handler ---
public partial class CollectionHandler : ITypeHandler
{
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
{
if( obj == null )
{ /* Write null */ return; }
if( !( obj is IEnumerable collection ) )
{ /* Error */ return; }
writer.WriteStartElement( name );
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
{
if( first )
{
Type elemType = GetElementType( obj.GetType() );
int i = 0;
foreach( var item in collection )
{
xml.WriteNode( writer, item, $"i{i++}", elemType, false );
}
}
}
writer.WriteEndElement();
}
}
// --- Object Handler (Default/Complex) ---
public partial class ObjectHandler : ITypeHandler
{
public void WriteXml( XmlSer xml, XmlWriter writer, object? obj, string name, Type memberType, bool forceType )
{
if( obj == null )
{ /* Write null */ return; }
writer.WriteStartElement( name );
xml.WriteTypeAttr( writer, memberType, obj.GetType() );
var ti = xml._meta.Get( obj.GetType() );
if( xml.HandleGraphWrite( writer, obj, out bool first ) )
{
if( first )
{
foreach( var memberMeta in ti.Members )
{
var value = memberMeta.GetValue( obj );
if( value != null )
{
// If POD-Attribute, write attribute
if( memberMeta.IsPodAttribute )
{
try
{
writer.WriteAttributeString( memberMeta.XmlName, value.ToString() );
}
catch( Exception ex )
{
log.error( $"Writing Att {memberMeta.XmlName} = [{value}]" );
}
}
else
{
try
{
xml.WriteNode( writer, value, memberMeta.XmlName, memberMeta.Type, false );
}
catch( Exception ex )
{
log.error( $"Writing Node {memberMeta.XmlName} = [{value}]" );
}
}
}
}
}
}
writer.WriteEndElement();
}
}

125
tests/Tests.cs Normal file
View File

@ -0,0 +1,125 @@
using System.Diagnostics;
using System.IO;
namespace test;
public record class SimpleImmutable( string Name, int Age ) : imm.Timed<SimpleImmutable>;
static public class XmlFormatter2
{
public class ClassWithProperties
{
public string ActualProperty { get; set; } = "test_ActualProperty_set_inline";
public string ActualProperty_NotSerialized { get; set; } = "ActualProperty_NotSerialized";
}
[ser.Ser( Types = ser.Types.Implied )]
public partial class ClassContainsClassWithProp
{
[ser.Do]
public bool doBool = true;
[ser.ChildPropsAttribute( "ActualProperty" )]
public ClassWithProperties propHolder = new();
public string doNotSerialize = "test_do_not_serialize";
}
[ser.Ser]
public class ClassHasFields
{
public ClassContainsClassWithProp prop = new();
};
public static void Serialization()
{
lib.XmlFormatter2Cfg cfg = new()
{
};
ClassHasFields classHasFields = new()
{
prop = new()
{
propHolder = new()
{
ActualProperty = "ActualProperty_set_in_cons"
}
}
};
Debug.Assert( classHasFields.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
var memStream = new MemoryStream();
{
var xml = new lib.XmlFormatter2 ( cfg );
xml.Serialize( memStream, classHasFields );
}
memStream.Position = 0;
var strXml = System.Text.Encoding.UTF8.GetString( memStream.ToArray() );
var badXml = "<root>\n <prop doBool=\"True\" />\n</root>";
/*
<root _.t="test.XmlFormatter2+ClassHasFields" _.version.="2">
<prop doBool="True">
<propHolder ActualProperty="ActualProperty_set_in_cons" ActualProperty_NotSerialized="ActualProperty_NotSerialized" ActualProperty="ActualProperty_set_in_cons" />
</prop>
</root>*/
Debug.Assert( strXml != badXml );
memStream.Position = 0;
var classHasFields2 = new ClassHasFields();
classHasFields2.prop.propHolder.ActualProperty_NotSerialized = "ActualProperty_NotSerialized_set_in_test_01";
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "test_ActualProperty_set_inline" );
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized" );
{
var xml = new lib.XmlFormatter2 ( cfg );
classHasFields2 = xml.Deserialize<ClassHasFields>( memStream );
}
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
Debug.Assert( classHasFields2.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
memStream.Position = 0;
var classHasFields3 = new ClassHasFields();
{
var xml = new lib.XmlFormatter2 ( cfg );
xml.DeserializeInto( memStream, classHasFields3 );
}
Debug.Assert( classHasFields3.prop.propHolder.ActualProperty == "ActualProperty_set_in_cons" );
Debug.Assert( classHasFields3.prop.propHolder.ActualProperty_NotSerialized == "ActualProperty_NotSerialized_set_in_test_01" );
}
}