Deserialize json to complex dynamic type using Unity JsonUtility

Hello!

I solving a problem with deserialize json to complex dynamic type using Unity JsonUtility. And have some issue with which I need help.

First, I create a simple dynamic type with C# TypeBuilder. This type contains one string field. This type deserialised fine.

using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;

public static class JsonDynamicTypeDeserializer
{
    private static TypeBuilder GenerateDynamicTypeBuilder(string name, ModuleBuilder mb, AssemblyName an)
    {
        var typeBuilder = mb.DefineType($"{name}",
            TypeAttributes.Public
            | TypeAttributes.Class
            | TypeAttributes.Serializable,
            parent: null);
        return typeBuilder;
    }

    public static void SimpleDynamicTypeDeserialize()
    {
        var an = new AssemblyName("DynamicAssembly");
        var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var mb = ab.DefineDynamicModule(an.Name);

        var simpleTb = GenerateDynamicTypeBuilder("Simple", mb, an);
        simpleTb.DefineField("string_data", typeof(string), FieldAttributes.Public);

        var simpleT = simpleTb.CreateType();

        dynamic data = JsonUtility.FromJson("{\"string_data\":\"Hello World!\"}", simpleT);

        Debug.Log($"string_data='{data.string_data}'");
    }
}

Output: string_data='Hello World!'

Then I create a complex dynamic type with C# TypeBuilder. This type contains that simple dynamic typed field. This type not deserialized.

using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;

public static class JsonDynamicTypeDeserializer
{
    private static TypeBuilder GenerateDynamicTypeBuilder(string name, ModuleBuilder mb, AssemblyName an)
    {
        var typeBuilder = mb.DefineType($"{name}",
            TypeAttributes.Public
            | TypeAttributes.Class
            | TypeAttributes.Serializable,
            parent: null);
        return typeBuilder;
    }

    public static void ComplexDynamicTypeDeserialize()
    {
        var an = new AssemblyName("DynamicAssembly");
        var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var mb = ab.DefineDynamicModule(an.Name);

        var simpleTb = GenerateDynamicTypeBuilder("Simple", mb, an);
        simpleTb.DefineField("string_data", typeof(string), FieldAttributes.Public);

        var simpleT = simpleTb.CreateType();

        var complexTb = GenerateDynamicTypeBuilder("Complex", mb, an);
        complexTb.DefineField("complex_data", simpleT, FieldAttributes.Public);

        var complexT = complexTb.CreateType();

        dynamic data = JsonUtility.FromJson("{\"complex_data\":{\"string_data\":\"Hello World!\"}}", complexT);

        Debug.Log($"complex_string_data='{data.complex_data?.string_data}'");
    }
}

Output: complex_string_data=''

Then for test I create another complex dynamic type with C# TypeBuilder. This type contains non dynamic simple typed field. This type deserialized fine. :eyes:

using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;

public static class JsonDynamicTypeDeserializer
{
    private static TypeBuilder GenerateDynamicTypeBuilder(string name, ModuleBuilder mb, AssemblyName an)
    {
        var typeBuilder = mb.DefineType($"{name}",
            TypeAttributes.Public
            | TypeAttributes.Class
            | TypeAttributes.Serializable,
            parent: null);
        return typeBuilder;
    }

    [Serializable]
    public class SimpleData
    {
        public string string_data;
    }

    public static void ComplexDynamicTypeWithNonDynamicFieldDeserialize()
    {
        var an = new AssemblyName("DynamicAssembly");
        var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var mb = ab.DefineDynamicModule(an.Name);

        var complexTb = GenerateDynamicTypeBuilder("Complex", mb, an);
        complexTb.DefineField("complex_data", typeof(SimpleData), FieldAttributes.Public);

        var complexT = complexTb.CreateType();

        dynamic data = JsonUtility.FromJson("{\"complex_data\":{\"string_data\":\"Hello World!\"}}", complexT);

        Debug.Log($"complex_string_data='{data.complex_data?.string_data}'");
    }
}

Output: complex_string_data='Hello World!'

Is this a limitation of JsonUtility? Or I miss something?

P.S.
I found source code of JsonUtility.bindings.cs

But this source code no help.
Is there any way to look at source code of JsonUtility deeper (FromJsonInternal method)?

The actual implementation of JsonUtility is in Unity’s native C++ code, which is not public.

I don’t see anything wrong with your code but you’re really stretching what JsonUtility is intended for. It’s basically an interface to Unity’s serialization system, just using a different format than Unity’s usual yaml/binary ones. I don’t think Unity’s serialization is intended to be used with dynamic types and you could be hitting a limitation where nested types need to part of a non-dynamic assembly. You’re probably better off using a different JSON library.

2 Likes

Adrian is right. Unity’s JsonUtility is quite limited. It’s fast but designed to work with a limited subset of what json generally allows. It’s even surprising that it even works with the TypeBuilder ^^. I never actually use any dynamic type generation. It also limits your build targets as all AOT platforms are out of the question which includes all IL2CPP platforms like WebGL or Android.

It’s not clear how or where you would use your dynamic classes in the end. I don’t really see much value in having dynamic classes in a game. It’s most likely just slower and more insecure. One of the advantages of a compiled strictly typed language is that it’s much easier to reason about the code which reduces possible errors.

There are some easy ways to just load arbitrary json data in a completely type safe matter. You may want to have a look at my SimpleJSON parser. Note the framework is designed so it can be easily extended, which I did with several extension files. Here’s one for some common Unity types. All SimpleJSON does is parsing the raw json information into wrapper classes which provide easy, direct and type safe access to that data without the need to manually cast / convert the actual values.

1 Like

I agree. But in the end I use that logic only in Editor to scan localized strings, that inside json data.
As input I got json file name and data field name. Therefore I use dynamic type created on the fly.

Thank you for share SimpleJSON :slight_smile:

Thank you all for response. I will presume that this kind of behaviour - is limitation of JsonUtility.