Skip to content

Commit 6064bb0

Browse files
committed
fix: TryGetValue logic, JSON number types, and type conversion
v1.3.0 - Bug fixes and improvements Fixed: - TryGetValue now returns true for valid paths even when value is null - Exception messages include full path for all error cases - JSON array bounds checking throws proper exception Added: - Enhanced type conversion: Enum, Guid, Nullable<T> support - Smarter JSON numbers: int/long for integers, double for floats - XML documentation for ToExpando method - 13 new test cases (108 total)
1 parent 196d0a0 commit 6064bb0

6 files changed

Lines changed: 310 additions & 16 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,4 +364,5 @@ FodyWeavers.xsd
364364
/scripts/nuget-publish.ps1
365365

366366
# Local
367-
.serena/
367+
.serena/
368+
CLAUDE.md

docs/CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.3.0] - 2025-01-20
9+
10+
### Added
11+
12+
- **Enhanced type conversion**: Support for `Enum`, `Guid`, and `Nullable<T>` types in `GetValue<T>()`
13+
- **Smarter JSON number handling**: JSON integers return `int`/`long`, floats return `double` (previously all returned `decimal`)
14+
- **XML documentation**: Added comprehensive XML docs to `ToExpando()` method
15+
- **New tests**: 13 additional test cases for bug fixes and improvements (108 total)
16+
17+
### Fixed
18+
19+
- **TryGetValue return logic**: Now correctly returns `true` for valid paths even when the value is `null`
20+
- **Exception message consistency**: All exceptions now include the full path for easier debugging
21+
- **JSON array bounds checking**: Proper exception thrown for out-of-bounds array access in JSON
22+
823
## [1.2.0] - 2024-11-27
924

1025
### Added

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.2.0</Version>
10+
<Version>1.3.0</Version>
1111
<Authors>iyulab</Authors>
1212
<Company>Iyulab Corporation</Company>
1313
<Owners>iyulab</Owners>

src/ObjectPath.Tests/ObjectPathTests.cs

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ public void GetValue_ReturnsCorrectValue_ForJsonElement()
964964

965965
// Assert
966966
Assert.Equal("John", name);
967-
Assert.Equal(30m, age);
967+
Assert.Equal(30, age); // Changed from 30m to 30 (int) - JSON integers now return int
968968
Assert.Equal("New York", city);
969969
Assert.Equal("123 Main St", street);
970970
Assert.Equal("home", phoneType1);
@@ -1733,4 +1733,202 @@ private class CircularRefClass
17331733
}
17341734

17351735
#endregion
1736+
1737+
#region Phase 4: Bug Fixes and Improvements Tests
1738+
1739+
public class BugFixTests
1740+
{
1741+
[Fact]
1742+
public void TryGetValue_ReturnsTrue_WhenPathReturnsNullValue()
1743+
{
1744+
// Arrange - object with a null property value
1745+
var obj = new { Address = (string?)null };
1746+
1747+
// Act
1748+
var result = ObjectPath.TryGetValue(obj, "Address", out var value);
1749+
1750+
// Assert - should return true because path is valid, even though value is null
1751+
Assert.True(result);
1752+
Assert.Null(value);
1753+
}
1754+
1755+
[Fact]
1756+
public void TryGetValueT_ReturnsTrue_WhenPathReturnsNullValue()
1757+
{
1758+
// Arrange
1759+
var obj = new { Name = (string?)null };
1760+
1761+
// Act
1762+
var result = ObjectPath.TryGetValue<string>(obj, "Name", out var value);
1763+
1764+
// Assert
1765+
Assert.True(result);
1766+
Assert.Null(value);
1767+
}
1768+
}
1769+
1770+
public class JsonNumberTypeTests
1771+
{
1772+
[Fact]
1773+
public void GetValue_ReturnsInt_ForJsonInteger()
1774+
{
1775+
// Arrange
1776+
var json = """{"value": 42}""";
1777+
var doc = JsonDocument.Parse(json);
1778+
1779+
// Act
1780+
var value = ObjectPath.GetValue(doc.RootElement, "value");
1781+
1782+
// Assert
1783+
Assert.IsType<int>(value);
1784+
Assert.Equal(42, value);
1785+
}
1786+
1787+
[Fact]
1788+
public void GetValue_ReturnsLong_ForLargeJsonInteger()
1789+
{
1790+
// Arrange
1791+
var json = """{"value": 9223372036854775807}""";
1792+
var doc = JsonDocument.Parse(json);
1793+
1794+
// Act
1795+
var value = ObjectPath.GetValue(doc.RootElement, "value");
1796+
1797+
// Assert
1798+
Assert.IsType<long>(value);
1799+
Assert.Equal(9223372036854775807L, value);
1800+
}
1801+
1802+
[Fact]
1803+
public void GetValue_ReturnsDouble_ForJsonFloat()
1804+
{
1805+
// Arrange
1806+
var json = """{"value": 3.14159}""";
1807+
var doc = JsonDocument.Parse(json);
1808+
1809+
// Act
1810+
var value = ObjectPath.GetValue(doc.RootElement, "value");
1811+
1812+
// Assert
1813+
Assert.IsType<double>(value);
1814+
Assert.Equal(3.14159, value);
1815+
}
1816+
}
1817+
1818+
public class EnhancedTypeConversionTests
1819+
{
1820+
[Fact]
1821+
public void GetValueT_ConvertsStringToEnum()
1822+
{
1823+
// Arrange
1824+
var obj = new { Day = "Monday" };
1825+
1826+
// Act
1827+
var day = ObjectPath.GetValue<DayOfWeek>(obj, "Day");
1828+
1829+
// Assert
1830+
Assert.Equal(DayOfWeek.Monday, day);
1831+
}
1832+
1833+
[Fact]
1834+
public void GetValueT_ConvertsIntToEnum()
1835+
{
1836+
// Arrange
1837+
var obj = new { Day = 1 }; // Monday = 1
1838+
1839+
// Act
1840+
var day = ObjectPath.GetValue<DayOfWeek>(obj, "Day");
1841+
1842+
// Assert
1843+
Assert.Equal(DayOfWeek.Monday, day);
1844+
}
1845+
1846+
[Fact]
1847+
public void GetValueT_ConvertsStringToGuid()
1848+
{
1849+
// Arrange
1850+
var guidStr = "12345678-1234-1234-1234-123456789012";
1851+
var obj = new { Id = guidStr };
1852+
1853+
// Act
1854+
var guid = ObjectPath.GetValue<Guid>(obj, "Id");
1855+
1856+
// Assert
1857+
Assert.Equal(Guid.Parse(guidStr), guid);
1858+
}
1859+
1860+
[Fact]
1861+
public void GetValueT_ConvertsToNullableInt()
1862+
{
1863+
// Arrange
1864+
var obj = new { Value = 42 };
1865+
1866+
// Act
1867+
var value = ObjectPath.GetValue<int?>(obj, "Value");
1868+
1869+
// Assert
1870+
Assert.Equal(42, value);
1871+
}
1872+
1873+
[Fact]
1874+
public void TryGetValueT_ReturnsFalse_ForInvalidEnumString()
1875+
{
1876+
// Arrange
1877+
var obj = new { Day = "InvalidDay" };
1878+
1879+
// Act
1880+
var result = ObjectPath.TryGetValue<DayOfWeek>(obj, "Day", out var value);
1881+
1882+
// Assert
1883+
Assert.False(result);
1884+
Assert.Equal(default, value);
1885+
}
1886+
1887+
[Fact]
1888+
public void TryGetValueT_ReturnsFalse_ForInvalidGuidString()
1889+
{
1890+
// Arrange
1891+
var obj = new { Id = "not-a-guid" };
1892+
1893+
// Act
1894+
var result = ObjectPath.TryGetValue<Guid>(obj, "Id", out var value);
1895+
1896+
// Assert
1897+
Assert.False(result);
1898+
Assert.Equal(default, value);
1899+
}
1900+
}
1901+
1902+
public class ExceptionMessageTests
1903+
{
1904+
[Fact]
1905+
public void InvalidPath_ExceptionIncludesFullPath_ForJsonElement()
1906+
{
1907+
// Arrange
1908+
var json = """{"user": {"name": "John"}}""";
1909+
var doc = JsonDocument.Parse(json);
1910+
1911+
// Act & Assert
1912+
var ex = Assert.Throws<InvalidObjectPathException>(() =>
1913+
ObjectPath.GetValue(doc.RootElement, "user.email"));
1914+
1915+
Assert.Contains("user.email", ex.Message);
1916+
}
1917+
1918+
[Fact]
1919+
public void InvalidArrayIndex_ExceptionIncludesFullPath_ForJsonElement()
1920+
{
1921+
// Arrange
1922+
var json = """{"items": [1, 2, 3]}""";
1923+
var doc = JsonDocument.Parse(json);
1924+
1925+
// Act & Assert
1926+
var ex = Assert.Throws<InvalidObjectPathException>(() =>
1927+
ObjectPath.GetValue(doc.RootElement, "items[10]"));
1928+
1929+
Assert.Contains("items[10]", ex.Message);
1930+
}
1931+
}
1932+
1933+
#endregion
17361934
}

src/ObjectPath/DictionaryExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ namespace ObjectPathLibrary
44
{
55
public static class DictionaryExtensions
66
{
7+
/// <summary>
8+
/// Converts a dictionary to an ExpandoObject, enabling dynamic property access.
9+
/// Nested dictionaries are recursively converted to ExpandoObjects.
10+
/// </summary>
11+
/// <param name="dictionary">The source dictionary to convert.</param>
12+
/// <returns>An ExpandoObject with properties matching the dictionary keys.</returns>
13+
/// <example>
14+
/// <code>
15+
/// var dict = new Dictionary&lt;string, object?&gt; { ["Name"] = "John", ["Age"] = 30 };
16+
/// dynamic expando = dict.ToExpando();
17+
/// Console.WriteLine(expando.Name); // "John"
18+
/// </code>
19+
/// </example>
720
public static dynamic ToExpando(this IDictionary<string, object?> dictionary)
821
{
922
var expando = new ExpandoObject();

0 commit comments

Comments
 (0)