Repository: pydantic/pydantic

Issue:
json_encoders not respected for `enum.Enum` in V2
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

We are using `json_encoders={enum.Enum: lambda val: val.name}` in pydantic V1. In V2, this setting seems to be ignored when migrating to V2.

### Example Code

```Python
import enum

import pydantic
from pydantic import ConfigDict


class MyEnum(enum.Enum):
    A = 'a'
    B = 'b'

class A(pydantic.BaseModel):
    a: MyEnum
    model_config = ConfigDict(json_encoders={enum.Enum: lambda val: val.name})


print(A(a = MyEnum.A).model_dump_json()) # {"a":"a"}, expected {"a":"A"}
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.1.1
        pydantic-core version: 2.4.0
          pydantic-core build: profile=release pgo=true mimalloc=true
                 install path: .../lib/python3.10/site-packages/pydantic
               python version: 3.10.2 (main, Mar  3 2022, 12:03:29) [GCC 9.3.0]
                     platform: Linux-5.14.0-1054-oem-x86_64-with-glibc2.35
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @samuelcolvin


Discussion:
@adriangb please can you have a look at this since you worked on `json_encoders`.
It's likely a bug stemming form the complexity in `GenerateSchema`. I think we can fix it, but it may not be trivial.

Resolving patch:
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -242,7 +242,8 @@ def _add_custom_serialization_from_json_encoders(
         return schema
     # Check the class type and its superclasses for a matching encoder
     # Decimal.__class__.__mro__ (and probably other cases) doesn't include Decimal itself
-    for base in (tp, *tp.__class__.__mro__[:-1]):
+    # if the type is a GenericAlias (e.g. from list[int]) we need to use __class__ instead of .__mro__
+    for base in (tp, *getattr(tp, '__mro__', tp.__class__.__mro__)[:-1]):
         encoder = json_encoders.get(base)
         if encoder is None:
             continue


Test patch:
diff --git a/tests/test_json.py b/tests/test_json.py
--- a/tests/test_json.py
+++ b/tests/test_json.py
@@ -7,7 +7,7 @@
 from enum import Enum
 from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
 from pathlib import Path
-from typing import Any, Generator, Optional, Pattern, Union
+from typing import Any, Generator, List, Optional, Pattern, Union
 from uuid import UUID
 
 import pytest
@@ -482,3 +482,21 @@ class Model(BaseModel):
     m = Model(x=1)
     assert m.model_dump() == {'x': 1}
     assert m.model_dump_json() == '{"x":"encoded!"}'
+
+
+def test_json_encoders_types() -> None:
+    class MyEnum(Enum):
+        A = 'a'
+        B = 'b'
+
+    class A(BaseModel):
+        a: MyEnum
+        b: List[int]
+        c: Decimal
+        model_config = ConfigDict(
+            json_encoders={Enum: lambda val: val.name, List[int]: lambda val: 'list!', Decimal: lambda val: 'decimal!'}
+        )
+
+    m = A(a=MyEnum.A, b=[1, 2, 3], c=Decimal('0'))
+    assert m.model_dump_json() == '{"a":"A","b":"list!","c":"decimal!"}'
+    assert m.model_dump() == {'a': MyEnum.A, 'b': [1, 2, 3], 'c': Decimal('0')}