Skip to content

Fix empty Dictionary when passing JS object to CLR method accepting Dictionary<string, T>#2312

Merged
lahma merged 2 commits into
mainfrom
copilot/fix-empty-dictionary-issue
Mar 6, 2026
Merged

Fix empty Dictionary when passing JS object to CLR method accepting Dictionary<string, T>#2312
lahma merged 2 commits into
mainfrom
copilot/fix-empty-dictionary-issue

Conversation

Copilot AI commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

When a JavaScript object is passed to a CLR method expecting a concrete Dictionary<string, T> parameter, the resulting dictionary is always empty.

Root Cause

DefaultTypeConverter.TryConvert handles source types that are string-keyed generic dictionaries (e.g. ExpandoObject, which is what plain JS objects become via ToObject()) by iterating the target type's members and looking them up in the source. For Dictionary<string, object>, those members are Count, Keys, Values, etc. — none of which exist as keys in the source, so nothing is populated.

This only manifested with concrete dictionary classes. Interface targets like IDictionary<string, object> worked fine because ExpandoObject directly implements that interface, so the earlier type.IsInstanceOfType(value) check short-circuits correctly.

Fix

When both source and target are string-keyed generic dictionaries, iterate the source's keys and populate the target via IDictionary, converting each value to the target's declared value type.

// Before: empty — iterates Dictionary<string,object> members (Count, Keys…) not found in source
public void Method(Dictionary<string, object> values) { /* values is {} */ }

// After: correctly populated
engine.Evaluate("method({ value: 'world' })"); // values = { "value": "world" }

Changes

  • DefaultTypeConverter.TryConvert: When source TypeDescriptor.IsStringKeyedGenericDictionary is true and the target type is also a string-keyed generic dictionary (detected via TypeDescriptor), populate the target dictionary from source keys rather than mapping to target class members. Falls back to the original member-mapping path for non-dictionary targets.
  • InteropTests: Added Test3/Test4 methods and two new tests covering Dictionary<string, object> as a direct parameter and as an optional parameter with a null default.
Original prompt

This section details on the original issue you should resolve

<issue_title>Dictionary<string, object> in c# method always is empty</issue_title>
<issue_description>Hi, Im encountering an issue with Jint. I exposed this class to Jint:

using DragonEngine.Json;

namespace DragonEngine.Assets.Translation.Simple;

[JsonSerializableClass(AddToInstancePath = nameof(Content))]
public class Translatable : IJsonSerializable {
	[JsonSerializableProperty] public string Content { get; set; } = "";

	public Translatable(string content) {
		Content = content;
	}

	public string Translate(Dictionary<string, object>? variables = null) {
		return Tr.T(Content, variables);
	}
	public string T(Dictionary<string, object>? variables = null) => Translate(variables);

	public string TranslateOptional(Dictionary<string, object>? variables = null) {
		return Tr.TO(Content, variables);
	}
	public string TO(Dictionary<string, object>? variables = null) => TranslateOptional(variables);
}

When trying to call T() from Jint, the dictionary is always empty, even if it is correct in js. This seems to be consistent, since I already got the same problem on multiple other occations. When I accept an object, as an arg, and then I run my function that converts everything Jint into clr objects, it works fine.

Js code:

return Translatable.new('test.test2'):T({value = 'world'}

My conversion function:

	public override object? ConvertToClr(object? value) {
		// Handle null
		if (value == null) {
			return null!;
		}

		// Arrays are already correct, but recurse into them
		if (value is object[] array) {
			var result = new object[array.Length];
			for (int i = 0; i < array.Length; i++) {
				result[i] = ConvertToClr(array[i])!;
			}
			return result;
		}

		// JS objects become ExpandoObject
		if (value is ExpandoObject expando) {
			var dict = (IDictionary<string, object>)expando!;

			var result = new Dictionary<string, object>();
			foreach (var pair in dict) {
				result[pair.Key] = ConvertToClr(pair.Value)!;
			}
			return result;
		}

		// JsValue not yet converted
		if (value is JsValue jsValue) {
			return ConvertToClr(jsValue.ToObject()!);
		}

		// Primitive
		return value;
	}

Linux manjaro, net 8.0, c# 12.0, Jint 4.6.0

Btw, it would be nice if there was a built in ConvertToClr function</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

…ictionary<string, T>

Co-authored-by: lahma <171892+lahma@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix empty dictionary issue in Translatable class Fix empty Dictionary when passing JS object to CLR method accepting Dictionary<string, T> Mar 6, 2026
Copilot finished work on behalf of lahma March 6, 2026 10:28
@lahma
lahma marked this pull request as ready for review March 6, 2026 10:32
@lahma
lahma enabled auto-merge (squash) March 6, 2026 10:42
@lahma
lahma merged commit c532e8f into main Mar 6, 2026
7 of 8 checks passed
@lahma
lahma deleted the copilot/fix-empty-dictionary-issue branch March 6, 2026 11:05
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

Successfully merging this pull request may close these issues.

Dictionary<string, object> in c# method always is empty

2 participants