diff --git a/src/PowderCoating.Application/DTOs/Columbia/ColumbiaImageJsonConverter.cs b/src/PowderCoating.Application/DTOs/Columbia/ColumbiaImageJsonConverter.cs
new file mode 100644
index 0000000..1ee746d
--- /dev/null
+++ b/src/PowderCoating.Application/DTOs/Columbia/ColumbiaImageJsonConverter.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace PowderCoating.Application.DTOs.Columbia;
+
+///
+/// Tolerant converter for Columbia image fields. WordPress/WooCommerce returns an object
+/// ({id,src,name,alt}) when an image is present, but an empty array ([]) — or
+/// sometimes false/empty string — when it is not. A single
+/// can't bind to those non-object forms, so this converter reads the object when present and
+/// yields null for anything else (consuming the token so deserialization continues).
+///
+public class ColumbiaImageJsonConverter : JsonConverter
+{
+ public override ColumbiaImage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.StartObject:
+ using (var doc = JsonDocument.ParseValue(ref reader))
+ {
+ var el = doc.RootElement;
+ return new ColumbiaImage
+ {
+ Id = el.TryGetProperty("id", out var id) && id.TryGetInt32(out var i) ? i : 0,
+ Src = GetString(el, "src"),
+ Name = GetString(el, "name"),
+ Alt = GetString(el, "alt"),
+ };
+ }
+
+ case JsonTokenType.StartArray:
+ reader.Skip(); // empty/non-empty array form means "no image"
+ return null;
+
+ default:
+ // Primitive (false / "" / null / number): nothing to consume further.
+ return null;
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, ColumbiaImage? value, JsonSerializerOptions options)
+ => throw new NotSupportedException("Columbia image fields are read-only.");
+
+ private static string GetString(JsonElement el, string property) =>
+ el.TryGetProperty(property, out var v) && v.ValueKind == JsonValueKind.String
+ ? v.GetString() ?? string.Empty
+ : string.Empty;
+}
diff --git a/src/PowderCoating.Application/DTOs/Columbia/ColumbiaProductDtos.cs b/src/PowderCoating.Application/DTOs/Columbia/ColumbiaProductDtos.cs
index 10e8c75..7612e72 100644
--- a/src/PowderCoating.Application/DTOs/Columbia/ColumbiaProductDtos.cs
+++ b/src/PowderCoating.Application/DTOs/Columbia/ColumbiaProductDtos.cs
@@ -89,7 +89,6 @@ public class ColumbiaProduct
public string ShortDescription { get; set; } = string.Empty;
public ColumbiaImage? FeaturedImage { get; set; }
- public List GalleryImages { get; set; } = new();
// ── Taxonomy (arrays of {name}) — used at import to derive manufacturer/category, not stored raw ──
public List Categories { get; set; } = new();
diff --git a/src/PowderCoating.Infrastructure/Services/ColumbiaCoatingsApiClient.cs b/src/PowderCoating.Infrastructure/Services/ColumbiaCoatingsApiClient.cs
index 3ea51b7..36c317c 100644
--- a/src/PowderCoating.Infrastructure/Services/ColumbiaCoatingsApiClient.cs
+++ b/src/PowderCoating.Infrastructure/Services/ColumbiaCoatingsApiClient.cs
@@ -32,6 +32,7 @@ public class ColumbiaCoatingsApiClient : IColumbiaCoatingsApiClient
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true,
+ Converters = { new ColumbiaImageJsonConverter() },
};
public ColumbiaCoatingsApiClient(
diff --git a/tests/PowderCoating.UnitTests/ColumbiaCatalogMapperTests.cs b/tests/PowderCoating.UnitTests/ColumbiaCatalogMapperTests.cs
index b04da4b..7bf96ad 100644
--- a/tests/PowderCoating.UnitTests/ColumbiaCatalogMapperTests.cs
+++ b/tests/PowderCoating.UnitTests/ColumbiaCatalogMapperTests.cs
@@ -230,6 +230,37 @@ public class ColumbiaCatalogMapperTests
// ── End-to-end mapping invariants ─────────────────────────────────────
+ // ── Tolerant image deserialization (WordPress returns [] / false when empty) ──
+
+ private static readonly JsonSerializerOptions ClientJsonOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
+ PropertyNameCaseInsensitive = true,
+ Converters = { new ColumbiaImageJsonConverter() },
+ };
+
+ [Fact]
+ public void Deserialize_FeaturedImageEmptyArray_YieldsNullImage()
+ {
+ // WordPress returns featured_image: [] for products with no image.
+ var json = """{ "sku": "X1", "name": "No Image", "featured_image": [] }""";
+ var product = JsonSerializer.Deserialize(json, ClientJsonOptions);
+
+ Assert.NotNull(product);
+ Assert.Null(product!.FeaturedImage);
+ Assert.Null(ColumbiaCatalogMapper.Map(product).ImageUrl);
+ }
+
+ [Fact]
+ public void Deserialize_FeaturedImageObject_ParsesSrc()
+ {
+ var json = """{ "sku": "X1", "name": "Has Image", "featured_image": { "id": 5, "src": "https://x/img.png", "name": "i", "alt": "" } }""";
+ var product = JsonSerializer.Deserialize(json, ClientJsonOptions);
+
+ Assert.Equal("https://x/img.png", product!.FeaturedImage!.Src);
+ Assert.Equal("https://x/img.png", ColumbiaCatalogMapper.Map(product).ImageUrl);
+ }
+
[Fact]
public void Map_AlwaysStampsSource_AndLeavesEnrichmentFieldsNull()
{