Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem serializing (and maybe deserializing?) dynamic types #42

Open
levicki opened this issue Jul 7, 2024 · 2 comments
Open

Problem serializing (and maybe deserializing?) dynamic types #42

levicki opened this issue Jul 7, 2024 · 2 comments

Comments

@levicki
Copy link

levicki commented Jul 7, 2024

Let's start with simple stuff:

// Example 1
namespace DynamicDictionaryTest
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Dynamic;
	using Tomlet;
	using Tomlet.Attributes;

	public class GameState
	{
		public Dictionary<string, dynamic> GameVars = new Dictionary<string, dynamic>();
	}

	internal class Program
	{
		public static GameState State = new GameState();

		static void Main(string[] args)
		{
			State.GameVars["GAME1_BASE_LEVEL1_AVAILABLE"] = true;
			State.GameVars["GAME2_DLC1_LEVEL2_TREASURE_FOUND"] = 3;
			State.GameVars["GAME3_GLOBAL_PLAYER_FERTILITY"] = 0.98;

			string tomlString = TomletMain.TomlStringFrom(State);
		}
	}
}

This produces the following exception:

System.Reflection.TargetInvocationException
  HResult=0x80131604
  Message=Exception has been thrown by the target of an invocation.
  Source=mscorlib
  StackTrace:
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) in f:\dd\ndp\clr\src\BCL\system\reflection\methodinfo.cs:line 761
   at System.Delegate.DynamicInvokeImpl(Object[] args) in f:\dd\ndp\clr\src\BCL\system\delegate.cs:line 123
   at Tomlet.TomlSerializationMethods.<>c__DisplayClass12_1.<GetSerializer>b__0(Object dict) in /_/Tomlet/TomlSerializationMethods.cs:line 133
   at Tomlet.TomlCompositeSerializer.<>c__DisplayClass0_2.<For>b__8(Object instance) in /_/Tomlet/TomlCompositeSerializer.cs:line 60
   at Tomlet.TomletMain.ValueFrom(Type type, Object t, TomlSerializerOptions options) in /_/Tomlet/TomletMain.cs:line 58
   at Tomlet.TomletMain.DocumentFrom(Type type, Object t, TomlSerializerOptions options) in /_/Tomlet/TomletMain.cs:line 81
   at Tomlet.TomletMain.DocumentFrom[T](T t, TomlSerializerOptions options) in /_/Tomlet/TomletMain.cs:line 67
   at Tomlet.TomletMain.TomlStringFrom[T](T t, TomlSerializerOptions options) in /_/Tomlet/TomletMain.cs:line 85
   at DynamicDictionaryTest.Program.Main(String[] args) in C:\WORK\Projects\C#\ConsoleApp10\ConsoleApp10\Program.cs:line 137

  This exception was originally thrown at this call stack:
    System.Reflection.RtFieldInfo.CheckConsistency(object) in fieldinfo.cs
    System.Reflection.RtFieldInfo.GetValue(object) in fieldinfo.cs
    Tomlet.TomlCompositeSerializer.For.AnonymousMethod__8(object) in TomlCompositeSerializer.cs
    Tomlet.TomlSerializationMethods.RegisterSerializer.__ObjectAcceptingSerializer|0(object) in TomlSerializationMethods.cs
    Tomlet.TomlSerializationMethods.GenericDictionarySerializer<TKey, TValue>(System.Collections.Generic.Dictionary<TKey, TValue>, Tomlet.TomlSerializerOptions) in TomlSerializationMethods.cs

Inner Exception 1:
ArgumentException: Field 'GameVars' defined on type 'DynamicDictionaryTest.GameState' is not a field on the target object which is of type 'System.Boolean'.

If you do not wrap the Dictionary<string, dynamic> into a GameState class but put it as a top object for serialization:

// Example 2
namespace DynamicDictionaryTest
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Dynamic;
	using Tomlet;
	using Tomlet.Attributes;

	internal class Program
	{
		public static Dictionary<string, dynamic> GameVars = new Dictionary<string, dynamic>();

		static void Main(string[] args)
		{
			GameVars["GAME1_BASE_LEVEL1_AVAILABLE"] = true;
			GameVars["GAME2_DLC1_LEVEL2_TREASURE_FOUND"] = 3;
			GameVars["GAME3_GLOBAL_PLAYER_FERTILITY"] = 0.98;

			string tomlString = TomletMain.TomlStringFrom(GameVars);
		}
	}
}

Serialization kind of works (i.e. doesn't crash), but produces nonsensical / useless output:

GAME1_BASE_LEVEL1_AVAILABLE = {  }
GAME2_DLC1_LEVEL2_TREASURE_FOUND = {  }
GAME3_GLOBAL_PLAYER_FERTILITY = {  }

Am I wrong expecting this to produce proper values at the TOML file level outside of any table instead of bunch of empty inline tables?

Also, I expected the first case with GameVars inside of GameState class to produce:

[GameVars]
GAME1_BASE_LEVEL1_AVAILABLE = true
GAME2_DLC1_LEVEL2_TREASURE_FOUND = 3
GAME3_GLOBAL_PLAYER_FERTILITY = 0.98

Hopefully this is easy to fix?

@levicki
Copy link
Author

levicki commented Jul 7, 2024

To clarify, doing serialization using Newtonsoft.JSON produces the following output:

Example 1
image

Example 2
image

@levicki
Copy link
Author

levicki commented Jul 7, 2024

Workaround for the problem (partial, very inelegant, and without error handling):

TomletMain.RegisterMapper<Dictionary<string, dynamic>>(
    s => {
        TomlTable Result = new TomlTable();
        foreach (var kvp in s) {
            TomlValue Value = null;
            TypeCode tc = Type.GetTypeCode(kvp.Value.GetType());
            switch (tc) {
            case TypeCode.Boolean:
                Value = TomlBoolean.ValueOf(kvp.Value);
                break;
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.UInt16:
            case TypeCode.Int32:
            case TypeCode.UInt32:
            case TypeCode.Int64:
            case TypeCode.UInt64:
                Value = new TomlLong(kvp.Value);
                break;
            case TypeCode.Single:
            case TypeCode.Double:
            case TypeCode.Decimal:
                Value = new TomlDouble(kvp.Value);
                break;
            case TypeCode.DateTime:
                Value = new TomlLocalDateTime(kvp.Value);
                break;
            case TypeCode.String:
            case TypeCode.Char:
                Value = new TomlString(kvp.Value);
                break;
            default:
                // TODO: throw proper exception about type unsupported by TOML
                break;
            }
            Result.PutValue(kvp.Key, Value);
        }
        return Result;
    },
    d => {
        Dictionary<string, dynamic> Result = new Dictionary<string, dynamic>();
        TomlDocument doc = d as TomlDocument;
        foreach (var kvp in doc) {
            Result.Add(kvp.Key, kvp.Value);
        }
        return Result;
    }
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant