diff --git a/CustomAutoAdapterMapper.Tests/UnitTest1.cs b/CustomAutoAdapterMapper.Tests/UnitTest1.cs index fb42cb5..2d7e236 100644 --- a/CustomAutoAdapterMapper.Tests/UnitTest1.cs +++ b/CustomAutoAdapterMapper.Tests/UnitTest1.cs @@ -260,4 +260,112 @@ public void TestMapperReturnsCollectionWithMappingConfigurationCorrectWithNested Assert.That(result, Is.EqualTo(destinationCollection)); Assert.That(firstItem.DescriptionVariation, Is.Not.Null); } + + [Test] + public void TestMapperSupportsNestedRootKeyWithDotNotation() + { + var json = @" + { + 'data': { + 'employees': [ + { 'API': 'ServiceA', 'Description': 'First', 'Auth': 'apiKey', 'HTTPS': true, 'Cors': 'yes', 'Link': 'https://a.com', 'Category': 'Test' }, + { 'API': 'ServiceB', 'Description': 'Second', 'Auth': 'none', 'HTTPS': false, 'Cors': 'no', 'Link': 'https://b.com', 'Category': 'Test' } + ] + } + }"; + + var destinationCollection = new List(); + var result = json.MapCollection(destinationCollection, options => + { + options.RootKey = "data.employees"; + }); + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result.First().API, Is.EqualTo("ServiceA")); + } + + [Test] + public void TestMapperSupportsThreeLevelNestedRootKeyWithDotNotation() + { + var json = @" + { + 'response': { + 'data': { + 'employees': [ + { 'API': 'ServiceA', 'Description': 'First', 'Auth': 'apiKey', 'HTTPS': true, 'Cors': 'yes', 'Link': 'https://a.com', 'Category': 'Test' } + ] + } + } + }"; + + var destinationCollection = new List(); + var result = json.MapCollection(destinationCollection, options => + { + options.RootKey = "response.data.employees"; + }); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First().API, Is.EqualTo("ServiceA")); + } + + [Test] + public void TestMapperDoesNotSetMappedPropertyWhenSourceFieldAbsentFromJson() + { + var json = @"{'entries': [{'API': 'ServiceA', 'Description': 'First', 'Auth': 'none', 'HTTPS': true, 'Cors': 'yes', 'Link': 'https://a.com', 'Category': 'Test'}]}"; + + var destinationCollection = new List(); + var result = json.MapCollection(destinationCollection, options => + { + options.RootKey = "entries"; + options.Mappings = new Dictionary + { + { "DescriptionVariation", "NonExistentField" } + }; + }); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First().DescriptionVariation, Is.Null); + } + + [Test] + public void TestSeedKnownCollectionReturnsEarlyWhenNoMatchingRecordFoundInFeed() + { + var json = @"{'entries': [{'API': 'ServiceA', 'Description': 'First', 'Auth': 'apiKey', 'HTTPS': true, 'Cors': 'yes', 'Link': 'https://a.com', 'Category': 'Test'}]}"; + + var destinationCollection = new List { new TestObject { API = "NotInFeed" } }; + var result = json.MapCollection(destinationCollection, options => + { + options.RootKey = "entries"; + options.ItemKey = "API"; + options.Mappings = new Dictionary + { + { "Description", "Description" } + }; + }); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First().API, Is.EqualTo("NotInFeed")); + Assert.That(result.First().Description, Is.Null); + } + + [Test] + public void TestSeedKnownCollectionUsesRemappedJsonKeyWhenItemKeyIsMapped() + { + var json = @"{'entries': [{'api_name': 'ServiceA', 'Description': 'First', 'Auth': 'apiKey', 'HTTPS': true, 'Cors': 'yes', 'Link': 'https://a.com', 'Category': 'Test'}]}"; + + var destinationCollection = new List { new TestObject { API = "ServiceA" } }; + var result = json.MapCollection(destinationCollection, options => + { + options.RootKey = "entries"; + options.ItemKey = "API"; + options.Mappings = new Dictionary + { + { "API", "api_name" }, + { "Description", "Description" } + }; + }); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First().Description, Is.EqualTo("First")); + } } \ No newline at end of file diff --git a/CustomAutoAdapterMapper/CustomAutoAdapterMapper.csproj b/CustomAutoAdapterMapper/CustomAutoAdapterMapper.csproj index 1d141a4..b1a91cf 100644 --- a/CustomAutoAdapterMapper/CustomAutoAdapterMapper.csproj +++ b/CustomAutoAdapterMapper/CustomAutoAdapterMapper.csproj @@ -9,8 +9,8 @@ https://github.com/teghoz/CustomAutoAdapterMapper README.md license.txt - 1.0.7 - v1.0.7: Fixed collection duplication issue when using mapped ItemKey in update mode. + 1.0.8 + v1.0.8: Fixed ItemKey mapping logic to correctly identify update vs create mode, preventing duplicate records in collections. https://github.com/teghoz/CustomAutoAdapterMapper diff --git a/CustomAutoAdapterMapper/Mapper.cs b/CustomAutoAdapterMapper/Mapper.cs index 429ba6b..e53be92 100644 --- a/CustomAutoAdapterMapper/Mapper.cs +++ b/CustomAutoAdapterMapper/Mapper.cs @@ -50,21 +50,13 @@ private static bool JsonStringIsValid(string jsonString) private static bool MapperShouldIterateThroughEntireIncomingCollection(List destination, Option options) { + // First check if the ItemKey property exists and has values in destination var itemKeyIdentifierIsEmpty = destination != null && !string.IsNullOrEmpty(options.ItemKey) && destination.All(x => string.IsNullOrEmpty(x.GetType()?.GetProperty(options.ItemKey) ?.GetValue(x)?.ToString() ?? string.Empty)); - if (itemKeyIdentifierIsEmpty && options.Mappings.ContainsKey(options.ItemKey)) - { - var mappedPropertyName = options.Mappings[options.ItemKey]; - itemKeyIdentifierIsEmpty = destination.All(x => - string.IsNullOrEmpty(x.GetType()?.GetProperty(mappedPropertyName) - ?.GetValue(x)?.ToString() ?? string.Empty)); - options.ItemKey = mappedPropertyName; - } - var result = destination == null || destination.Count == 0 || itemKeyIdentifierIsEmpty; return result; } @@ -108,14 +100,24 @@ private static void SeedKnownCollectionOfItem(List entries, T entry, if (string.IsNullOrEmpty(mapperOptions.ItemKey)) throw new ItemKeyOptionNullException("Item Key Option Not Set!!!!!"); - var keyItemExist = entry.GetType().GetProperty(mapperOptions.ItemKey); + // Determine the JSON key to search for (original or mapped) + var jsonItemKey = mapperOptions.ItemKey; + var objectItemKey = mapperOptions.ItemKey; + + // If ItemKey is mapped, use the mapping for JSON lookup but keep original for object property + if (mapperOptions.Mappings.ContainsKey(mapperOptions.ItemKey)) + { + jsonItemKey = mapperOptions.Mappings[mapperOptions.ItemKey]; + } + + var keyItemExist = entry.GetType().GetProperty(objectItemKey); if (keyItemExist == null) return; var propertyKeyItemValue = - entry.GetType()?.GetProperty(mapperOptions.ItemKey)?.GetValue(entry)?.ToString() ?? null; + entry.GetType()?.GetProperty(objectItemKey)?.GetValue(entry)?.ToString() ?? null; if (propertyKeyItemValue == null) return; var incomingRecord = entries - .Where(e => e.Values().Any(ee => ee.ToString() == propertyKeyItemValue)) + .Where(e => e.SelectToken(jsonItemKey)?.ToString() == propertyKeyItemValue) .FirstOrDefault(); if (incomingRecord == null) return; @@ -146,7 +148,7 @@ private static JToken Validate(string jsonResponse, JObject jsonObject, Option o jsonObject = JObject.Parse(jsonResponse); - var rootProperty = jsonObject[option.RootKey]; + var rootProperty = jsonObject.SelectToken(option.RootKey); if (rootProperty == null) throw new RootKeyPropertyNullException("Root Property Does Not Exist In Object!!!!!");