Getting JsonTypeCoercionException when using JsonFX

I have this set of classes that I want to serialize/deserialize. Its called a Behaviour Tree and its in a tree structure (Composite pattern).

One problem that I solved is this (when I store subclasses in a BaseClass[ ] array, when deserialized, they should come out as the same subclasses)

Now my classes have generics. This is the base class’ decleration:

public abstract class Node<T> where T : IBlackboardHolder

it takes in an interface, but it never stores that interface in a member variable. Its only used here:

public abstract RunState Tick(T agent)

where it does stuff to T in that function.

Now, as I need type hinting to make serializing of the arrays work, here’s how the type hint of one of them look like:

"__type": "BehaviourTree.Root`1[[IUnit, Assembly-CSharp, Version=2.0.0.35553, Culture=neutral, PublicKeyToken=null]], Assembly-CSharp"

When I make edits to my code, and recompile, when I serialize again, this is how it looks like:

"__type": "BehaviourTree.Root`1[[IUnit, Assembly-CSharp, Version=2.0.0.35636, Culture=neutral, PublicKeyToken=null]], Assembly-CSharp"

The odd thing is the Version part there changed. Now when I try to deserialize the JSON string with the older Version, I get this error:

JsonTypeCoercionException: Interfaces, Abstract classes, and unsupported ValueTypes cannot be deserialized. (BehaviourTree.Node`1[[IUnit, Assembly-CSharp, Version=2.0.0.35553, Culture=neutral, PublicKeyToken=null]])
JsonFx.Json.TypeCoercionUtility.InstantiateObject (System.Type objectType, System.Collections.Generic.Dictionary`2 memberMap) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:136)
JsonFx.Json.TypeCoercionUtility.CoerceType (System.Type targetType, IDictionary value, System.Collections.Generic.Dictionary`2 memberMap) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:439)
JsonFx.Json.TypeCoercionUtility.CoerceType (System.Type targetType, System.Object value) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:359)
JsonFx.Json.TypeCoercionUtility.CoerceArray (System.Type elementType, IEnumerable value) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:598)
JsonFx.Json.TypeCoercionUtility.CoerceList (System.Type targetType, System.Type arrayType, IEnumerable value) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:457)
JsonFx.Json.TypeCoercionUtility.CoerceType (System.Type targetType, System.Object value) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:365)
JsonFx.Json.TypeCoercionUtility.SetMemberValue (System.Object result, System.Type memberType, System.Reflection.MemberInfo memberInfo, System.Object value) (at Assets/Packages/Scripts/JsonFx.Json/TypeCoercionUtility.cs:279)
JsonFx.Json.JsonReader.ReadObject (System.Type objectType) (at Assets/Packages/Scripts/JsonFx.Json/JsonReader.cs:481)
JsonFx.Json.JsonReader.Read (System.Type expectedType, Boolean typeIsHint) (at Assets/Packages/Scripts/JsonFx.Json/JsonReader.cs:304)
JsonFx.Json.JsonReader.Deserialize[Root`1] () (at Assets/Packages/Scripts/JsonFx.Json/JsonReader.cs:262)
AssetUtility.FromSerializedString[Root`1] (System.String jsonText) (at Assets/TacticsEnsemble/Scripts/Util/AssetUtility.cs:77)
AssetUtility.LoadFromLocal[Root`1] (System.String filePath) (at Assets/TacticsEnsemble/Scripts/Util/AssetUtility.cs:36)
BehaviourTreeEditorWindow.OnGUI () (at Assets/TacticsEnsemble/Scripts/AI/BehaviourTree/Editor/BehaviourTreeEditorWindow.cs:310)
System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)

This is what I’m looking at:

JsonTypeCoercionException: Interfaces, Abstract classes, and unsupported ValueTypes cannot be deserialized. (BehaviourTree.Node`1[[IUnit, Assembly-CSharp, Version=2.0.0.35553, Culture=neutral, PublicKeyToken=null]])

It thinks it needs to deserialize to a Node, which is an abstract base class and that shouldn’t happen. In fact, my JSON string never has Node in its type hints.

Take note if I try to deserialize the JSON string that has the newer version (2.0.0.35636 as opposed to 2.0.0.35553), no errors happen and I get my object deserialized fine.

So, as an experiment, I changed the type hinting from

type.FullName +", "+type.Assembly.GetName().Name

to:

type.ToString() +", "+type.Assembly.GetName().Name

and type hints I get now become:

"__type": "BehaviourTree.Root`1[IUnit], Assembly-CSharp"

and that seems to have fixed my problem.

My concern is, I’m not sure if that introduces any non-obvious bugs to the deserializing process.

The odd thing is the Version part there changed. Now when I try to deserialize the JSON string with the older Version, I get this error…

FYI, if you don’t want your version to auto-roll with each compilation you need to edit Properties/AssemblyInfo.cs and replace the asterisks with actual numbers. E.g., here is the attribute string JsonFx uses.

So, as an experiment, I changed the type hinting from type.FullName to type.ToString()

I’d really hesitate to use .ToString() for something other than debugging. They reserve the right to change what comes out of .ToString() and it often has a bunch of garbage too. If you are finding that type.FullName is too specific, you should probably use type.Name. In fact that very well could be what is inside .ToString() but it is safer for future or alternate runtimes as it won’t change implementation.

My concern is, I’m not sure if that introduces any non-obvious bugs to the deserializing process.

As for your question, if it works then you are probably okay. FYI, type hinting is kind of a hack in JSON and isn’t really much more than a hint. You may find that there is another edge case where C#'s type system is tough to rehydrate. Most of the code in JsonFx is simply to deal with how strongly typed it really is.

Also be aware that generics and inheritance have been problematic in the past. I know that they’ve improved it a lot in some situations with covariance and contravariance, etc. but generally it is a brittle pattern. Be thankful you aren’t working in Java where it basically just blows up or ignores the type variable altogether.

Ok, good to know, thanks.

EDIT: Not sure about AssemblyInfo.cs. Unity doesn’t have that.

type.Name gives out this:

"__type": "Root`1, Assembly-CSharp"

which gives me the same error.