Repository: getkin/kin-openapi
Pull request #1011 (2024-09-17 15:57:53)

openapi2: fix un/marshalling discriminator field
This PR addresses issue https://github.com/getkin/kin-openapi/issues/1010. It allows `discriminator` field to be of any type and case it based on the OpenAPI version. 

reversearrow: @fenollp can please review this PR? Thanks


fenollp: Hi! Thanks for tackling this. 

The ideal solution to me is to define a different Discriminator type in v2 and leave the current definition as is in v3. I. E. to have two different types and avoid casting. 
Do you think you'd be able to update your PR with this in mind?
Thanks
reversearrow: > Hi! Thanks for tackling this.
> 
> The ideal solution to me is to define a different Discriminator type in v2 and leave the current definition as is in v3. I. E. to have two different types and avoid casting. Do you think you'd be able to update your PR with this in mind? Thanks

Hi @fenollp, thank you for your feedback. I agree with your suggestion. After briefly reviewing the proposed approach, it seemed like it would involve significant effort. In the meantime, I’ve updated the PR with a simpler approach than before, while not perfect, addresses the bug. Do you think we can proceed with merging this PR? If so, I can work on implementing the original suggested approach without the pressure of a strict timeline, as this is currently impacting one of our customers in production.

reversearrow: @fenollp I have create a new Schema Object for the OpenAPI V2. Please review and let me know. 

Patch:
diff --git a/.github/docs/openapi2.txt b/.github/docs/openapi2.txt
index aec6439..9025f5a 100644
--- a/.github/docs/openapi2.txt
+++ b/.github/docs/openapi2.txt
@@ -47,29 +47,29 @@ type Parameter struct {
 
 	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 
-	In               string              `json:"in,omitempty" yaml:"in,omitempty"`
-	Name             string              `json:"name,omitempty" yaml:"name,omitempty"`
-	Description      string              `json:"description,omitempty" yaml:"description,omitempty"`
-	CollectionFormat string              `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
-	Type             *openapi3.Types     `json:"type,omitempty" yaml:"type,omitempty"`
-	Format           string              `json:"format,omitempty" yaml:"format,omitempty"`
-	Pattern          string              `json:"pattern,omitempty" yaml:"pattern,omitempty"`
-	AllowEmptyValue  bool                `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
-	Required         bool                `json:"required,omitempty" yaml:"required,omitempty"`
-	UniqueItems      bool                `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
-	ExclusiveMin     bool                `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
-	ExclusiveMax     bool                `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
-	Schema           *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
-	Items            *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
-	Enum             []any               `json:"enum,omitempty" yaml:"enum,omitempty"`
-	MultipleOf       *float64            `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
-	Minimum          *float64            `json:"minimum,omitempty" yaml:"minimum,omitempty"`
-	Maximum          *float64            `json:"maximum,omitempty" yaml:"maximum,omitempty"`
-	MaxLength        *uint64             `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
-	MaxItems         *uint64             `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
-	MinLength        uint64              `json:"minLength,omitempty" yaml:"minLength,omitempty"`
-	MinItems         uint64              `json:"minItems,omitempty" yaml:"minItems,omitempty"`
-	Default          any                 `json:"default,omitempty" yaml:"default,omitempty"`
+	In               string          `json:"in,omitempty" yaml:"in,omitempty"`
+	Name             string          `json:"name,omitempty" yaml:"name,omitempty"`
+	Description      string          `json:"description,omitempty" yaml:"description,omitempty"`
+	CollectionFormat string          `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
+	Type             *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
+	Format           string          `json:"format,omitempty" yaml:"format,omitempty"`
+	Pattern          string          `json:"pattern,omitempty" yaml:"pattern,omitempty"`
+	AllowEmptyValue  bool            `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
+	Required         bool            `json:"required,omitempty" yaml:"required,omitempty"`
+	UniqueItems      bool            `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
+	ExclusiveMin     bool            `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
+	ExclusiveMax     bool            `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
+	Schema           *SchemaRef      `json:"schema,omitempty" yaml:"schema,omitempty"`
+	Items            *SchemaRef      `json:"items,omitempty" yaml:"items,omitempty"`
+	Enum             []any           `json:"enum,omitempty" yaml:"enum,omitempty"`
+	MultipleOf       *float64        `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
+	Minimum          *float64        `json:"minimum,omitempty" yaml:"minimum,omitempty"`
+	Maximum          *float64        `json:"maximum,omitempty" yaml:"maximum,omitempty"`
+	MaxLength        *uint64         `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
+	MaxItems         *uint64         `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
+	MinLength        uint64          `json:"minLength,omitempty" yaml:"minLength,omitempty"`
+	MinItems         uint64          `json:"minItems,omitempty" yaml:"minItems,omitempty"`
+	Default          any             `json:"default,omitempty" yaml:"default,omitempty"`
 }
 
 func (parameter Parameter) MarshalJSON() ([]byte, error)
@@ -113,15 +113,21 @@ func (pathItem *PathItem) SetOperation(method string, operation *Operation)
 func (pathItem *PathItem) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets PathItem to a copy of data.
 
+type Ref struct {
+	Ref string `json:"$ref" yaml:"$ref"`
+}
+    Ref is specified by OpenAPI/Swagger 2.0 standard. See
+    https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object
+
 type Response struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
 
 	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 
-	Description string              `json:"description,omitempty" yaml:"description,omitempty"`
-	Schema      *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
-	Headers     map[string]*Header  `json:"headers,omitempty" yaml:"headers,omitempty"`
-	Examples    map[string]any      `json:"examples,omitempty" yaml:"examples,omitempty"`
+	Description string             `json:"description,omitempty" yaml:"description,omitempty"`
+	Schema      *SchemaRef         `json:"schema,omitempty" yaml:"schema,omitempty"`
+	Headers     map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
+	Examples    map[string]any     `json:"examples,omitempty" yaml:"examples,omitempty"`
 }
 
 func (response Response) MarshalJSON() ([]byte, error)
@@ -130,6 +136,107 @@ func (response Response) MarshalJSON() ([]byte, error)
 func (response *Response) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets Response to a copy of data.
 
+type Schema struct {
+	Extensions map[string]any `json:"-" yaml:"-"`
+
+	AllOf        SchemaRefs             `json:"allOf,omitempty" yaml:"allOf,omitempty"`
+	Not          *SchemaRef             `json:"not,omitempty" yaml:"not,omitempty"`
+	Type         *openapi3.Types        `json:"type,omitempty" yaml:"type,omitempty"`
+	Title        string                 `json:"title,omitempty" yaml:"title,omitempty"`
+	Format       string                 `json:"format,omitempty" yaml:"format,omitempty"`
+	Description  string                 `json:"description,omitempty" yaml:"description,omitempty"`
+	Enum         []any                  `json:"enum,omitempty" yaml:"enum,omitempty"`
+	Default      any                    `json:"default,omitempty" yaml:"default,omitempty"`
+	Example      any                    `json:"example,omitempty" yaml:"example,omitempty"`
+	ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+
+	// Array-related, here for struct compactness
+	UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
+	// Number-related, here for struct compactness
+	ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
+	ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
+	// Properties
+	ReadOnly        bool          `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
+	WriteOnly       bool          `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
+	AllowEmptyValue bool          `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
+	Deprecated      bool          `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
+	XML             *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"`
+
+	// Number
+	Min        *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
+	Max        *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
+	MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
+
+	// String
+	MinLength uint64  `json:"minLength,omitempty" yaml:"minLength,omitempty"`
+	MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
+	Pattern   string  `json:"pattern,omitempty" yaml:"pattern,omitempty"`
+
+	// Array
+	MinItems uint64     `json:"minItems,omitempty" yaml:"minItems,omitempty"`
+	MaxItems *uint64    `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
+	Items    *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
+
+	// Object
+	Required             []string                      `json:"required,omitempty" yaml:"required,omitempty"`
+	Properties           Schemas                       `json:"properties,omitempty" yaml:"properties,omitempty"`
+	MinProps             uint64                        `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
+	MaxProps             *uint64                       `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
+	AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
+	Discriminator        string                        `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
+}
+    Schema is specified by OpenAPI/Swagger 2.0 standard. See
+    https://swagger.io/specification/v2/#schema-object
+
+func (schema Schema) MarshalJSON() ([]byte, error)
+    MarshalJSON returns the JSON encoding of Schema.
+
+func (schema Schema) MarshalYAML() (any, error)
+    MarshalYAML returns the YAML encoding of Schema.
+
+func (schema *Schema) UnmarshalJSON(data []byte) error
+    UnmarshalJSON sets Schema to a copy of data.
+
+type SchemaRef struct {
+	// Extensions only captures fields starting with 'x-' as no other fields
+	// are allowed by the openapi spec.
+	Extensions map[string]any
+
+	Ref   string
+	Value *Schema
+
+	// Has unexported fields.
+}
+    SchemaRef represents either a Schema or a $ref to a Schema. When serializing
+    and both fields are set, Ref is preferred over Value.
+
+func (x *SchemaRef) CollectionName() string
+    CollectionName returns the JSON string used for a collection of these
+    components.
+
+func (x *SchemaRef) JSONLookup(token string) (any, error)
+    JSONLookup implements
+    https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
+
+func (x SchemaRef) MarshalJSON() ([]byte, error)
+    MarshalJSON returns the JSON encoding of SchemaRef.
+
+func (x SchemaRef) MarshalYAML() (any, error)
+    MarshalYAML returns the YAML encoding of SchemaRef.
+
+func (x *SchemaRef) RefPath() *url.URL
+    RefPath returns the path of the $ref relative to the root document.
+
+func (x *SchemaRef) RefString() string
+    RefString returns the $ref value.
+
+func (x *SchemaRef) UnmarshalJSON(data []byte) error
+    UnmarshalJSON sets SchemaRef to a copy of data.
+
+type SchemaRefs []*SchemaRef
+
+type Schemas map[string]*SchemaRef
+
 type SecurityRequirements []map[string][]string
 
 type SecurityScheme struct {
@@ -157,21 +264,21 @@ func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error
 type T struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
 
-	Swagger             string                         `json:"swagger" yaml:"swagger"` // required
-	Info                openapi3.Info                  `json:"info" yaml:"info"`       // required
-	ExternalDocs        *openapi3.ExternalDocs         `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
-	Schemes             []string                       `json:"schemes,omitempty" yaml:"schemes,omitempty"`
-	Consumes            []string                       `json:"consumes,omitempty" yaml:"consumes,omitempty"`
-	Produces            []string                       `json:"produces,omitempty" yaml:"produces,omitempty"`
-	Host                string                         `json:"host,omitempty" yaml:"host,omitempty"`
-	BasePath            string                         `json:"basePath,omitempty" yaml:"basePath,omitempty"`
-	Paths               map[string]*PathItem           `json:"paths,omitempty" yaml:"paths,omitempty"`
-	Definitions         map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
-	Parameters          map[string]*Parameter          `json:"parameters,omitempty" yaml:"parameters,omitempty"`
-	Responses           map[string]*Response           `json:"responses,omitempty" yaml:"responses,omitempty"`
-	SecurityDefinitions map[string]*SecurityScheme     `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
-	Security            SecurityRequirements           `json:"security,omitempty" yaml:"security,omitempty"`
-	Tags                openapi3.Tags                  `json:"tags,omitempty" yaml:"tags,omitempty"`
+	Swagger             string                     `json:"swagger" yaml:"swagger"` // required
+	Info                openapi3.Info              `json:"info" yaml:"info"`       // required
+	ExternalDocs        *openapi3.ExternalDocs     `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+	Schemes             []string                   `json:"schemes,omitempty" yaml:"schemes,omitempty"`
+	Consumes            []string                   `json:"consumes,omitempty" yaml:"consumes,omitempty"`
+	Produces            []string                   `json:"produces,omitempty" yaml:"produces,omitempty"`
+	Host                string                     `json:"host,omitempty" yaml:"host,omitempty"`
+	BasePath            string                     `json:"basePath,omitempty" yaml:"basePath,omitempty"`
+	Paths               map[string]*PathItem       `json:"paths,omitempty" yaml:"paths,omitempty"`
+	Definitions         map[string]*SchemaRef      `json:"definitions,omitempty" yaml:"definitions,omitempty"`
+	Parameters          map[string]*Parameter      `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+	Responses           map[string]*Response       `json:"responses,omitempty" yaml:"responses,omitempty"`
+	SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
+	Security            SecurityRequirements       `json:"security,omitempty" yaml:"security,omitempty"`
+	Tags                openapi3.Tags              `json:"tags,omitempty" yaml:"tags,omitempty"`
 }
     T is the root of an OpenAPI v2 document
 
diff --git a/.github/docs/openapi2conv.txt b/.github/docs/openapi2conv.txt
index 2f9f6de..e24925a 100644
--- a/.github/docs/openapi2conv.txt
+++ b/.github/docs/openapi2conv.txt
@@ -16,8 +16,8 @@ func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, med
 func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameters
 func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components) (*openapi2.Response, error)
 func FromV3Responses(responses map[string]*openapi3.ResponseRef, components *openapi3.Components) (map[string]*openapi2.Response, error)
-func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter)
-func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter)
+func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter)
+func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter)
 func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements
 func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*openapi2.SecurityScheme, error)
 func ToV3(doc2 *openapi2.T) (*openapi3.T, error)
@@ -29,8 +29,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error)
 func ToV3Ref(ref string) string
 func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.ResponseRef, error)
-func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef
-func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef
+func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef
+func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef
 func ToV3SecurityRequirements(requirements openapi2.SecurityRequirements) openapi3.SecurityRequirements
 func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.SecuritySchemeRef, error)
 func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL) (*openapi3.T, error)
diff --git a/openapi2/helpers.go b/openapi2/helpers.go
new file mode 100644
index 0000000..777a2f0
--- /dev/null
+++ b/openapi2/helpers.go
@@ -0,0 +1,15 @@
+package openapi2
+
+import (
+	"net/url"
+)
+
+// copyURI makes a copy of the pointer.
+func copyURI(u *url.URL) *url.URL {
+	if u == nil {
+		return nil
+	}
+
+	c := *u // shallow-copy
+	return &c
+}
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index 2d922c6..bd33753 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -10,21 +10,21 @@ import (
 type T struct {
 	Extensions map[string]any `json:"-" yaml:"-"`
 
-	Swagger             string                         `json:"swagger" yaml:"swagger"` // required
-	Info                openapi3.Info                  `json:"info" yaml:"info"`       // required
-	ExternalDocs        *openapi3.ExternalDocs         `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
-	Schemes             []string                       `json:"schemes,omitempty" yaml:"schemes,omitempty"`
-	Consumes            []string                       `json:"consumes,omitempty" yaml:"consumes,omitempty"`
-	Produces            []string                       `json:"produces,omitempty" yaml:"produces,omitempty"`
-	Host                string                         `json:"host,omitempty" yaml:"host,omitempty"`
-	BasePath            string                         `json:"basePath,omitempty" yaml:"basePath,omitempty"`
-	Paths               map[string]*PathItem           `json:"paths,omitempty" yaml:"paths,omitempty"`
-	Definitions         map[string]*openapi3.SchemaRef `json:"definitions,omitempty" yaml:"definitions,omitempty"`
-	Parameters          map[string]*Parameter          `json:"parameters,omitempty" yaml:"parameters,omitempty"`
-	Responses           map[string]*Response           `json:"responses,omitempty" yaml:"responses,omitempty"`
-	SecurityDefinitions map[string]*SecurityScheme     `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
-	Security            SecurityRequirements           `json:"security,omitempty" yaml:"security,omitempty"`
-	Tags                openapi3.Tags                  `json:"tags,omitempty" yaml:"tags,omitempty"`
+	Swagger             string                     `json:"swagger" yaml:"swagger"` // required
+	Info                openapi3.Info              `json:"info" yaml:"info"`       // required
+	ExternalDocs        *openapi3.ExternalDocs     `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+	Schemes             []string                   `json:"schemes,omitempty" yaml:"schemes,omitempty"`
+	Consumes            []string                   `json:"consumes,omitempty" yaml:"consumes,omitempty"`
+	Produces            []string                   `json:"produces,omitempty" yaml:"produces,omitempty"`
+	Host                string                     `json:"host,omitempty" yaml:"host,omitempty"`
+	BasePath            string                     `json:"basePath,omitempty" yaml:"basePath,omitempty"`
+	Paths               map[string]*PathItem       `json:"paths,omitempty" yaml:"paths,omitempty"`
+	Definitions         map[string]*SchemaRef      `json:"definitions,omitempty" yaml:"definitions,omitempty"`
+	Parameters          map[string]*Parameter      `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+	Responses           map[string]*Response       `json:"responses,omitempty" yaml:"responses,omitempty"`
+	SecurityDefinitions map[string]*SecurityScheme `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
+	Security            SecurityRequirements       `json:"security,omitempty" yaml:"security,omitempty"`
+	Tags                openapi3.Tags              `json:"tags,omitempty" yaml:"tags,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of T.
diff --git a/openapi2/parameter.go b/openapi2/parameter.go
index c701705..7f2bddb 100644
--- a/openapi2/parameter.go
+++ b/openapi2/parameter.go
@@ -28,29 +28,29 @@ type Parameter struct {
 
 	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 
-	In               string              `json:"in,omitempty" yaml:"in,omitempty"`
-	Name             string              `json:"name,omitempty" yaml:"name,omitempty"`
-	Description      string              `json:"description,omitempty" yaml:"description,omitempty"`
-	CollectionFormat string              `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
-	Type             *openapi3.Types     `json:"type,omitempty" yaml:"type,omitempty"`
-	Format           string              `json:"format,omitempty" yaml:"format,omitempty"`
-	Pattern          string              `json:"pattern,omitempty" yaml:"pattern,omitempty"`
-	AllowEmptyValue  bool                `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
-	Required         bool                `json:"required,omitempty" yaml:"required,omitempty"`
-	UniqueItems      bool                `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
-	ExclusiveMin     bool                `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
-	ExclusiveMax     bool                `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
-	Schema           *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
-	Items            *openapi3.SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
-	Enum             []any               `json:"enum,omitempty" yaml:"enum,omitempty"`
-	MultipleOf       *float64            `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
-	Minimum          *float64            `json:"minimum,omitempty" yaml:"minimum,omitempty"`
-	Maximum          *float64            `json:"maximum,omitempty" yaml:"maximum,omitempty"`
-	MaxLength        *uint64             `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
-	MaxItems         *uint64             `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
-	MinLength        uint64              `json:"minLength,omitempty" yaml:"minLength,omitempty"`
-	MinItems         uint64              `json:"minItems,omitempty" yaml:"minItems,omitempty"`
-	Default          any                 `json:"default,omitempty" yaml:"default,omitempty"`
+	In               string          `json:"in,omitempty" yaml:"in,omitempty"`
+	Name             string          `json:"name,omitempty" yaml:"name,omitempty"`
+	Description      string          `json:"description,omitempty" yaml:"description,omitempty"`
+	CollectionFormat string          `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
+	Type             *openapi3.Types `json:"type,omitempty" yaml:"type,omitempty"`
+	Format           string          `json:"format,omitempty" yaml:"format,omitempty"`
+	Pattern          string          `json:"pattern,omitempty" yaml:"pattern,omitempty"`
+	AllowEmptyValue  bool            `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
+	Required         bool            `json:"required,omitempty" yaml:"required,omitempty"`
+	UniqueItems      bool            `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
+	ExclusiveMin     bool            `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
+	ExclusiveMax     bool            `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
+	Schema           *SchemaRef      `json:"schema,omitempty" yaml:"schema,omitempty"`
+	Items            *SchemaRef      `json:"items,omitempty" yaml:"items,omitempty"`
+	Enum             []any           `json:"enum,omitempty" yaml:"enum,omitempty"`
+	MultipleOf       *float64        `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
+	Minimum          *float64        `json:"minimum,omitempty" yaml:"minimum,omitempty"`
+	Maximum          *float64        `json:"maximum,omitempty" yaml:"maximum,omitempty"`
+	MaxLength        *uint64         `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
+	MaxItems         *uint64         `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
+	MinLength        uint64          `json:"minLength,omitempty" yaml:"minLength,omitempty"`
+	MinItems         uint64          `json:"minItems,omitempty" yaml:"minItems,omitempty"`
+	Default          any             `json:"default,omitempty" yaml:"default,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of Parameter.
diff --git a/openapi2/ref.go b/openapi2/ref.go
new file mode 100644
index 0000000..e591d14
--- /dev/null
+++ b/openapi2/ref.go
@@ -0,0 +1,9 @@
+package openapi2
+
+//go:generate go run refsgenerator.go
+
+// Ref is specified by OpenAPI/Swagger 2.0 standard.
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md#reference-object
+type Ref struct {
+	Ref string `json:"$ref" yaml:"$ref"`
+}
diff --git a/openapi2/refs.go b/openapi2/refs.go
new file mode 100644
index 0000000..5109e98
--- /dev/null
+++ b/openapi2/refs.go
@@ -0,0 +1,104 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"net/url"
+	"sort"
+	"strings"
+
+	"github.com/go-openapi/jsonpointer"
+	"github.com/perimeterx/marshmallow"
+)
+
+// SchemaRef represents either a Schema or a $ref to a Schema.
+// When serializing and both fields are set, Ref is preferred over Value.
+type SchemaRef struct {
+	// Extensions only captures fields starting with 'x-' as no other fields
+	// are allowed by the openapi spec.
+	Extensions map[string]any
+
+	Ref   string
+	Value *Schema
+	extra []string
+
+	refPath *url.URL
+}
+
+var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
+
+func (x *SchemaRef) isEmpty() bool { return x == nil || x.Ref == "" && x.Value == nil }
+
+// RefString returns the $ref value.
+func (x *SchemaRef) RefString() string { return x.Ref }
+
+// CollectionName returns the JSON string used for a collection of these components.
+func (x *SchemaRef) CollectionName() string { return "schemas" }
+
+// RefPath returns the path of the $ref relative to the root document.
+func (x *SchemaRef) RefPath() *url.URL { return copyURI(x.refPath) }
+
+func (x *SchemaRef) setRefPath(u *url.URL) {
+	// Once the refPath is set don't override. References can be loaded
+	// multiple times not all with access to the correct path info.
+	if x.refPath != nil {
+		return
+	}
+
+	x.refPath = copyURI(u)
+}
+
+// MarshalYAML returns the YAML encoding of SchemaRef.
+func (x SchemaRef) MarshalYAML() (any, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value.MarshalYAML()
+}
+
+// MarshalJSON returns the JSON encoding of SchemaRef.
+func (x SchemaRef) MarshalJSON() ([]byte, error) {
+	y, err := x.MarshalYAML()
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(y)
+}
+
+// UnmarshalJSON sets SchemaRef to a copy of data.
+func (x *SchemaRef) UnmarshalJSON(data []byte) error {
+	var refOnly Ref
+	if extra, err := marshmallow.Unmarshal(data, &refOnly, marshmallow.WithExcludeKnownFieldsFromMap(true)); err == nil && refOnly.Ref != "" {
+		x.Ref = refOnly.Ref
+		if len(extra) != 0 {
+			x.extra = make([]string, 0, len(extra))
+			for key := range extra {
+				x.extra = append(x.extra, key)
+			}
+			sort.Strings(x.extra)
+			for k := range extra {
+				if !strings.HasPrefix(k, "x-") {
+					delete(extra, k)
+				}
+			}
+			if len(extra) != 0 {
+				x.Extensions = extra
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
+}
+
+// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
+func (x *SchemaRef) JSONLookup(token string) (any, error) {
+	if token == "$ref" {
+		return x.Ref, nil
+	}
+
+	if v, ok := x.Extensions[token]; ok {
+		return v, nil
+	}
+
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
+	return ptr, err
+}
diff --git a/openapi2/response.go b/openapi2/response.go
index 5306beb..3a2983c 100644
--- a/openapi2/response.go
+++ b/openapi2/response.go
@@ -11,10 +11,10 @@ type Response struct {
 
 	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 
-	Description string              `json:"description,omitempty" yaml:"description,omitempty"`
-	Schema      *openapi3.SchemaRef `json:"schema,omitempty" yaml:"schema,omitempty"`
-	Headers     map[string]*Header  `json:"headers,omitempty" yaml:"headers,omitempty"`
-	Examples    map[string]any      `json:"examples,omitempty" yaml:"examples,omitempty"`
+	Description string             `json:"description,omitempty" yaml:"description,omitempty"`
+	Schema      *SchemaRef         `json:"schema,omitempty" yaml:"schema,omitempty"`
+	Headers     map[string]*Header `json:"headers,omitempty" yaml:"headers,omitempty"`
+	Examples    map[string]any     `json:"examples,omitempty" yaml:"examples,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of Response.
diff --git a/openapi2/schema.go b/openapi2/schema.go
new file mode 100644
index 0000000..64bed27
--- /dev/null
+++ b/openapi2/schema.go
@@ -0,0 +1,269 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type (
+	Schemas    map[string]*SchemaRef
+	SchemaRefs []*SchemaRef
+)
+
+// Schema is specified by OpenAPI/Swagger 2.0 standard.
+// See https://swagger.io/specification/v2/#schema-object
+type Schema struct {
+	Extensions map[string]any `json:"-" yaml:"-"`
+
+	AllOf        SchemaRefs             `json:"allOf,omitempty" yaml:"allOf,omitempty"`
+	Not          *SchemaRef             `json:"not,omitempty" yaml:"not,omitempty"`
+	Type         *openapi3.Types        `json:"type,omitempty" yaml:"type,omitempty"`
+	Title        string                 `json:"title,omitempty" yaml:"title,omitempty"`
+	Format       string                 `json:"format,omitempty" yaml:"format,omitempty"`
+	Description  string                 `json:"description,omitempty" yaml:"description,omitempty"`
+	Enum         []any                  `json:"enum,omitempty" yaml:"enum,omitempty"`
+	Default      any                    `json:"default,omitempty" yaml:"default,omitempty"`
+	Example      any                    `json:"example,omitempty" yaml:"example,omitempty"`
+	ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+
+	// Array-related, here for struct compactness
+	UniqueItems bool `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
+	// Number-related, here for struct compactness
+	ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
+	ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
+	// Properties
+	ReadOnly        bool          `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
+	WriteOnly       bool          `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
+	AllowEmptyValue bool          `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
+	Deprecated      bool          `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
+	XML             *openapi3.XML `json:"xml,omitempty" yaml:"xml,omitempty"`
+
+	// Number
+	Min        *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
+	Max        *float64 `json:"maximum,omitempty" yaml:"maximum,omitempty"`
+	MultipleOf *float64 `json:"multipleOf,omitempty" yaml:"multipleOf,omitempty"`
+
+	// String
+	MinLength uint64  `json:"minLength,omitempty" yaml:"minLength,omitempty"`
+	MaxLength *uint64 `json:"maxLength,omitempty" yaml:"maxLength,omitempty"`
+	Pattern   string  `json:"pattern,omitempty" yaml:"pattern,omitempty"`
+
+	// Array
+	MinItems uint64     `json:"minItems,omitempty" yaml:"minItems,omitempty"`
+	MaxItems *uint64    `json:"maxItems,omitempty" yaml:"maxItems,omitempty"`
+	Items    *SchemaRef `json:"items,omitempty" yaml:"items,omitempty"`
+
+	// Object
+	Required             []string                      `json:"required,omitempty" yaml:"required,omitempty"`
+	Properties           Schemas                       `json:"properties,omitempty" yaml:"properties,omitempty"`
+	MinProps             uint64                        `json:"minProperties,omitempty" yaml:"minProperties,omitempty"`
+	MaxProps             *uint64                       `json:"maxProperties,omitempty" yaml:"maxProperties,omitempty"`
+	AdditionalProperties openapi3.AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
+	Discriminator        string                        `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of Schema.
+func (schema Schema) MarshalJSON() ([]byte, error) {
+	m, err := schema.MarshalYAML()
+	if err != nil {
+		return nil, err
+	}
+
+	return json.Marshal(m)
+}
+
+// MarshalYAML returns the YAML encoding of Schema.
+func (schema Schema) MarshalYAML() (any, error) {
+	m := make(map[string]any, 36+len(schema.Extensions))
+	for k, v := range schema.Extensions {
+		m[k] = v
+	}
+
+	if x := schema.AllOf; len(x) != 0 {
+		m["allOf"] = x
+	}
+	if x := schema.Not; x != nil {
+		m["not"] = x
+	}
+	if x := schema.Type; x != nil {
+		m["type"] = x
+	}
+	if x := schema.Title; len(x) != 0 {
+		m["title"] = x
+	}
+	if x := schema.Format; len(x) != 0 {
+		m["format"] = x
+	}
+	if x := schema.Description; len(x) != 0 {
+		m["description"] = x
+	}
+	if x := schema.Enum; len(x) != 0 {
+		m["enum"] = x
+	}
+	if x := schema.Default; x != nil {
+		m["default"] = x
+	}
+	if x := schema.Example; x != nil {
+		m["example"] = x
+	}
+	if x := schema.ExternalDocs; x != nil {
+		m["externalDocs"] = x
+	}
+
+	// Array-related
+	if x := schema.UniqueItems; x {
+		m["uniqueItems"] = x
+	}
+	// Number-related
+	if x := schema.ExclusiveMin; x {
+		m["exclusiveMinimum"] = x
+	}
+	if x := schema.ExclusiveMax; x {
+		m["exclusiveMaximum"] = x
+	}
+	if x := schema.ReadOnly; x {
+		m["readOnly"] = x
+	}
+	if x := schema.WriteOnly; x {
+		m["writeOnly"] = x
+	}
+	if x := schema.AllowEmptyValue; x {
+		m["allowEmptyValue"] = x
+	}
+	if x := schema.Deprecated; x {
+		m["deprecated"] = x
+	}
+	if x := schema.XML; x != nil {
+		m["xml"] = x
+	}
+
+	// Number
+	if x := schema.Min; x != nil {
+		m["minimum"] = x
+	}
+	if x := schema.Max; x != nil {
+		m["maximum"] = x
+	}
+	if x := schema.MultipleOf; x != nil {
+		m["multipleOf"] = x
+	}
+
+	// String
+	if x := schema.MinLength; x != 0 {
+		m["minLength"] = x
+	}
+	if x := schema.MaxLength; x != nil {
+		m["maxLength"] = x
+	}
+	if x := schema.Pattern; x != "" {
+		m["pattern"] = x
+	}
+
+	// Array
+	if x := schema.MinItems; x != 0 {
+		m["minItems"] = x
+	}
+	if x := schema.MaxItems; x != nil {
+		m["maxItems"] = x
+	}
+	if x := schema.Items; x != nil {
+		m["items"] = x
+	}
+
+	// Object
+	if x := schema.Required; len(x) != 0 {
+		m["required"] = x
+	}
+	if x := schema.Properties; len(x) != 0 {
+		m["properties"] = x
+	}
+	if x := schema.MinProps; x != 0 {
+		m["minProperties"] = x
+	}
+	if x := schema.MaxProps; x != nil {
+		m["maxProperties"] = x
+	}
+	if x := schema.AdditionalProperties; x.Has != nil || x.Schema != nil {
+		m["additionalProperties"] = &x
+	}
+	if x := schema.Discriminator; x != "" {
+		m["discriminator"] = x
+	}
+
+	return m, nil
+}
+
+// UnmarshalJSON sets Schema to a copy of data.
+func (schema *Schema) UnmarshalJSON(data []byte) error {
+	type SchemaBis Schema
+	var x SchemaBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return unmarshalError(err)
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, "oneOf")
+	delete(x.Extensions, "anyOf")
+	delete(x.Extensions, "allOf")
+	delete(x.Extensions, "not")
+	delete(x.Extensions, "type")
+	delete(x.Extensions, "title")
+	delete(x.Extensions, "format")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "enum")
+	delete(x.Extensions, "default")
+	delete(x.Extensions, "example")
+	delete(x.Extensions, "externalDocs")
+
+	// Array-related
+	delete(x.Extensions, "uniqueItems")
+	// Number-related
+	delete(x.Extensions, "exclusiveMinimum")
+	delete(x.Extensions, "exclusiveMaximum")
+	// Properties
+	delete(x.Extensions, "nullable")
+	delete(x.Extensions, "readOnly")
+	delete(x.Extensions, "writeOnly")
+	delete(x.Extensions, "allowEmptyValue")
+	delete(x.Extensions, "deprecated")
+	delete(x.Extensions, "xml")
+
+	// Number
+	delete(x.Extensions, "minimum")
+	delete(x.Extensions, "maximum")
+	delete(x.Extensions, "multipleOf")
+
+	// String
+	delete(x.Extensions, "minLength")
+	delete(x.Extensions, "maxLength")
+	delete(x.Extensions, "pattern")
+
+	// Array
+	delete(x.Extensions, "minItems")
+	delete(x.Extensions, "maxItems")
+	delete(x.Extensions, "items")
+
+	// Object
+	delete(x.Extensions, "required")
+	delete(x.Extensions, "properties")
+	delete(x.Extensions, "minProperties")
+	delete(x.Extensions, "maxProperties")
+	delete(x.Extensions, "additionalProperties")
+	delete(x.Extensions, "discriminator")
+
+	if len(x.Extensions) == 0 {
+		x.Extensions = nil
+	}
+
+	*schema = Schema(x)
+
+	if schema.Format == "date" {
+		// This is a fix for: https://github.com/getkin/kin-openapi/issues/697
+		if eg, ok := schema.Example.(string); ok {
+			schema.Example = strings.TrimSuffix(eg, "T00:00:00Z")
+		}
+	}
+	return nil
+}
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index fa161bc..ef0a5ed 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -272,7 +272,6 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 			MinLength:       parameter.MinLength,
 			MaxLength:       parameter.MaxLength,
 			Default:         parameter.Default,
-			Items:           parameter.Items,
 			MinItems:        parameter.MinItems,
 			MaxItems:        parameter.MaxItems,
 			Pattern:         parameter.Pattern,
@@ -281,6 +280,10 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 			MultipleOf:      parameter.MultipleOf,
 			Required:        required,
 		}}
+		if parameter.Items != nil {
+			schemaRef.Value.Items = ToV3SchemaRef(parameter.Items)
+		}
+
 		schemaRefMap := make(map[string]*openapi3.SchemaRef, 1)
 		schemaRefMap[parameter.Name] = schemaRef
 		return nil, nil, schemaRefMap, nil
@@ -301,7 +304,7 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 			Description: parameter.Description,
 			Required:    required,
 			Extensions:  stripNonExtensions(parameter.Extensions),
-			Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{
+			Schema: ToV3SchemaRef(&openapi2.SchemaRef{Value: &openapi2.Schema{
 				Type:            parameter.Type,
 				Format:          parameter.Format,
 				Enum:            parameter.Enum,
@@ -349,7 +352,7 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, c
 	sort.Strings(requireds)
 	schema := &openapi3.Schema{
 		Type:       &openapi3.Types{"object"},
-		Properties: ToV3Schemas(bodies),
+		Properties: bodies,
 		Required:   requireds,
 	}
 	return &openapi3.RequestBodyRef{
@@ -456,7 +459,7 @@ func ToV3Headers(defs map[string]*openapi2.Header) openapi3.Headers {
 	return headers
 }
 
-func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.SchemaRef {
+func ToV3Schemas(defs map[string]*openapi2.SchemaRef) map[string]*openapi3.SchemaRef {
 	schemas := make(map[string]*openapi3.SchemaRef, len(defs))
 	for name, schema := range defs {
 		schemas[name] = ToV3SchemaRef(schema)
@@ -464,34 +467,84 @@ func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.Schem
 	return schemas
 }
 
-func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
+func ToV3SchemaRef(schema *openapi2.SchemaRef) *openapi3.SchemaRef {
+	if schema == nil {
+		return &openapi3.SchemaRef{}
+	}
+
 	if ref := schema.Ref; ref != "" {
 		return &openapi3.SchemaRef{Ref: ToV3Ref(ref)}
 	}
+
 	if schema.Value == nil {
-		return schema
+		return &openapi3.SchemaRef{
+			Extensions: schema.Extensions,
+		}
+	}
+
+	v3Schema := &openapi3.Schema{
+		Extensions:           schema.Extensions,
+		Type:                 schema.Value.Type,
+		Title:                schema.Value.Title,
+		Format:               schema.Value.Format,
+		Description:          schema.Value.Description,
+		Enum:                 schema.Value.Enum,
+		Default:              schema.Value.Default,
+		Example:              schema.Value.Example,
+		ExternalDocs:         schema.Value.ExternalDocs,
+		UniqueItems:          schema.Value.UniqueItems,
+		ExclusiveMin:         schema.Value.ExclusiveMin,
+		ExclusiveMax:         schema.Value.ExclusiveMax,
+		ReadOnly:             schema.Value.ReadOnly,
+		WriteOnly:            schema.Value.WriteOnly,
+		AllowEmptyValue:      schema.Value.AllowEmptyValue,
+		Deprecated:           schema.Value.Deprecated,
+		XML:                  schema.Value.XML,
+		Min:                  schema.Value.Min,
+		Max:                  schema.Value.Max,
+		MultipleOf:           schema.Value.MultipleOf,
+		MinLength:            schema.Value.MinLength,
+		MaxLength:            schema.Value.MaxLength,
+		Pattern:              schema.Value.Pattern,
+		MinItems:             schema.Value.MinItems,
+		MaxItems:             schema.Value.MaxItems,
+		Required:             schema.Value.Required,
+		MinProps:             schema.Value.MinProps,
+		MaxProps:             schema.Value.MaxProps,
+		AllOf:                make(openapi3.SchemaRefs, len(schema.Value.AllOf)),
+		Properties:           make(openapi3.Schemas),
+		AdditionalProperties: schema.Value.AdditionalProperties,
+	}
+
+	if schema.Value.Discriminator != "" {
+		v3Schema.Discriminator = &openapi3.Discriminator{
+			PropertyName: schema.Value.Discriminator,
+		}
 	}
+
 	if schema.Value.Items != nil {
-		schema.Value.Items = ToV3SchemaRef(schema.Value.Items)
+		v3Schema.Items = ToV3SchemaRef(schema.Value.Items)
 	}
 	if schema.Value.Type.Is("file") {
-		schema.Value.Format, schema.Value.Type = "binary", &openapi3.Types{"string"}
+		v3Schema.Format, v3Schema.Type = "binary", &openapi3.Types{"string"}
 	}
 	for k, v := range schema.Value.Properties {
-		schema.Value.Properties[k] = ToV3SchemaRef(v)
-	}
-	if v := schema.Value.AdditionalProperties.Schema; v != nil {
-		schema.Value.AdditionalProperties.Schema = ToV3SchemaRef(v)
+		v3Schema.Properties[k] = ToV3SchemaRef(v)
 	}
 	for i, v := range schema.Value.AllOf {
-		schema.Value.AllOf[i] = ToV3SchemaRef(v)
+		v3Schema.AllOf[i] = ToV3SchemaRef(v)
 	}
 	if val, ok := schema.Value.Extensions["x-nullable"]; ok {
-		schema.Value.Nullable, _ = val.(bool)
-		delete(schema.Value.Extensions, "x-nullable")
+		if nullable, valid := val.(bool); valid {
+			v3Schema.Nullable = nullable
+			delete(v3Schema.Extensions, "x-nullable")
+		}
 	}
 
-	return schema
+	return &openapi3.SchemaRef{
+		Extensions: schema.Extensions,
+		Value:      v3Schema,
+	}
 }
 
 var ref2To3 = map[string]string{
@@ -742,8 +795,8 @@ func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, c
 	return
 }
 
-func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi3.SchemaRef, map[string]*openapi2.Parameter) {
-	v2Defs := make(map[string]*openapi3.SchemaRef)
+func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.Components) (map[string]*openapi2.SchemaRef, map[string]*openapi2.Parameter) {
+	v2Defs := make(map[string]*openapi2.SchemaRef)
 	v2Params := make(map[string]*openapi2.Parameter)
 	for name, schema := range schemas {
 		schemaConv, parameterConv := FromV3SchemaRef(schema, components)
@@ -759,7 +812,7 @@ func FromV3Schemas(schemas map[string]*openapi3.SchemaRef, components *openapi3.
 	return v2Defs, v2Params
 }
 
-func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi3.SchemaRef, *openapi2.Parameter) {
+func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components) (*openapi2.SchemaRef, *openapi2.Parameter) {
 	if ref := schema.Ref; ref != "" {
 		name := getParameterNameFromNewRef(ref)
 		if val, ok := components.Schemas[name]; ok {
@@ -769,10 +822,12 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 			}
 		}
 
-		return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}, nil
+		return &openapi2.SchemaRef{Ref: FromV3Ref(ref)}, nil
 	}
 	if schema.Value == nil {
-		return schema, nil
+		return &openapi2.SchemaRef{
+			Extensions: schema.Extensions,
+		}, nil
 	}
 
 	if schema.Value != nil {
@@ -789,19 +844,19 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 				}
 			}
 			return nil, &openapi2.Parameter{
-				In:              "formData",
-				Name:            originalName,
-				Description:     schema.Value.Description,
-				Type:            paramType,
-				Enum:            schema.Value.Enum,
-				Minimum:         schema.Value.Min,
-				Maximum:         schema.Value.Max,
-				ExclusiveMin:    schema.Value.ExclusiveMin,
-				ExclusiveMax:    schema.Value.ExclusiveMax,
-				MinLength:       schema.Value.MinLength,
-				MaxLength:       schema.Value.MaxLength,
-				Default:         schema.Value.Default,
-				Items:           schema.Value.Items,
+				In:           "formData",
+				Name:         originalName,
+				Description:  schema.Value.Description,
+				Type:         paramType,
+				Enum:         schema.Value.Enum,
+				Minimum:      schema.Value.Min,
+				Maximum:      schema.Value.Max,
+				ExclusiveMin: schema.Value.ExclusiveMin,
+				ExclusiveMax: schema.Value.ExclusiveMax,
+				MinLength:    schema.Value.MinLength,
+				MaxLength:    schema.Value.MaxLength,
+				Default:      schema.Value.Default,
+				// Items:           schema.Value.Items,
 				MinItems:        schema.Value.MinItems,
 				MaxItems:        schema.Value.MaxItems,
 				AllowEmptyValue: schema.Value.AllowEmptyValue,
@@ -812,32 +867,72 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 			}
 		}
 	}
+
+	v2Schema := &openapi2.Schema{
+		Extensions:           schema.Value.Extensions,
+		Type:                 schema.Value.Type,
+		Title:                schema.Value.Title,
+		Format:               schema.Value.Format,
+		Description:          schema.Value.Description,
+		Enum:                 schema.Value.Enum,
+		Default:              schema.Value.Default,
+		Example:              schema.Value.Example,
+		ExternalDocs:         schema.Value.ExternalDocs,
+		UniqueItems:          schema.Value.UniqueItems,
+		ExclusiveMin:         schema.Value.ExclusiveMin,
+		ExclusiveMax:         schema.Value.ExclusiveMax,
+		ReadOnly:             schema.Value.ReadOnly,
+		WriteOnly:            schema.Value.WriteOnly,
+		AllowEmptyValue:      schema.Value.AllowEmptyValue,
+		Deprecated:           schema.Value.Deprecated,
+		XML:                  schema.Value.XML,
+		Min:                  schema.Value.Min,
+		Max:                  schema.Value.Max,
+		MultipleOf:           schema.Value.MultipleOf,
+		MinLength:            schema.Value.MinLength,
+		MaxLength:            schema.Value.MaxLength,
+		Pattern:              schema.Value.Pattern,
+		MinItems:             schema.Value.MinItems,
+		MaxItems:             schema.Value.MaxItems,
+		Required:             schema.Value.Required,
+		MinProps:             schema.Value.MinProps,
+		MaxProps:             schema.Value.MaxProps,
+		Properties:           make(openapi2.Schemas),
+		AllOf:                make(openapi2.SchemaRefs, len(schema.Value.AllOf)),
+		AdditionalProperties: schema.Value.AdditionalProperties,
+	}
+
 	if v := schema.Value.Items; v != nil {
-		schema.Value.Items, _ = FromV3SchemaRef(v, components)
+		v2Schema.Items, _ = FromV3SchemaRef(v, components)
 	}
+
 	keys := make([]string, 0, len(schema.Value.Properties))
 	for k := range schema.Value.Properties {
 		keys = append(keys, k)
 	}
 	sort.Strings(keys)
 	for _, key := range keys {
-		schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components)
-	}
-	if v := schema.Value.AdditionalProperties.Schema; v != nil {
-		schema.Value.AdditionalProperties.Schema, _ = FromV3SchemaRef(v, components)
+		property, _ := FromV3SchemaRef(schema.Value.Properties[key], components)
+		if property != nil {
+			v2Schema.Properties[key] = property
+		}
 	}
+
 	for i, v := range schema.Value.AllOf {
-		schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
+		v2Schema.AllOf[i], _ = FromV3SchemaRef(v, components)
 	}
 	if schema.Value.PermitsNull() {
 		schema.Value.Nullable = false
 		if schema.Value.Extensions == nil {
-			schema.Value.Extensions = make(map[string]any)
+			v2Schema.Extensions = make(map[string]any)
 		}
-		schema.Value.Extensions["x-nullable"] = true
+		v2Schema.Extensions["x-nullable"] = true
 	}
 
-	return schema, nil
+	return &openapi2.SchemaRef{
+		Extensions: schema.Extensions,
+		Value:      v2Schema,
+	}, nil
 }
 
 func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) openapi2.SecurityRequirements {
@@ -906,6 +1001,11 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
 				break
 			}
 		}
+
+		var v2Items *openapi2.SchemaRef
+		if val.Items != nil {
+			v2Items, _ = FromV3SchemaRef(val.Items, nil)
+		}
 		parameter := &openapi2.Parameter{
 			Name:         propName,
 			Description:  val.Description,
@@ -918,7 +1018,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
 			MinLength:    val.MinLength,
 			MaxLength:    val.MaxLength,
 			Default:      val.Default,
-			Items:        val.Items,
+			Items:        v2Items,
 			MinItems:     val.MinItems,
 			MaxItems:     val.MaxItems,
 			Maximum:      val.Max,
@@ -1029,12 +1129,12 @@ func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components
 		Extensions:  stripNonExtensions(parameter.Extensions),
 	}
 	if schemaRef := parameter.Schema; schemaRef != nil {
-		schemaRef, _ = FromV3SchemaRef(schemaRef, components)
-		if ref := schemaRef.Ref; ref != "" {
-			result.Schema = &openapi3.SchemaRef{Ref: FromV3Ref(ref)}
+		schemaRefV2, _ := FromV3SchemaRef(schemaRef, components)
+		if ref := schemaRefV2.Ref; ref != "" {
+			result.Schema = &openapi2.SchemaRef{Ref: FromV3Ref(ref)}
 			return result, nil
 		}
-		schema := schemaRef.Value
+		schema := schemaRefV2.Value
 		result.Type = schema.Type
 		result.Format = schema.Format
 		result.Enum = schema.Enum


Test patch:
diff --git a/openapi2/issues1010_test.go b/openapi2/issues1010_test.go
new file mode 100644
index 0000000..2f84dc9
--- /dev/null
+++ b/openapi2/issues1010_test.go
@@ -0,0 +1,98 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue1010(t *testing.T) {
+	v2 := []byte(`
+{
+    "basePath": "/v2",
+    "host": "test.example.com",
+    "info": {
+        "title": "MyAPI",
+        "version": "0.1",
+        "x-info": "info extension"
+    },
+    "paths": {
+        "/foo": {
+            "get": {
+                "operationId": "getFoo",
+                "responses": {
+                    "200": {
+                        "description": "returns all information",
+                        "schema": {
+                            "$ref": "#/definitions/Pet"
+                        }
+                    },
+                    "default": {
+                        "description": "OK"
+                    }
+                },
+                "summary": "get foo"
+            }
+        }
+    },
+    "schemes": [
+        "http"
+    ],
+    "swagger": "2.0",
+    "definitions": {
+        "Pet": {
+            "type": "object",
+            "required": ["petType"],
+            "properties": {
+                "petType": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "age": {
+                    "type": "integer"
+                }
+            },
+            "discriminator": "petType"
+        },
+        "Dog": {
+            "allOf": [
+                {
+                    "$ref": "#/definitions/Pet"
+                },
+                {
+                    "type": "object",
+                    "properties": {
+                        "breed": {
+                            "type": "string"
+                        }
+                    }
+                }
+            ]
+        },
+        "Cat": {
+            "allOf": [
+                {
+                    "$ref": "#/definitions/Pet"
+                },
+                {
+                    "type": "object",
+                    "properties": {
+                        "color": {
+                            "type": "string"
+                        }
+                    }
+                }
+            ]
+        }
+    }
+}
+`)
+
+	var doc2 T
+	err := json.Unmarshal(v2, &doc2)
+	require.NoError(t, err)
+	require.Equal(t, "petType", doc2.Definitions["Pet"].Value.Discriminator)
+}