Serializing / deserializing DataTrees with Json.NET

Hi all,
I’m trying to serialize a DataTree<int> into a json string using Newtonsoft’s Json.NET package.
The serialization works just fine and the resulting json string looks like it contains all the necessary info.

The deserialization, however, always gives me and empty tree.
This is what I’m doing:

using Newtonsoft.Json;

// Create a test tree
DataTree<int> testTree = new DataTree<int>();
testTree.Add(0, new GH_Path(0, 1));
testTree.Add(13, new GH_Path(0, 1));
testTree.Add(42, new GH_Path(1, 2));

// Serialize
string testTreeJson = JsonConvert.SerializeObject(testTree);

// Deserialize
DataTree<int> testTreeDeserialized = JsonConvert.DeserializeObject<DataTree<int>>(testTreeJson);

testTreeJson looks like this:

{
	"BranchCount":2,
	"DataCount":3,
	"Paths":
	[
		{
			"DebuggerDisplay":"{0;1}",
			"Indices":[0,1],
			"InternalPath":[0,1],
			"Length":2,
			"Valid":true
		},
		{
			"DebuggerDisplay":"{1;2}",
			"Indices":[1,2],
			"InternalPath":[1,2],
			"Length":2,
			"Valid":true
		}
	],
	"Branches":
	[
		[0,13],
		[42]
	],
	"TopologyDescription":"Tree (Branches = 2)\\r\\n{0;1} (N = 2)\\r\\n{1;2} (N = 1)"
}

However, my testTreeDeserialized always is empty.

The bad thing is that I don’t have much experience with Json.NET and don’t know if the problem is with Grasshopper or Json.NET

If anybody has any hints on where to look or some advice it will be greatly apprechiated!
Thanks a lot!
Paul

I don’t know anything about JSON, but I doubt it’s smart enough to recreate an entire tree from a string. It seems to just be collecting a bunch of properties (some of them readonly) in the class and calling it quits. It doesn’t seem to have any data anywhere, only some info about paths and branches.

If you know your trees are always of a specific type then you should be able to handle that relatively easily, if however you do not know the type constraint at compile time you’re in for a rough ride.

There are some methods for (de)serializing GH_Structure<T> instances in Grasshopper, but nothing for DataTree<T>.

I think the data is in the "Branches" item in the json above, at least these are the ints I put in the tree.
Guess you are right and I need to create my own serialization method. As I’m only dealing with ints it should not be too hard (hopefully).
I’ll report back once I find out more…

According to this there’s the possibility to code a custom json deserializer for specific data types.

I’ve come up with the following. This is the test code (only works if testTree is a class property, otherwise it doesn’t accept the [JsonConverter...] attribute):

// Create this as a class property
[JsonConverter(typeof(DataTreeConverter<int>))]
DataTree<int> testTree = new DataTree<int>();

// Call this in some class method
testTree.Add(0, new GH_Path(0, 1));
testTree.Add(13, new GH_Path(0, 1));
testTree.Add(42, new GH_Path(1, 2));
string testTreeJson = JsonConvert.SerializeObject(testTree);
DataTree<int> testTreeDeserialized = JsonConvert.DeserializeObject<DataTree<int>>(testTreeJson);

An this is the custom deserializer

public class DataTreeConverter<T> : Newtonsoft.Json.JsonConverter
{
	public override bool CanRead
	{
		get { return true; }
	}

	public override bool CanWrite
	{
		get { return false; }
	}

	public override bool CanConvert(Type objectType)
	{
		return typeof(DataTree<T>).IsAssignableFrom(objectType); //return false;
	}

	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
	{
		if (reader.TokenType == JsonToken.Null)
		{
			return string.Empty;
		}
		else if (reader.TokenType == JsonToken.String)
		{
			return serializer.Deserialize(reader, objectType);
		}
		else
		{
			// This is where the tree is built.
			JObject obj = JObject.Load(reader);
			// Check if the current JObject has the "BranchCount" parameter.
			if (obj["BranchCount"] != null)
			{
				DataTree<T> tree = new DataTree<T>();
				// Get path info and data.
				var pathsJson = obj["Paths"];
				var dataJson = obj["Branches"];
				// Add data to tree.
				for (int i = 0; i < pathsJson.Count(); i++)
				{
					int[] pathIndices = pathsJson[i]["Indices"].ToObject<int[]>();
					T[] pathData = dataJson[i].ToObject<T[]>();
					tree.AddRange(pathData, new GH_Path(pathIndices));
				}
				return tree;
			}
			else
				return serializer.Deserialize(reader, objectType);
		}
	}

	public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
	{
		throw new NotImplementedException("Not implemented yet");
	}
}

I know this is a bit cryptic alltogether and I must admit that I understand only 75% of it. I simply post it anyways in case someone else might find it useful…

2 Likes