Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 82 additions & 4 deletions engine/Sandbox.Engine/Scene/Components/Component.Serialize.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Facepunch.ActionGraphs;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace Sandbox;

Expand Down Expand Up @@ -63,7 +64,15 @@ public JsonNode Serialize( GameObject.SerializeOptions options = null )
var value = field.GetValue( this );
try
{
json.Add( field.Name, Json.ToNode( value, field.FieldType ) );
var serializerOptions = GetSerializerOptions( field, field.FieldType );
if ( value is IJsonPopulator jss )
{
json.Add( field.Name, jss.Serialize() );
}
else
{
json.Add( field.Name, JsonSerializer.SerializeToNode( value, field.FieldType, serializerOptions ) );
}
}
catch ( System.Exception e )
{
Expand All @@ -75,7 +84,15 @@ public JsonNode Serialize( GameObject.SerializeOptions options = null )
var value = prop.GetValue( this );
try
{
json.Add( prop.Name, Json.ToNode( value, prop.PropertyType ) );
var serializerOptions = GetSerializerOptions( prop, prop.PropertyType );
if ( value is IJsonPopulator jss )
{
json.Add( prop.Name, jss.Serialize() );
}
else
{
json.Add( prop.Name, JsonSerializer.SerializeToNode( value, prop.PropertyType, serializerOptions ) );
}
}
catch ( System.Exception e )
{
Expand Down Expand Up @@ -187,6 +204,51 @@ public void DeserializeImmediately( JsonObject node )
[ConVar( "serialization_warn_time", ConVarFlags.Protected, Help = "Warn if deserializing a component property takes longer than this number of milliseconds." )]
internal static int DeserializeTimeWarnThreshold { get; set; }

/// <summary>
/// Gets JsonSerializerOptions with a custom converter if the member has a JsonConverterAttribute.
/// Otherwise returns the default options.
/// </summary>
private static JsonSerializerOptions GetSerializerOptions( MemberDescription member, Type targetType )
{
var jsonConverterAttr = member.GetCustomAttribute<JsonConverterAttribute>();
if ( jsonConverterAttr == null || jsonConverterAttr.ConverterType == null )
{
return Json.options;
}

// Create a new options instance based on the default options
var customOptions = new JsonSerializerOptions( Json.options );

// Create an instance of the converter type
try
{
var converterInstance = Activator.CreateInstance( jsonConverterAttr.ConverterType );

// Check if it's a JsonConverterFactory
if ( converterInstance is JsonConverterFactory factory )
{
// For factories, we need to create the converter for the specific type
var converter = factory.CreateConverter( targetType, customOptions );
if ( converter != null )
{
customOptions.Converters.Insert( 0, converter );
}
}
else if ( converterInstance is JsonConverter converter )
{
// For regular converters, add them directly
customOptions.Converters.Insert( 0, converter );
}
}
catch ( System.Exception e )
{
Log.Warning( $"Failed to create instance of JsonConverter type {jsonConverterAttr.ConverterType}: {e.Message}" );
return Json.options;
}

return customOptions;
}

private void StopTiming( MemberDescription member, Type type, FastTimer timer )
{
if ( DeserializeTimeWarnThreshold <= 0 ) return;
Expand Down Expand Up @@ -231,7 +293,15 @@ internal void DeserializeProperty( MemberDescription member, JsonNode node )
}
else
{
prop.SetValue( this, Json.FromNode( node, prop.PropertyType ) );
if ( node is null )
{
prop.SetValue( this, default );
}
else
{
var serializerOptions = GetSerializerOptions( prop, prop.PropertyType );
prop.SetValue( this, node.Deserialize( prop.PropertyType, serializerOptions ) );
}
}

StopTiming( prop, prop.PropertyType, startTime );
Expand Down Expand Up @@ -265,7 +335,15 @@ internal void DeserializeProperty( MemberDescription member, JsonNode node )
}
else
{
field.SetValue( this, Json.FromNode( node, field.FieldType ) );
if ( node is null )
{
field.SetValue( this, default );
}
else
{
var serializerOptions = GetSerializerOptions( field, field.FieldType );
field.SetValue( this, node.Deserialize( field.FieldType, serializerOptions ) );
}
}

StopTiming( field, field.FieldType, startTime );
Expand Down