Last active
January 25, 2021 01:07
-
-
Save spacechase0/feb87d5f6ea1d19adc212d58ad6c3e5e to your computer and use it in GitHub Desktop.
Net type serialization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace StardewModdingAPI.Toolkit.Serialization | |
{ | |
public class MyNetConverter : JsonConverter | |
{ | |
public override bool CanRead => true; | |
public override bool CanWrite => true; | |
public override bool CanConvert( Type objectType ) | |
{ | |
//Console.WriteLine( "checking " + objectType ); | |
return typeof( AbstractNetSerializable ).IsAssignableFrom( objectType ); | |
} | |
// I thought you didn't need this sort of thing for inherited fields? Sigh... | |
private FieldInfo FindField( Type type, string fieldName ) | |
{ | |
var field = type.GetField( fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance ); | |
if ( field == null ) | |
{ | |
if ( type.BaseType != null ) | |
return this.FindField( type.BaseType, fieldName ); | |
return null; | |
} | |
return field; | |
} | |
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) | |
{ | |
//Console.WriteLine( " Doing " + objectType + " " + existingValue + "!" ); | |
// For some reason dictionaries aren't behaving like other things | |
if ( objectType.Name.Contains( "Dictionary" ) ) // Alternatively could whitelist the dictionary types | |
{ | |
var field1 = this.FindField( objectType, "dict" ); | |
var field2 = this.FindField( objectType, "dictReassigns" ); | |
object dict = serializer.Deserialize( reader, field1.GetValue( existingValue ).GetType() ); | |
field1.SetValue( existingValue, dict ); | |
var dictR = (System.Collections.IDictionary) field2.GetValue( existingValue ); | |
foreach ( object entry in ( ( System.Collections.IDictionary ) dict ).Keys ) | |
dictR.Add( entry, new NetVersion() ); | |
return existingValue; | |
} | |
AbstractNetSerializable ret = ( AbstractNetSerializable ) serializer.Deserialize( reader, objectType ); | |
var existing = existingValue as AbstractNetSerializable; | |
using MemoryStream stream = new MemoryStream(); | |
using BinaryWriter writer = new BinaryWriter(stream); | |
using BinaryReader reader_ = new BinaryReader(stream); | |
ret.WriteFull( writer ); | |
stream.Seek( 0L, SeekOrigin.Begin ); | |
existing.ReadFull( reader_, new NetClock().netVersion ); | |
existing.MarkClean(); | |
return existingValue; | |
} | |
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ) | |
{ | |
if ( value != null ) | |
{ | |
var type = value.GetType(); | |
// For some reason dictionaries aren't behaving like other things | |
if ( type.Name.Contains( "Dictionary" ) ) // Alternatively could whitelist the dictionary types | |
{ | |
object values = this.FindField( type, "dict" ).GetValue( value ); | |
serializer.Serialize( writer, values ); | |
return; | |
} | |
} | |
serializer.Serialize( writer, value ); | |
} | |
} | |
public class MyContractResolver : DefaultContractResolver | |
{ | |
private bool IsNetType( Type type ) | |
{ | |
return typeof( AbstractNetSerializable ).IsAssignableFrom( type ); | |
} | |
protected override JsonContract CreateContract( Type objectType ) | |
{ | |
// Force net types to be objects and not convert as `IEnumerable<>`s | |
if ( this.IsNetType( objectType ) ) | |
{ | |
return this.CreateObjectContract( objectType ); | |
} | |
//else Console.WriteLine( "not net " + objectType ); | |
return base.CreateContract( objectType ); | |
} | |
protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization ) | |
{ | |
string[] netBlacklist = new string[] | |
{ | |
// AbstractNetSerializable | |
"dirtyTick", "minNextDirtyTime", "ChangeVersion", "DeltaAggregateTicks", | |
"needsTick", "childNeedsTick", "parent", "DirtyTick", "Dirty", "NeedsTick", | |
"ChildNeedsTick", "Root", "Parent", "NetFields", | |
// NetFieldBase | |
"_bools", "interpolationStartTick", "previousValue"/*, "targetValue"*/, "InterpolationEnabled", | |
"ExtrapolationEnabled", "InterpolationWait", "notifyOnTargetValueChange", "TargetValue", | |
"Value", "fieldChangeEvent", "fieldChangeVisibleEvent", | |
// NetField, and various other classes have this | |
"xmlInitialized", | |
// NetDictionary | |
"InterpolationWait", "dictReassigns", "outgoingChanges", "incomingChanges", | |
"IsReadOnly", "Keys", "Values", "Pairs", "FieldDict", "OnValueAdded", | |
"OnValueRemoved", "OnValueTargetUpdated", "OnConflictResolve", "Item" /* operator [] */, | |
// NetIntDelta | |
"networkValue", "DirtyThreshold", "Minimum", "Maximum", | |
// NetString | |
"Length", "FilterStringEvent", | |
// NetRefBase | |
"Serializer", "deltaType", "reassigned", "OnConflictResolve", | |
// NetRectangle | |
"X", "Y", "Width", "Height", "Center", "Top", "Bottom", "Left", "Right", | |
// NetPoint | |
"X", "Y", | |
// NetObjectShrinkList | |
"Count", "IsReadOnly", | |
// NetArray | |
"appendPosition", "Fields", "Count", "Length", "IsReadOnly", "IsFixedSize", | |
"OnFieldCreate", | |
// NetList | |
"initialSize", "resizeFactor", "Count", "Capacity", "IsReadOnly", "OnElementChanged", | |
"OnArrayReplaced", | |
// NetVector2 | |
"AxisAlignedMovement", "ExtrapolationSpeed", "MinDeltaForDirectionChange", | |
"MaxInterpolationDistance", "interpolateXFirst", "isExtrapolating", "isFixingExtrapolation", | |
"X", "Y", | |
// NetCollection | |
"Count", "ISFixedSize", "IsReadOnly", "InterpolationWait", "OnValueAdded", | |
"OnValueRemoved", | |
// NetPosition | |
"SmoothingFudge", "DefaultDeltaAggragateTicks", "ExtrapolationEnabled", "X", "Y", | |
// NetLocationRef | |
"_dirty", "_usedLocalLocation", "_gameLocation", | |
// NetFarmerRef | |
"UID", "Value", | |
// NetLocationRef | |
"Value", | |
// "NetBuildingRef | |
"Value", | |
}; | |
bool isNetType = this.IsNetType( type ); | |
//Console.WriteLine( "test: " + isNetType + " " + type ); | |
var members = this.GetSerializableMembers( type ); | |
var props = new JsonPropertyCollection( type ); | |
foreach ( var member in members ) | |
{ | |
if ( member.GetCustomAttribute( typeof( XmlIgnoreAttribute ) ) != null ) | |
{ | |
continue; | |
} | |
//Console.WriteLine( "checking prop " + member ); | |
Type memType = null; | |
string memName = null; | |
if ( member is FieldInfo field ) | |
{ | |
memType = field.FieldType; | |
memName = field.Name; | |
} | |
else if ( member is PropertyInfo property ) | |
{ | |
memType = property.PropertyType; | |
memName = property.Name; | |
} | |
if ( isNetType ) | |
{ | |
if ( netBlacklist.Contains( memName ) ) | |
{ | |
continue; | |
} | |
} | |
//Console.WriteLine( "\tdoing prop " + member ); | |
var prop = this.CreateProperty( member, MemberSerialization.Fields ); | |
if ( prop.Ignored ) | |
continue; | |
if ( prop.PropertyName == "value" || prop.PropertyName == "targetValue" ) | |
prop.TypeNameHandling = TypeNameHandling.Objects; | |
if ( isNetType ) | |
{ | |
if ( type.Name.StartsWith( "NetRef" ) && prop.PropertyName == "value" || | |
!type.Name.StartsWith( "NetRef" ) && prop.PropertyName == "targetValue" ) | |
continue; | |
} | |
//Console.WriteLine( "memtype:" + memType ); | |
if ( this.IsNetType( memType ) ) | |
{ | |
//Console.WriteLine( "net type! " + memType ); | |
prop.Converter = prop.ItemConverter = new MyNetConverter(); | |
} | |
//Console.WriteLine( $"\t\tACTUALLY doing prop {prop.Writable} {prop.Readable}" ); | |
props.Add( prop ); | |
} | |
return props.OrderBy(p=>p.Order??-1).ToList(); | |
} | |
protected override List<MemberInfo> GetSerializableMembers( Type objectType ) | |
{ | |
bool isNetType = this.IsNetType( objectType ); | |
if ( isNetType ) | |
{ | |
List<MemberInfo> members = new List<MemberInfo>(); | |
foreach ( var field in objectType.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) ) | |
members.Add( field ); | |
foreach ( var prop in objectType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) ) | |
members.Add( prop ); | |
return members; | |
} | |
return base.GetSerializableMembers( objectType ); | |
} | |
} | |
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary> | |
public class JsonHelper | |
{ | |
/********* | |
** Accessors | |
*********/ | |
/// <summary>The JSON settings to use when serializing and deserializing files.</summary> | |
public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings | |
{ | |
Formatting = Formatting.Indented, | |
ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded | |
Converters = new List<JsonConverter> | |
{ | |
new SemanticVersionConverter(), | |
new StringEnumConverter(), | |
}, | |
ContractResolver = new MyContractResolver(), | |
ReferenceLoopHandling = ReferenceLoopHandling.Serialize, // For net types | |
}; | |
// rest of the file here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment