mgholam / fastJSON

Smallest, fastest polymorphic JSON serializer
https://www.codeproject.com/Articles/159450/fastJSON-Smallest-Fastest-Polymorphic-JSON-Seriali
MIT License
479 stars 147 forks source link

UnityEngine.Vector3 serializes but does not deserialize (with fix!) #130

Open jhughes2112 opened 3 years ago

jhughes2112 commented 3 years ago

Here's the error (which was reported previously, but nobody actually solved):

InvalidProgramException: Invalid IL code in (wrapper dynamic-method) FakeVector3:_cgm (object): IL_0013: ret       

System.Delegate.CreateDelegate (System.Type type, System.Object firstArgument, System.Reflection.MethodInfo method, System.Boolean throwOnBindFailure, System.Boolean allowClosed) (at <695d1cc93cca45069c528c15c9fdd749>:0)

Here is some simple test code that shows it broken:

public struct FakeVector3
{
    public float x;
    public float y;
    public float z;

    public static FakeVector3 right { get { return new FakeVector3() { x=1,y=2,z=3 }; } }
}

FakeVector3 v = new FakeVector3() { x=0, y=1, z=2 };
string json = fastJSON.JSON.ToJSON(v);
FakeVector3 newV = fastJSON.JSON.ToObject<FakeVector3>(json);

Basically what is happening is the Reflection.CreateGetMethod() was doing the wrong thing for static getters in structs. Here's how I modified it to work, which does deserialize properly:

        internal static GenericGetter CreateGetMethod(Type type, PropertyInfo propertyInfo)
        {
            MethodInfo getMethod = propertyInfo.GetGetMethod();
            if (getMethod == null)
                return null;

            DynamicMethod getter = new DynamicMethod("_cgm", typeof(object), new Type[] { typeof(object) }, type, true);

            ILGenerator il = getter.GetILGenerator();

            if (!type.IsClass) // structs
            {
                var lv = il.DeclareLocal(type);
        if (!getMethod.IsStatic)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Unbox_Any, type);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldloca_S, lv);
            il.EmitCall(OpCodes.Call, getMethod, null);
        }
        else  // call a static method on a struct type
        {
            il.Emit(OpCodes.Call, getMethod);
        }
        if (propertyInfo.PropertyType.IsValueType)
            il.Emit(OpCodes.Box, propertyInfo.PropertyType);
            }
            else
            {
                if (!getMethod.IsStatic)
                {
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
                    il.EmitCall(OpCodes.Callvirt, getMethod, null);
                }
                else
                    il.Emit(OpCodes.Call, getMethod);

                if (propertyInfo.PropertyType.IsValueType)
                    il.Emit(OpCodes.Box, propertyInfo.PropertyType);
            }

            il.Emit(OpCodes.Ret);

            return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
        }
jhughes2112 commented 3 years ago

Since I'm giving you code, here's an updated version of WriteString() that is table driven and significantly faster than what's there. I was profiling today and this was coming up a lot for long documents. It removes the vast majority of the comparisons using a simple lookup table.

In my version, I also removed all the ToLowerInvariant() support, because that creates a lot of allocations whether you enable the write-as-lower feature or not.

Enjoy!

        static private bool[] escapedUnicodeSet = null;
        static private bool[] normalSet = null;
        private void WriteString(string s)
        {
            // Build lookup tables
            if (escapedUnicodeSet==null)
            {
                escapedUnicodeSet = new bool[256];
                normalSet = new bool[256];
                for (int c=0; c<256; c++)
                {
                    escapedUnicodeSet[c] = (c >= ' ' && c < 128 && c != '\"' && c != '\\');
                    normalSet[c] = (c != '\t' && c != '\n' && c != '\r' && c != '\"' && c != '\\' && c != '\0'); // && c != ':' && c!=',')
                }
            }

            _output.Append('\"');

            bool[] setToUse = _useEscapedUnicode ? escapedUnicodeSet : normalSet;  // table-driven removes almost all the comparisons
            int runIndex = -1;
            int l = s.Length;
            for (var index = 0; index < l; ++index)
            {
                var c = s[index];

                if (setToUse[c])
                {
                    if (runIndex == -1)
                        runIndex = index;
                    continue;
                }

                if (runIndex != -1)
                {
                    _output.Append(s, runIndex, index - runIndex);
                    runIndex = -1;
                }

                switch (c)
                {
                    case '\t': _output.Append('\\').Append('t'); break;
                    case '\r': _output.Append('\\').Append('r'); break;
                    case '\n': _output.Append('\\').Append('n'); break;
                    case '"':
                    case '\\': _output.Append('\\'); _output.Append(c); break;
                    case '\0': _output.Append("\\u0000"); break;
                    default:
                        if (_useEscapedUnicode)
                        {
                            _output.Append("\\u");
                            _output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
                        }
                        else
                            _output.Append(c);

                        break;
                }
            }

            if (runIndex != -1)
                _output.Append(s, runIndex, s.Length - runIndex);

            _output.Append('\"');
        }
mgholam commented 3 years ago

Thanks! I will check and let you know.