title: Entity Framework Core - ValueConverter with ValueComparer to convert Enum to Class not working
body: <p>I'm trying to send this Json to my API with <a href="https://www.postman.com/" rel="nofollow noreferrer">Postman</a>:</p>
  <pre><code>{
      &quot;name&quot;: &quot;yummy food&quot;,
      &quot;brand&quot;: &quot;brand&quot;,
      &quot;tags&quot;: [
          &quot;1&quot;,
          &quot;2&quot;
      ]
  }
  </code></pre>
  <p>But I'm getting this error:</p>
  <pre><code>{
      &quot;type&quot;: &quot;https://tools.ietf.org/html/rfc7231#section-6.5.1&quot;,
      &quot;title&quot;: &quot;One or more validation errors occurred.&quot;,
      &quot;status&quot;: 400,
      &quot;traceId&quot;: &quot;00-b7a6042817b5124294f5c6d2f6169f05-70d797d0744bfe40-00&quot;,
      &quot;errors&quot;: {
          &quot;$.tags[0]&quot;: [
              &quot;The JSON value could not be converted to GroceryItemTag. Path: $.tags[0] | LineNumber: 4 | BytePositionInLine: 11.&quot;
          ]
      }
  }
  </code></pre>
  <p>The GroceryItemTag field (&quot;tags&quot;) in post, is an enum but using a lookup table to be a <code>GroceryItemTag</code> object with enum id, name, and iconCodePoint fields.</p>
  <p>The postman request:</p>
  <p><a href="https://i.stack.imgur.com/Mra6L.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Mra6L.png" alt="enter image description here" /></a></p>
  <p>Here is my entity framework core model:</p>
  <p>GroceryItem:</p>
  <pre><code>using System.Collections.Generic;
  
  namespace Vepo.Domain
  {
      public class GroceryItem : VeganItem&lt;GroceryItem, GroceryItemTagEnum, GroceryItemTag&gt;
      {
          public string Name {get; set;}
          public string Brand {get; set;}
          public string Description {get; set;}
          public string Image {get; set;}
          public virtual ICollection&lt;GroceryItemGroceryStore&gt; GroceryItemGroceryStores { get; set; }
  
      }
  }
  </code></pre>
  <p>The base class of GroceryItem (take note of virtual <code>Tags</code> with <code>TagIds</code>):</p>
  <pre><code>using System.Collections.Generic;
  
  namespace Vepo.Domain
  {
      public abstract class VeganItem&lt;VeganItemType, VeganItemTagEnumType, VeganItemTagType&gt;
      {
          public int Id { get; set; }
          public int IsNotVeganCount { get; set; }
          public int IsVeganCount { get; set; }
          public int RatingsCount { get; set; }
          public int Rating { get; set; }
          public List&lt;VeganItemTagEnumType&gt; TagIds { get; set; }
          public virtual List&lt;VeganItemTagType&gt; Tags { get; set; }
  
          public List&lt;Establishment&lt;VeganItemType&gt;&gt; Establishments { get; set; }
          public int CurrentRevisionId { get; set; }
  
      }
  }
  </code></pre>
  <p>GroceryItemTagEnum:</p>
  <pre><code>public enum GroceryItemTagEnum
  {
    BabyAndChild = 1,
    Baking,
    Bathroom,
    BeerAndWine,
    Condiments,
    Confectionary,  
    Cooking,
    Dessert,
    Drinks,
    FauxDairy,
    FauxMeat,
    FauxSeafood,
    FridgeAndDeli,
    Frozen,
    HealthFood,
    HouseHold,
    Other,
    Pantry,
    Pet,
  }     
  </code></pre>
  <p>GroceryItemTag class for lookup table:</p>
  <pre><code>public class GroceryItemTag
  {
      public GroceryItemTagEnum Id { get; set; }
      public int IconCodePoint {get; set;}
      public string Name { get; set; }
  }
  </code></pre>
  <p>The controller, take note of <code>PostGroceryItem</code>:</p>
  <pre><code>using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading.Tasks;
  using Microsoft.AspNetCore.Http;
  using Microsoft.AspNetCore.Mvc;
  using Microsoft.EntityFrameworkCore;
  using Vepo.Domain;
  
  namespace Vepo.Controllers
  {
      [Route(&quot;api/[controller]&quot;)]
      [ApiController]
      public class GroceryItemsController : ControllerBase
      {
          private readonly VepoContext _context;
  
          public GroceryItemsController(VepoContext context)
          {
              _context = context;
          }
  
          // GET: api/GroceryItems
          [HttpGet]
          public async Task&lt;ActionResult&lt;IEnumerable&lt;GroceryItem&gt;&gt;&gt; GetGroceryItems()
          {
              return await _context.GroceryItems.ToListAsync();
          }
  
          // GET: api/GroceryItems/5
          [HttpGet(&quot;{id}&quot;)]
          public async Task&lt;ActionResult&lt;GroceryItem&gt;&gt; GetGroceryItem(int id)
          {
              var groceryItem = await _context.GroceryItems.FindAsync(id);
  
              if (groceryItem == null)
              {
                  return NotFound();
              }
  
              return groceryItem;
          }
  
          // PUT: api/GroceryItems/5
          // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
          [HttpPut(&quot;{id}&quot;)]
          public async Task&lt;IActionResult&gt; PutGroceryItem(int id, GroceryItem groceryItem)
          {
              if (id != groceryItem.Id)
              {
                  return BadRequest();
              }
  
              _context.Entry(groceryItem).State = EntityState.Modified;
  
              try
              {
                  await _context.SaveChangesAsync();
              }
              catch (DbUpdateConcurrencyException)
              {
                  if (!GroceryItemExists(id))
                  {
                      return NotFound();
                  }
                  else
                  {
                      throw;
                  }
              }
  
              return NoContent();
          }
  
          // POST: api/GroceryItems
          // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
          [HttpPost]
          public async Task&lt;ActionResult&lt;GroceryItem&gt;&gt; PostGroceryItem(GroceryItem groceryItem)
          {
              _context.GroceryItems.Add(groceryItem);
              await _context.SaveChangesAsync();
  
              return CreatedAtAction(&quot;GetGroceryItem&quot;, new { id = groceryItem.Id }, groceryItem);
          }
  
          // DELETE: api/GroceryItems/5
          [HttpDelete(&quot;{id}&quot;)]
          public async Task&lt;IActionResult&gt; DeleteGroceryItem(int id)
          {
              var groceryItem = await _context.GroceryItems.FindAsync(id);
              if (groceryItem == null)
              {
                  return NotFound();
              }
  
              _context.GroceryItems.Remove(groceryItem);
              await _context.SaveChangesAsync();
  
              return NoContent();
          }
  
          private bool GroceryItemExists(int id)
          {
              return _context.GroceryItems.Any(e =&gt; e.Id == id);
          }
      }
  }
  </code></pre>
  <p>My database context which seeds the GroceryItemTag lookup table:</p>
  <pre><code>using Microsoft.EntityFrameworkCore;
  
  namespace Vepo.Domain
  {
      public class VepoContext : DbContext
      {
          public VepoContext(DbContextOptions&lt;VepoContext&gt; options)
              : base(options)
          {
          }
  
          protected override void OnModelCreating(ModelBuilder builder)
          {
              builder.Entity&lt;GroceryItemGroceryStore&gt;().HasKey(table =&gt; new
              {
                  table.GroceryItemId,
                  table.GroceryStoreId
              });
  
              builder.Entity&lt;MenuItemRestaurant&gt;().HasKey(table =&gt; new
              {
                  table.MenuItemId,
                  table.RestaurantId
              });
  
              builder.Entity&lt;GroceryItemTag&gt;()
              .Property(tag =&gt; tag.Id)
              .ValueGeneratedNever();
  
              builder.Entity&lt;MenuItemTag&gt;()
              .Property(tag =&gt; tag.Id)
              .ValueGeneratedNever();
  
              builder.Entity&lt;GroceryItemTag&gt;().HasData(
                  new GroceryItemTag[] {
                  new GroceryItemTag {
                      Name = &quot;Baby &amp; Child&quot;,
                      Id = GroceryItemTagEnum.BabyAndChild,
                      IconCodePoint = 0xf77c
                  },
                  new GroceryItemTag {
                      Name = &quot;Baking&quot;,
                      Id = GroceryItemTagEnum.Baking,
                      IconCodePoint = 0xf563
                  },
                  new GroceryItemTag {
                      Name = &quot;Beer &amp; Wine&quot;,
                      Id = GroceryItemTagEnum.BeerAndWine,
                      IconCodePoint = 0xf4e3
                  },
                  new GroceryItemTag {
                      Name = &quot;Condiments&quot;,
                      Id = GroceryItemTagEnum.Condiments,
                      IconCodePoint = 0xf72f
                  },
                  new GroceryItemTag {
                      Name = &quot;Confectionary&quot;,
                      Id = GroceryItemTagEnum.Confectionary,
                      IconCodePoint = 0xf819
                  },
                  new GroceryItemTag {
                      Name = &quot;Cooking&quot;,
                      Id = GroceryItemTagEnum.Cooking,
                      IconCodePoint = 0xe01d
                  },
                  new GroceryItemTag {
                      Name = &quot;Dessert&quot;,
                      Id = GroceryItemTagEnum.Dessert,
                      IconCodePoint = 0xf810
                  },
                  new GroceryItemTag {
                      Name = &quot;Drinks&quot;,
                      Id = GroceryItemTagEnum.Drinks,
                      IconCodePoint = 0xf804
                  },
                  new GroceryItemTag {
                      Name = &quot;Faux Meat&quot;,
                      Id = GroceryItemTagEnum.FauxMeat,
                      IconCodePoint = 0xf814
                  },
                  new GroceryItemTag {
                      Name = &quot;Faux Dairy&quot;,
                      Id = GroceryItemTagEnum.FauxDairy,
                      IconCodePoint = 0xf7f0
                  },
                  new GroceryItemTag {
                      Name = &quot;Faux Seafood&quot;,
                      Id = GroceryItemTagEnum.FauxSeafood,
                      IconCodePoint = 0xf7fe
                  },
                  new GroceryItemTag {
                      Name = &quot;Fridge &amp; Deli&quot;,
                      Id = GroceryItemTagEnum.FridgeAndDeli,
                      IconCodePoint = 0xe026
                  },
                  new GroceryItemTag {
                      Name = &quot;Frozen&quot;,
                      Id = GroceryItemTagEnum.Frozen,
                      IconCodePoint = 0xf7ad
                  },
                  new GroceryItemTag {
                      Name = &quot;Bathroom&quot;,
                      Id = GroceryItemTagEnum.Bathroom,
                      IconCodePoint = 0xe06b
                  },
                  new GroceryItemTag {
                      Name = &quot;Health Food&quot;,
                      Id = GroceryItemTagEnum.HealthFood,
                      IconCodePoint = 0xf787
                  },
                  new GroceryItemTag {
                      Name = &quot;Household&quot;,
                      Id = GroceryItemTagEnum.HouseHold,
                      IconCodePoint = 0xf898
                  },
                  new GroceryItemTag {
                      Name = &quot;Pantry&quot;,
                      Id = GroceryItemTagEnum.Pantry,
                      IconCodePoint = 0xf7eb
                  },
                  new GroceryItemTag {
                      Name = &quot;Pet&quot;,
                      Id = GroceryItemTagEnum.Pet,
                      IconCodePoint = 0xf6d3
                  },
                  new GroceryItemTag {
                      Name = &quot;Other&quot;,
                      Id = GroceryItemTagEnum.Other,
                      IconCodePoint = 0xf39b
                  }});
  
  
          builder.Entity&lt;GroceryItem&gt;()
          .Property(e =&gt; e.Tags)
          .HasConversion(
              v =&gt; JsonSerializer.Serialize(v, null),
              v =&gt; JsonSerializer.Deserialize&lt;List&lt;GroceryItemTag&gt;&gt;(v, null),
              new ValueComparer&lt;IList&lt;GroceryItemTag&gt;&gt;(
                  (c1, c2) =&gt; c1.SequenceEqual(c2),
                  c =&gt; c.Aggregate(0, (a, v) =&gt; HashCode.Combine(a, v.GetHashCode())),
                  c =&gt; (IList&lt;GroceryItemTag&gt;)c.ToList()));
  
  
          }
  
          public DbSet&lt;GroceryItem&gt; GroceryItems { get; set; }
          public DbSet&lt;GroceryItemGroceryStore&gt; GroceryItemGroceryStores { get; set; }
          public DbSet&lt;MenuItemRestaurant&gt; MenuItemRestaurants { get; set; }
          public DbSet&lt;MenuItem&gt; MenuItems { get; set; }
          public DbSet&lt;GroceryStore&gt; GroceryStores { get; set; }
          public DbSet&lt;GroceryItemTag&gt; GroceryItemTags { get; set; }
          public DbSet&lt;MenuItemTag&gt; MenuItemTags { get; set; }
          public DbSet&lt;Restaurant&gt; Restaurants { get; set; }
      }
  }
  </code></pre>
  <p>How do I send the post from postman that will be able to convert the tag id to a <code>GroceryItemTag</code></p>
  <p>I know that I need to do this (<a href="https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations" rel="nofollow noreferrer">See official Microsoft docs</a>):</p>
  <pre><code>    builder.Entity&lt;GroceryItem&gt;()
      .Property(e =&gt; e.Tags)
      .HasConversion(
          v =&gt; JsonSerializer.Serialize(v, null),
          v =&gt; JsonSerializer.Deserialize&lt;List&lt;GroceryItemTag&gt;&gt;(v, null),
          new ValueComparer&lt;IList&lt;GroceryItemTag&gt;&gt;(
              (c1, c2) =&gt; c1.SequenceEqual(c2),
              c =&gt; c.Aggregate(0, (a, v) =&gt; HashCode.Combine(a, v.GetHashCode())),
              c =&gt; (IList&lt;GroceryItemTag&gt;)c.ToList()));
  </code></pre>
  <p>I just can't manage to get it working, for example, it compiles but I get the exact same error. No change at all.</p>