Hello everyone,
What’s your preferred way of serializing complex, and nested classes in late 2023? The 5 year old sample by @dale uses the BinaryFormatter which is now discontinued for security reasons.
Which alternatives are you using?
Hello everyone,
What’s your preferred way of serializing complex, and nested classes in late 2023? The 5 year old sample by @dale uses the BinaryFormatter which is now discontinued for security reasons.
Which alternatives are you using?
.NET offers several in-box serializers that can handle untrusted data safely:
DataContractSerializer
with NetDataContractSerializer.Also, doing the user data thing is a great alternative - the Rhino way.
— Dale
Thanks for your prompt response, Dale!
Can you elaborate? If you are referring to ArchivableDictionaries attached to geometry, then I’m doing this already, but there are some plugin-level settings I need to serialize too.
We are quite happy using Json.NET - Newtonsoft.
It is solid, fast and has every edge case and feature we’ve needed.
Thanks @david.birch.uk!
I ended up going with this base class definition which so far seems to be covering all my needs. Any feedback would be much appreciated.
using System;
using System.Reflection;
using Newtonsoft.Json;
using Rhino.FileIO;
namespace UI
{
/// <summary>
/// Represents a base class for serializable objects, providing methods for serialization and deserialization.
/// </summary>
[Serializable]
public abstract class SerializableBase
{
/// <summary>
/// Copies property values from another instance of SerializableBase to this instance.
/// </summary>
/// <param name="src">The source instance from which to copy properties.</param>
public virtual void Create(SerializableBase src)
{
PropertyInfo[] properties = src.GetType().GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanRead && propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(src, null), null);
}
}
}
public virtual bool IsValid => true;
/// <summary>
/// Writes the current object to a binary archive.
/// </summary>
/// <param name="archive">The binary archive writer to which the object is written.</param>
/// <returns>true if the write operation is successful; otherwise, false.</returns>
public bool Write(BinaryArchiveWriter archive)
{
bool result = false;
if (archive != null)
{
try
{
archive.Write3dmChunkVersion(1, 0);
string json = JsonConvert.SerializeObject(this, Formatting.None, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
archive.WriteString(json);
result = !archive.WriteErrorOccured;
}
catch
{
}
}
return result;
}
/// <summary>
/// Reads and deserializes an object from a binary archive.
/// </summary>
/// <param name="archive">The binary archive reader from which the object is read.</param>
/// <returns>true if the read operation is successful; otherwise, false.</returns>
public bool Read(BinaryArchiveReader archive)
{
bool result = false;
if (archive != null)
{
archive.Read3dmChunkVersion(out var major, out var minor);
if (1 == major && minor == 0)
{
try
{
string json = archive.ReadString();
SerializableBase src = JsonConvert.DeserializeObject<SerializableBase>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace
});
Create(src);
result = !archive.ReadErrorOccured;
}
catch
{
}
}
}
return result;
}
}
}
That is good news @mrhe
Code looks good, couple of thoughts:
result = false
?JsonSerializerSettings
to a static
field to avoid repeated object creation.Hope that helps
Thanks for the pointers @david.birch.uk!
Here is an updated version for whoever is interested in using it:
using System;
using System.Diagnostics;
using System.Reflection;
using Newtonsoft.Json;
using Rhino.FileIO;
namespace UI
{
/// <summary>
/// Represents a base class for serializable objects, providing methods for serialization and deserialization.
/// </summary>
[Serializable]
public abstract class SerializableBase
{
/// <summary>
/// Static JsonSerializerSettings used for serializing and deserializing objects.
/// This configuration helps in managing the type handling and object creation process during serialization.
/// </summary>
/// <remarks>
/// - TypeNameHandling.Auto: Automatically handles the type information of the serialized objects,
/// ensuring the correct types are instantiated during deserialization.
/// - ObjectCreationHandling.Replace: Instructs the serializer to replace objects in collections
/// and properties rather than merging when deserializing.
/// </remarks>
private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace
};
/// <summary>
/// Copies property values from another instance of SerializableBase to this instance.
/// </summary>
/// <param name="src">The source instance from which to copy properties.</param>
public virtual void Create(SerializableBase src)
{
PropertyInfo[] properties = src.GetType().GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
if (propertyInfo.CanRead && propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(src, null), null);
}
}
}
public virtual bool IsValid => true;
/// <summary>
/// Writes the current object to a binary archive.
/// </summary>
/// <param name="archive">The binary archive writer to which the object is written.</param>
/// <returns>true if the write operation is successful; otherwise, false.</returns>
public bool Write(BinaryArchiveWriter archive)
{
bool result = false;
if (archive != null)
{
try
{
archive.Write3dmChunkVersion(1, 0);
string json = JsonConvert.SerializeObject(this, Formatting.None, SerializerSettings);
archive.WriteString(json);
result = !archive.WriteErrorOccured;
}
catch (Exception ex)
{
Debug.WriteLine($"Write operation failed: {ex.Message}");
result = false;
}
}
return result;
}
/// <summary>
/// Reads and deserializes an object from a binary archive.
/// </summary>
/// <param name="archive">The binary archive reader from which the object is read.</param>
/// <returns>true if the read operation is successful; otherwise, false.</returns>
public bool Read(BinaryArchiveReader archive)
{
bool result = false;
if (archive != null)
{
archive.Read3dmChunkVersion(out var major, out var minor);
if (1 == major && minor == 0)
{
try
{
string json = archive.ReadString();
SerializableBase src = JsonConvert.DeserializeObject<SerializableBase>(json, SerializerSettings);
Create(src);
result = !archive.ReadErrorOccured;
}
catch (Exception ex)
{
Debug.WriteLine($"Read operation failed: {ex.Message}");
result = false;
}
}
}
return result;
}
}
}