Repository: netbox-community/netbox
Issue: swagger/ipam/ip-addresses/: Invalid type for "assigned_object"

### NetBox version

v3.2.6

### Python version

3.8

### Steps to Reproduce

Hi there,

the netbox swagger.yml states that the IP address list endpoint `/ipam/ip-addresses/` returns a value of type map[string]string for key `assigned_object ` instead of the correct type object. This causes issues for me since I use an auto-generated api client which will error out in case it receives something _not string-like_ - in this case e.g. a number as id.

Another similar issue has been opened here already: https://github.com/netbox-community/go-netbox/issues/133

What can we do about that? :)

Thanks a lot!

### Expected Behavior

Correct type stated in swagger.yml

### Observed Behavior

Incorrect type stated in swagger.yml

Changed files:
netbox/ipam/api/serializers.py
netbox/utilities/custom_inspectors.py
netbox/dcim/api/serializers.py
netbox/netbox/api/serializers/generic.py
netbox/dcim/api/nested_serializers.py
netbox/extras/api/serializers.py
netbox/netbox/settings.py
netbox/tenancy/api/serializers.py
netbox/virtualization/api/serializers.py

diff --git a/netbox/dcim/api/nested_serializers.py b/netbox/dcim/api/nested_serializers.py
index 1be8bb9dc..f5e06e155 100644
--- a/netbox/dcim/api/nested_serializers.py
+++ b/netbox/dcim/api/nested_serializers.py
@@ -316,6 +316,7 @@ class NestedModuleSerializer(WritableNestedSerializer):
 class NestedConsoleServerPortSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
     device = NestedDeviceSerializer(read_only=True)
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.ConsoleServerPort
@@ -325,6 +326,7 @@ class NestedConsoleServerPortSerializer(WritableNestedSerializer):
 class NestedConsolePortSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
     device = NestedDeviceSerializer(read_only=True)
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.ConsolePort
@@ -334,6 +336,7 @@ class NestedConsolePortSerializer(WritableNestedSerializer):
 class NestedPowerOutletSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
     device = NestedDeviceSerializer(read_only=True)
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.PowerOutlet
@@ -343,6 +346,7 @@ class NestedPowerOutletSerializer(WritableNestedSerializer):
 class NestedPowerPortSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
     device = NestedDeviceSerializer(read_only=True)
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.PowerPort
@@ -352,6 +356,7 @@ class NestedPowerPortSerializer(WritableNestedSerializer):
 class NestedInterfaceSerializer(WritableNestedSerializer):
     device = NestedDeviceSerializer(read_only=True)
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.Interface
@@ -361,6 +366,7 @@ class NestedInterfaceSerializer(WritableNestedSerializer):
 class NestedRearPortSerializer(WritableNestedSerializer):
     device = NestedDeviceSerializer(read_only=True)
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.RearPort
@@ -370,6 +376,7 @@ class NestedRearPortSerializer(WritableNestedSerializer):
 class NestedFrontPortSerializer(WritableNestedSerializer):
     device = NestedDeviceSerializer(read_only=True)
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.FrontPort
@@ -454,6 +461,7 @@ class NestedPowerPanelSerializer(WritableNestedSerializer):
 
 class NestedPowerFeedSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerfeed-detail')
+    _occupied = serializers.BooleanField(required=False, read_only=True)
 
     class Meta:
         model = models.PowerFeed
diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py
index af806acb8..79f5339ad 100644
--- a/netbox/dcim/api/serializers.py
+++ b/netbox/dcim/api/serializers.py
@@ -579,7 +579,7 @@ class InventoryItemTemplateSerializer(ValidatedModelSerializer):
             'description', 'component_type', 'component_id', 'component', 'created', 'last_updated', '_depth',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_component(self, obj):
         if obj.component is None:
             return None
@@ -693,13 +693,13 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
             'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_config_context(self, obj):
         return obj.get_config_context()
 
 
 class DeviceNAPALMSerializer(serializers.Serializer):
-    method = serializers.DictField()
+    method = serializers.JSONField()
 
 
 #
@@ -975,7 +975,7 @@ class InventoryItemSerializer(NetBoxModelSerializer):
             'custom_fields', 'created', 'last_updated', '_depth',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_component(self, obj):
         if obj.component is None:
             return None
@@ -1046,7 +1046,7 @@ class CableTerminationSerializer(NetBoxModelSerializer):
             'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id', 'termination'
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_termination(self, obj):
         serializer = get_serializer_for_model(obj.termination, prefix=NESTED_SERIALIZER_PREFIX)
         context = {'request': self.context['request']}
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index 69792e88c..533238d36 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -192,7 +192,7 @@ class ImageAttachmentSerializer(ValidatedModelSerializer):
 
         return data
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_parent(self, obj):
         serializer = get_serializer_for_model(obj.parent, prefix=NESTED_SERIALIZER_PREFIX)
         return serializer(obj.parent, context={'request': self.context['request']}).data
@@ -242,7 +242,7 @@ class JournalEntrySerializer(NetBoxModelSerializer):
 
         return data
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_assigned_object(self, instance):
         serializer = get_serializer_for_model(instance.assigned_object_type.model_class(), prefix=NESTED_SERIALIZER_PREFIX)
         context = {'request': self.context['request']}
@@ -461,7 +461,7 @@ class ObjectChangeSerializer(BaseModelSerializer):
             'changed_object_id', 'changed_object', 'prechange_data', 'postchange_data',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_changed_object(self, obj):
         """
         Serialize a nested representation of the changed object.
diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py
index 91a81d3b2..6244e5f04 100644
--- a/netbox/ipam/api/serializers.py
+++ b/netbox/ipam/api/serializers.py
@@ -143,7 +143,7 @@ class FHRPGroupAssignmentSerializer(NetBoxModelSerializer):
             'last_updated',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_interface(self, obj):
         if obj.interface is None:
             return None
@@ -373,7 +373,7 @@ class IPAddressSerializer(NetBoxModelSerializer):
             'custom_fields', 'created', 'last_updated',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_assigned_object(self, obj):
         if obj.assigned_object is None:
             return None
@@ -482,7 +482,7 @@ class L2VPNTerminationSerializer(NetBoxModelSerializer):
             'assigned_object', 'tags', 'custom_fields', 'created', 'last_updated'
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_assigned_object(self, instance):
         serializer = get_serializer_for_model(instance.assigned_object, prefix=NESTED_SERIALIZER_PREFIX)
         context = {'request': self.context['request']}
diff --git a/netbox/netbox/api/serializers/generic.py b/netbox/netbox/api/serializers/generic.py
index 5016bdaab..f01e6995a 100644
--- a/netbox/netbox/api/serializers/generic.py
+++ b/netbox/netbox/api/serializers/generic.py
@@ -38,7 +38,7 @@ class GenericObjectSerializer(serializers.Serializer):
 
         return data
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_object(self, obj):
         serializer = get_serializer_for_model(obj, prefix=NESTED_SERIALIZER_PREFIX)
         # context = {'request': self.context['request']}
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
index 8c44ff015..ee91d057c 100644
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -575,7 +575,6 @@ SWAGGER_SETTINGS = {
     'DEFAULT_AUTO_SCHEMA_CLASS': 'utilities.custom_inspectors.NetBoxSwaggerAutoSchema',
     'DEFAULT_FIELD_INSPECTORS': [
         'utilities.custom_inspectors.CustomFieldsDataFieldInspector',
-        'utilities.custom_inspectors.JSONFieldInspector',
         'utilities.custom_inspectors.NullableBooleanFieldInspector',
         'utilities.custom_inspectors.ChoiceFieldInspector',
         'utilities.custom_inspectors.SerializedPKRelatedFieldInspector',
@@ -585,6 +584,7 @@ SWAGGER_SETTINGS = {
         'drf_yasg.inspectors.ChoiceFieldInspector',
         'drf_yasg.inspectors.FileFieldInspector',
         'drf_yasg.inspectors.DictFieldInspector',
+        'drf_yasg.inspectors.JSONFieldInspector',
         'drf_yasg.inspectors.SerializerMethodFieldInspector',
         'drf_yasg.inspectors.SimpleFieldInspector',
         'drf_yasg.inspectors.StringDefaultFieldInspector',
diff --git a/netbox/tenancy/api/serializers.py b/netbox/tenancy/api/serializers.py
index f217fdaf8..d2c6801c6 100644
--- a/netbox/tenancy/api/serializers.py
+++ b/netbox/tenancy/api/serializers.py
@@ -107,7 +107,7 @@ class ContactAssignmentSerializer(NetBoxModelSerializer):
             'last_updated',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_object(self, instance):
         serializer = get_serializer_for_model(instance.content_type.model_class(), prefix=NESTED_SERIALIZER_PREFIX)
         context = {'request': self.context['request']}
diff --git a/netbox/utilities/custom_inspectors.py b/netbox/utilities/custom_inspectors.py
index 1a5ede23f..258399e86 100644
--- a/netbox/utilities/custom_inspectors.py
+++ b/netbox/utilities/custom_inspectors.py
@@ -1,4 +1,3 @@
-from django.contrib.postgres.fields import JSONField
 from drf_yasg import openapi
 from drf_yasg.inspectors import FieldInspector, NotHandled, PaginatorInspector, SwaggerAutoSchema
 from drf_yasg.utils import get_serializer_ref_name
@@ -131,15 +130,6 @@ class CustomFieldsDataFieldInspector(FieldInspector):
         return NotHandled
 
 
-class JSONFieldInspector(FieldInspector):
-    """Required because by default, Swagger sees a JSONField as a string and not dict
-    """
-    def process_result(self, result, method_name, obj, **kwargs):
-        if isinstance(result, openapi.Schema) and isinstance(obj, JSONField):
-            result.type = 'dict'
-        return result
-
-
 class NullablePaginatorInspector(PaginatorInspector):
     def process_result(self, result, method_name, obj, **kwargs):
         if method_name == 'get_paginated_response' and isinstance(result, openapi.Schema):
diff --git a/netbox/virtualization/api/serializers.py b/netbox/virtualization/api/serializers.py
index 903d89a07..b88bc7712 100644
--- a/netbox/virtualization/api/serializers.py
+++ b/netbox/virtualization/api/serializers.py
@@ -100,7 +100,7 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
             'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
         ]
 
-    @swagger_serializer_method(serializer_or_field=serializers.DictField)
+    @swagger_serializer_method(serializer_or_field=serializers.JSONField)
     def get_config_context(self, obj):
         return obj.get_config_context()