Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions CustomAutoAdapterMapper.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestObject>();
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<TestObject>();
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<TestObjectWithVariation>();
var result = json.MapCollection(destinationCollection, options =>
{
options.RootKey = "entries";
options.Mappings = new Dictionary<string, string>
{
{ "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<TestObject> { new TestObject { API = "NotInFeed" } };
var result = json.MapCollection(destinationCollection, options =>
{
options.RootKey = "entries";
options.ItemKey = "API";
options.Mappings = new Dictionary<string, string>
{
{ "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<TestObject> { new TestObject { API = "ServiceA" } };
var result = json.MapCollection(destinationCollection, options =>
{
options.RootKey = "entries";
options.ItemKey = "API";
options.Mappings = new Dictionary<string, string>
{
{ "API", "api_name" },
{ "Description", "Description" }
};
});

Assert.That(result.Count, Is.EqualTo(1));
Assert.That(result.First().Description, Is.EqualTo("First"));
}
}
4 changes: 2 additions & 2 deletions CustomAutoAdapterMapper/CustomAutoAdapterMapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<RepositoryUrl>https://github.com/teghoz/CustomAutoAdapterMapper</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>license.txt</PackageLicenseFile>
<Version>1.0.7</Version>
<PackageReleaseNotes>v1.0.7: Fixed collection duplication issue when using mapped ItemKey in update mode.</PackageReleaseNotes>
<Version>1.0.8</Version>
<PackageReleaseNotes>v1.0.8: Fixed ItemKey mapping logic to correctly identify update vs create mode, preventing duplicate records in collections.</PackageReleaseNotes>
<PackageProjectUrl>https://github.com/teghoz/CustomAutoAdapterMapper</PackageProjectUrl>
</PropertyGroup>

Expand Down
28 changes: 15 additions & 13 deletions CustomAutoAdapterMapper/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,13 @@ private static bool JsonStringIsValid(string jsonString)

private static bool MapperShouldIterateThroughEntireIncomingCollection<T>(List<T> 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;
}
Expand Down Expand Up @@ -108,14 +100,24 @@ private static void SeedKnownCollectionOfItem<T>(List<JToken> 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;
Expand Down Expand Up @@ -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!!!!!");
Expand Down