Skip to content

Commit 48dc58d

Browse files
committed
fix: dictionary numeric string key access (#1)
- Add TryGetDictionaryValue helper to check dictionary keys before treating numeric segments as array indices - Dictionary<string, T> with keys like "1" now correctly returns values when accessed via [1] syntax - Add 21 comprehensive test cases for numeric key scenarios - Bump version to 1.4.1 Fixes #1
1 parent 63c1ea0 commit 48dc58d

3 files changed

Lines changed: 458 additions & 2 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
88

99
<!-- Package Metadata -->
10-
<Version>1.4.0</Version>
10+
<Version>1.4.1</Version>
1111
<Authors>iyulab</Authors>
1212
<Company>Iyulab Corporation</Company>
1313
<Owners>iyulab</Owners>

src/ObjectPath.Tests/ObjectPathTests.cs

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2116,4 +2116,376 @@ public void TryGetValue_WithInvalidBracketKey_ReturnsFalse()
21162116
}
21172117

21182118
#endregion
2119+
2120+
#region Dictionary with Numeric String Keys Tests (GitHub Issue #1)
2121+
2122+
public class DictionaryNumericKeyTests
2123+
{
2124+
[Fact]
2125+
public void GetValue_DictionaryStringString_WithNumericKey_ReturnsValue()
2126+
{
2127+
// Arrange - Exact case from GitHub issue #1
2128+
var dict = new Dictionary<string, string>
2129+
{
2130+
["1"] = "hello world"
2131+
};
2132+
2133+
// Act
2134+
var result = ObjectPath.GetValue(dict, "[1]");
2135+
2136+
// Assert
2137+
Assert.Equal("hello world", result);
2138+
}
2139+
2140+
[Fact]
2141+
public void GetValue_DictionaryStringObject_WithNumericKey_ReturnsValue()
2142+
{
2143+
// Arrange
2144+
var dict = new Dictionary<string, object>
2145+
{
2146+
["0"] = "zero",
2147+
["1"] = "one",
2148+
["2"] = "two"
2149+
};
2150+
2151+
// Act & Assert
2152+
Assert.Equal("zero", ObjectPath.GetValue(dict, "[0]"));
2153+
Assert.Equal("one", ObjectPath.GetValue(dict, "[1]"));
2154+
Assert.Equal("two", ObjectPath.GetValue(dict, "[2]"));
2155+
}
2156+
2157+
[Fact]
2158+
public void GetValue_DictionaryStringInt_WithNumericKey_ReturnsValue()
2159+
{
2160+
// Arrange
2161+
var dict = new Dictionary<string, int>
2162+
{
2163+
["100"] = 999,
2164+
["200"] = 888
2165+
};
2166+
2167+
// Act
2168+
var result = ObjectPath.GetValue(dict, "[100]");
2169+
2170+
// Assert
2171+
Assert.Equal(999, result);
2172+
}
2173+
2174+
[Fact]
2175+
public void GetValue_Hashtable_WithNumericStringKey_ReturnsValue()
2176+
{
2177+
// Arrange
2178+
var hashtable = new System.Collections.Hashtable
2179+
{
2180+
["1"] = "hashtable value",
2181+
["2"] = 42
2182+
};
2183+
2184+
// Act
2185+
var result1 = ObjectPath.GetValue(hashtable, "[1]");
2186+
var result2 = ObjectPath.GetValue(hashtable, "[2]");
2187+
2188+
// Assert
2189+
Assert.Equal("hashtable value", result1);
2190+
Assert.Equal(42, result2);
2191+
}
2192+
2193+
[Fact]
2194+
public void GetValue_NestedDictionary_WithNumericKeys_ReturnsValue()
2195+
{
2196+
// Arrange
2197+
var dict = new Dictionary<string, object>
2198+
{
2199+
["1"] = new Dictionary<string, object>
2200+
{
2201+
["2"] = new Dictionary<string, string>
2202+
{
2203+
["3"] = "nested value"
2204+
}
2205+
}
2206+
};
2207+
2208+
// Act
2209+
var result = ObjectPath.GetValue(dict, "[1][2][3]");
2210+
2211+
// Assert
2212+
Assert.Equal("nested value", result);
2213+
}
2214+
2215+
[Fact]
2216+
public void GetValue_DictionaryWithArrayValue_NumericKeyThenArrayIndex()
2217+
{
2218+
// Arrange - Dictionary with numeric key containing an array
2219+
var dict = new Dictionary<string, object>
2220+
{
2221+
["1"] = new[] { "a", "b", "c" }
2222+
};
2223+
2224+
// Act - Access dictionary key "1", then array index 2
2225+
var result = ObjectPath.GetValue(dict, "[1][2]");
2226+
2227+
// Assert
2228+
Assert.Equal("c", result);
2229+
}
2230+
2231+
[Fact]
2232+
public void GetValue_ArrayContainingDictionaryWithNumericKey_WorksCorrectly()
2233+
{
2234+
// Arrange - Array containing dictionaries with numeric keys
2235+
var array = new object[]
2236+
{
2237+
new Dictionary<string, string> { ["0"] = "dict0-key0" },
2238+
new Dictionary<string, string> { ["1"] = "dict1-key1" }
2239+
};
2240+
2241+
// Act - Access array index 0, then dictionary key "0"
2242+
var result0 = ObjectPath.GetValue(array, "[0][0]");
2243+
var result1 = ObjectPath.GetValue(array, "[1][1]");
2244+
2245+
// Assert
2246+
Assert.Equal("dict0-key0", result0);
2247+
Assert.Equal("dict1-key1", result1);
2248+
}
2249+
2250+
[Fact]
2251+
public void GetValue_DictionaryNumericKey_WithDotNotation_ReturnsNestedValue()
2252+
{
2253+
// Arrange
2254+
var dict = new Dictionary<string, object>
2255+
{
2256+
["1"] = new { Name = "Test", Value = 123 }
2257+
};
2258+
2259+
// Act
2260+
var name = ObjectPath.GetValue(dict, "[1].Name");
2261+
var value = ObjectPath.GetValue(dict, "[1].Value");
2262+
2263+
// Assert
2264+
Assert.Equal("Test", name);
2265+
Assert.Equal(123, value);
2266+
}
2267+
2268+
[Fact]
2269+
public void GetValue_DictionaryNumericKey_CaseInsensitive_NotApplicable()
2270+
{
2271+
// Arrange - Numeric keys are exact match only (case sensitivity doesn't apply)
2272+
var dict = new Dictionary<string, string>
2273+
{
2274+
["1"] = "value"
2275+
};
2276+
2277+
// Act & Assert - Both ignoreCase true and false should work
2278+
Assert.Equal("value", ObjectPath.GetValue(dict, "[1]", ignoreCase: true));
2279+
Assert.Equal("value", ObjectPath.GetValue(dict, "[1]", ignoreCase: false));
2280+
}
2281+
2282+
[Fact]
2283+
public void TryGetValue_DictionaryNumericKey_ReturnsTrue()
2284+
{
2285+
// Arrange
2286+
var dict = new Dictionary<string, string>
2287+
{
2288+
["1"] = "hello"
2289+
};
2290+
2291+
// Act
2292+
var result = ObjectPath.TryGetValue(dict, "[1]", out var value);
2293+
2294+
// Assert
2295+
Assert.True(result);
2296+
Assert.Equal("hello", value);
2297+
}
2298+
2299+
[Fact]
2300+
public void TryGetValue_DictionaryNumericKey_Generic_ReturnsTrue()
2301+
{
2302+
// Arrange
2303+
var dict = new Dictionary<string, int>
2304+
{
2305+
["42"] = 100
2306+
};
2307+
2308+
// Act
2309+
var result = ObjectPath.TryGetValue<int>(dict, "[42]", out var value);
2310+
2311+
// Assert
2312+
Assert.True(result);
2313+
Assert.Equal(100, value);
2314+
}
2315+
2316+
[Fact]
2317+
public void GetValue_DictionaryNumericKey_KeyNotFound_ThrowsException()
2318+
{
2319+
// Arrange
2320+
var dict = new Dictionary<string, string>
2321+
{
2322+
["1"] = "value"
2323+
};
2324+
2325+
// Act & Assert - Key "99" doesn't exist, and there's no array to index
2326+
var ex = Assert.Throws<InvalidObjectPathException>(() => ObjectPath.GetValue(dict, "[99]"));
2327+
Assert.Contains("99", ex.Message);
2328+
}
2329+
2330+
[Fact]
2331+
public void GetValue_ArrayAccess_StillWorksAfterDictionaryFix()
2332+
{
2333+
// Arrange - Ensure regular array access is not broken
2334+
var obj = new
2335+
{
2336+
Items = new[] { "first", "second", "third" }
2337+
};
2338+
2339+
// Act
2340+
var result = ObjectPath.GetValue(obj, "Items[1]");
2341+
2342+
// Assert
2343+
Assert.Equal("second", result);
2344+
}
2345+
2346+
[Fact]
2347+
public void GetValue_ListAccess_StillWorksAfterDictionaryFix()
2348+
{
2349+
// Arrange - Ensure list access is not broken
2350+
var obj = new
2351+
{
2352+
Numbers = new List<int> { 10, 20, 30, 40, 50 }
2353+
};
2354+
2355+
// Act
2356+
var result = ObjectPath.GetValue(obj, "Numbers[3]");
2357+
2358+
// Assert
2359+
Assert.Equal(40, result);
2360+
}
2361+
2362+
[Fact]
2363+
public void GetValue_MixedDictionaryAndArray_ComplexPath()
2364+
{
2365+
// Arrange - Complex structure with both dictionaries and arrays
2366+
var data = new Dictionary<string, object>
2367+
{
2368+
["users"] = new[]
2369+
{
2370+
new Dictionary<string, object>
2371+
{
2372+
["1"] = new { Name = "Alice" }
2373+
},
2374+
new Dictionary<string, object>
2375+
{
2376+
["2"] = new { Name = "Bob" }
2377+
}
2378+
}
2379+
};
2380+
2381+
// Act - users[0] = array index, [1] = dictionary key
2382+
var alice = ObjectPath.GetValue(data, "users[0][1].Name");
2383+
var bob = ObjectPath.GetValue(data, "users[1][2].Name");
2384+
2385+
// Assert
2386+
Assert.Equal("Alice", alice);
2387+
Assert.Equal("Bob", bob);
2388+
}
2389+
2390+
[Fact]
2391+
public void GetValue_DictionaryNumericKey_VsStringLiteralSyntax_SameResult()
2392+
{
2393+
// Arrange
2394+
var dict = new Dictionary<string, string>
2395+
{
2396+
["1"] = "value"
2397+
};
2398+
2399+
// Act - Both syntaxes should work
2400+
var resultBracket = ObjectPath.GetValue(dict, "[1]");
2401+
var resultStringLiteral = ObjectPath.GetValue(dict, "[\"1\"]");
2402+
2403+
// Assert
2404+
Assert.Equal("value", resultBracket);
2405+
Assert.Equal("value", resultStringLiteral);
2406+
Assert.Equal(resultBracket, resultStringLiteral);
2407+
}
2408+
2409+
[Fact]
2410+
public void GetValue_EmptyDictionary_NumericKey_ThrowsException()
2411+
{
2412+
// Arrange
2413+
var dict = new Dictionary<string, string>();
2414+
2415+
// Act & Assert
2416+
Assert.Throws<InvalidObjectPathException>(() => ObjectPath.GetValue(dict, "[0]"));
2417+
}
2418+
2419+
[Fact]
2420+
public void GetValue_DictionaryWithNegativeNumericKey_ReturnsValue()
2421+
{
2422+
// Arrange - Dictionary can have negative number as string key
2423+
var dict = new Dictionary<string, string>
2424+
{
2425+
["-1"] = "negative one"
2426+
};
2427+
2428+
// Act
2429+
var result = ObjectPath.GetValue(dict, "[-1]");
2430+
2431+
// Assert
2432+
Assert.Equal("negative one", result);
2433+
}
2434+
2435+
[Fact]
2436+
public void GetValue_DictionaryWithLeadingZeroKey_ReturnsValue()
2437+
{
2438+
// Arrange - Dictionary key "01" is different from "1"
2439+
var dict = new Dictionary<string, string>
2440+
{
2441+
["01"] = "zero-one",
2442+
["1"] = "one"
2443+
};
2444+
2445+
// Act
2446+
var result01 = ObjectPath.GetValue(dict, "[01]");
2447+
var result1 = ObjectPath.GetValue(dict, "[1]");
2448+
2449+
// Assert - Dictionary lookup uses the exact segment string
2450+
// "[01]" segment is "01" -> matches key "01"
2451+
// "[1]" segment is "1" -> matches key "1"
2452+
Assert.Equal("zero-one", result01);
2453+
Assert.Equal("one", result1);
2454+
}
2455+
2456+
[Fact]
2457+
public void GetValueByPath_ExtensionMethod_DictionaryNumericKey_Works()
2458+
{
2459+
// Arrange
2460+
var dict = new Dictionary<string, string>
2461+
{
2462+
["1"] = "extension test"
2463+
};
2464+
2465+
// Act
2466+
var result = dict.GetValueByPath("[1]");
2467+
2468+
// Assert
2469+
Assert.Equal("extension test", result);
2470+
}
2471+
2472+
[Fact]
2473+
public void TryGetValueByPath_ExtensionMethod_DictionaryNumericKey_Works()
2474+
{
2475+
// Arrange
2476+
var dict = new Dictionary<string, string>
2477+
{
2478+
["1"] = "extension test"
2479+
};
2480+
2481+
// Act
2482+
var success = dict.TryGetValueByPath("[1]", out var result);
2483+
2484+
// Assert
2485+
Assert.True(success);
2486+
Assert.Equal("extension test", result);
2487+
}
2488+
}
2489+
2490+
#endregion
21192491
}

0 commit comments

Comments
 (0)