Repository: getkin/kin-openapi
Pull request #239 (2020-07-17 20:09:50)

various fixes mainly to openapi2<->openapi3 conversion
Fixes https://github.com/getkin/kin-openapi/pull/237#issuecomment-659446439

Superseeds (closes) https://github.com/getkin/kin-openapi/pull/238


Patch:
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index f6b113f..2477752 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -17,6 +17,7 @@ import (
 
 type Swagger struct {
 	openapi3.ExtensionProps
+	Swagger             string                         `json:"swagger"`
 	Info                openapi3.Info                  `json:"info"`
 	ExternalDocs        *openapi3.ExternalDocs         `json:"externalDocs,omitempty"`
 	Schemes             []string                       `json:"schemes,omitempty"`
@@ -168,27 +169,30 @@ type Parameters []*Parameter
 
 type Parameter struct {
 	openapi3.ExtensionProps
-	Ref          string              `json:"$ref,omitempty"`
-	In           string              `json:"in,omitempty"`
-	Name         string              `json:"name,omitempty"`
-	Description  string              `json:"description,omitempty"`
-	Required     bool                `json:"required,omitempty"`
-	UniqueItems  bool                `json:"uniqueItems,omitempty"`
-	ExclusiveMin bool                `json:"exclusiveMinimum,omitempty"`
-	ExclusiveMax bool                `json:"exclusiveMaximum,omitempty"`
-	Schema       *openapi3.SchemaRef `json:"schema,omitempty"`
-	Type         string              `json:"type,omitempty"`
-	Format       string              `json:"format,omitempty"`
-	Enum         []interface{}       `json:"enum,omitempty"`
-	Minimum      *float64            `json:"minimum,omitempty"`
-	Maximum      *float64            `json:"maximum,omitempty"`
-	MinLength    uint64              `json:"minLength,omitempty"`
-	MaxLength    *uint64             `json:"maxLength,omitempty"`
-	Pattern      string              `json:"pattern,omitempty"`
-	Items        *openapi3.SchemaRef `json:"items,omitempty"`
-	MinItems     uint64              `json:"minItems,omitempty"`
-	MaxItems     *uint64             `json:"maxItems,omitempty"`
-	Default      interface{}         `json:"default,omitempty"`
+	Ref              string              `json:"$ref,omitempty"`
+	In               string              `json:"in,omitempty"`
+	Name             string              `json:"name,omitempty"`
+	Description      string              `json:"description,omitempty"`
+	CollectionFormat string              `json:"collectionFormat,omitempty"`
+	Type             string              `json:"type,omitempty"`
+	Format           string              `json:"format,omitempty"`
+	Pattern          string              `json:"pattern,omitempty"`
+	AllowEmptyValue  bool                `json:"allowEmptyValue,omitempty"`
+	Required         bool                `json:"required,omitempty"`
+	UniqueItems      bool                `json:"uniqueItems,omitempty"`
+	ExclusiveMin     bool                `json:"exclusiveMinimum,omitempty"`
+	ExclusiveMax     bool                `json:"exclusiveMaximum,omitempty"`
+	Schema           *openapi3.SchemaRef `json:"schema,omitempty"`
+	Items            *openapi3.SchemaRef `json:"items,omitempty"`
+	Enum             []interface{}       `json:"enum,omitempty"`
+	MultipleOf       *float64            `json:"multipleOf,omitempty"`
+	Minimum          *float64            `json:"minimum,omitempty"`
+	Maximum          *float64            `json:"maximum,omitempty"`
+	MaxLength        *uint64             `json:"maxLength,omitempty"`
+	MaxItems         *uint64             `json:"maxItems,omitempty"`
+	MinLength        uint64              `json:"minLength,omitempty"`
+	MinItems         uint64              `json:"minItems,omitempty"`
+	Default          interface{}         `json:"default,omitempty"`
 }
 
 func (parameter *Parameter) MarshalJSON() ([]byte, error) {
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 408e3f5..97b00eb 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -16,7 +16,7 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 	stripNonCustomExtensions(swagger.Extensions)
 
 	result := &openapi3.Swagger{
-		OpenAPI:        "3.0.2",
+		OpenAPI:        "3.0.3",
 		Info:           &swagger.Info,
 		Components:     openapi3.Components{},
 		Tags:           swagger.Tags,
@@ -24,7 +24,7 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 		ExternalDocs:   swagger.ExternalDocs,
 	}
 
-	if host := swagger.Host; len(host) > 0 {
+	if host := swagger.Host; host != "" {
 		schemes := swagger.Schemes
 		if len(schemes) == 0 {
 			schemes = []string{"https://"}
@@ -53,18 +53,17 @@ func ToV3Swagger(swagger *openapi2.Swagger) (*openapi3.Swagger, error) {
 	}
 
 	if parameters := swagger.Parameters; parameters != nil {
-		result.Components.Parameters = make(map[string]*openapi3.ParameterRef)
-		result.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
+		result.Components.Parameters = make(map[string]*openapi3.ParameterRef, len(parameters))
+		result.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef, len(parameters))
 		for k, parameter := range parameters {
-			resultParameter, resultRequestBody, err := ToV3Parameter(parameter)
-			if err != nil {
+			v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
+			switch {
+			case err != nil:
 				return nil, err
-			}
-			if resultParameter != nil {
-				result.Components.Parameters[k] = resultParameter
-			}
-			if resultRequestBody != nil {
-				result.Components.RequestBodies[k] = resultRequestBody
+			case v3RequestBody != nil:
+				result.Components.RequestBodies[k] = v3RequestBody
+			default:
+				result.Components.Parameters[k] = v3Parameter
 			}
 		}
 	}
@@ -119,62 +118,18 @@ func ToV3PathItem(swagger *openapi2.Swagger, pathItem *openapi2.PathItem) (*open
 	}
 	for _, parameter := range pathItem.Parameters {
 		v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
-		if err != nil {
+		switch {
+		case err != nil:
 			return nil, err
+		case v3RequestBody != nil:
+			return nil, errors.New("pathItem must not have a body parameter")
+		default:
+			result.Parameters = append(result.Parameters, v3Parameter)
 		}
-		if v3RequestBody != nil {
-			return nil, errors.New("PathItem shouldn't have a body parameter")
-		}
-		result.Parameters = append(result.Parameters, v3Parameter)
 	}
 	return result, nil
 }
 
-func ToV3RequestBodyFormData(parameters []*openapi2.Parameter) *openapi3.RequestBodyRef {
-	if len(parameters) == 0 || parameters[0].In != "formData" {
-		return nil
-	}
-	schema := &openapi3.Schema{
-		Type:       "object",
-		Properties: make(map[string]*openapi3.SchemaRef, len(parameters)),
-	}
-	for _, parameter := range parameters {
-		if parameter.In != "formData" || parameter.Name == "" {
-			continue
-		}
-		format := parameter.Format
-		typ := parameter.Type
-		if parameter.Type == "file" {
-			format = "binary"
-			typ = "string"
-		}
-		pschema := &openapi3.Schema{
-			Description:    parameter.Description,
-			Type:           typ,
-			ExtensionProps: parameter.ExtensionProps,
-			Format:         format,
-			Enum:           parameter.Enum,
-			Min:            parameter.Minimum,
-			Max:            parameter.Maximum,
-			ExclusiveMin:   parameter.ExclusiveMin,
-			ExclusiveMax:   parameter.ExclusiveMax,
-			MinLength:      parameter.MinLength,
-			MaxLength:      parameter.MaxLength,
-			Default:        parameter.Default,
-			Items:          parameter.Items,
-			MinItems:       parameter.MinItems,
-			MaxItems:       parameter.MaxItems,
-		}
-		schemaRef := openapi3.SchemaRef{
-			Value: pschema,
-		}
-		schema.Properties[parameter.Name] = &schemaRef
-	}
-	return &openapi3.RequestBodyRef{
-		Value: openapi3.NewRequestBody().WithFormDataSchema(schema),
-	}
-}
-
 func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, operation *openapi2.Operation) (*openapi3.Operation, error) {
 	if operation == nil {
 		return nil, nil
@@ -192,22 +147,23 @@ func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, opera
 		result.Security = &resultSecurity
 	}
 
-	requestBodyRef := ToV3RequestBodyFormData(operation.Parameters)
-	if requestBodyRef != nil {
-		result.RequestBody = requestBodyRef
-	} else {
-		for _, parameter := range operation.Parameters {
-			v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
-			if err != nil {
-				return nil, err
-			}
-			if v3RequestBody != nil {
-				result.RequestBody = v3RequestBody
-			} else if v3Parameter != nil {
-				result.Parameters = append(result.Parameters, v3Parameter)
-			}
+	var reqBodies []*openapi3.RequestBodyRef
+	for _, parameter := range operation.Parameters {
+		v3Parameter, v3RequestBody, err := ToV3Parameter(parameter)
+		switch {
+		case err != nil:
+			return nil, err
+		case v3RequestBody != nil:
+			reqBodies = append(reqBodies, v3RequestBody)
+		default:
+			result.Parameters = append(result.Parameters, v3Parameter)
 		}
 	}
+	var err error
+	if result.RequestBody, err = onlyOneReqBodyParam(reqBodies); err != nil {
+		return nil, err
+	}
+
 	if responses := operation.Responses; responses != nil {
 		resultResponses := make(openapi3.Responses, len(responses))
 		for k, response := range responses {
@@ -223,68 +179,159 @@ func ToV3Operation(swagger *openapi2.Swagger, pathItem *openapi2.PathItem, opera
 }
 
 func ToV3Parameter(parameter *openapi2.Parameter) (*openapi3.ParameterRef, *openapi3.RequestBodyRef, error) {
-	if parameter == nil {
-		return nil, nil, nil
-	}
-	if ref := parameter.Ref; len(ref) > 0 {
-		return &openapi3.ParameterRef{
-			Ref: ToV3Ref(ref),
-		}, nil, nil
+	if ref := parameter.Ref; ref != "" {
+		return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil
 	}
 	stripNonCustomExtensions(parameter.Extensions)
-	in := parameter.In
-	if in == "body" {
+
+	switch parameter.In {
+	case "body":
 		result := &openapi3.RequestBody{
 			Description:    parameter.Description,
 			Required:       parameter.Required,
 			ExtensionProps: parameter.ExtensionProps,
 		}
 		if schemaRef := parameter.Schema; schemaRef != nil {
-			// Assume it's JSON
+			// Assuming JSON
 			result.WithJSONSchemaRef(ToV3SchemaRef(schemaRef))
 		}
-		return nil, &openapi3.RequestBodyRef{
-			Value: result,
-		}, nil
+		return nil, &openapi3.RequestBodyRef{Value: result}, nil
+
+	case "formData":
+		format, typ := parameter.Format, parameter.Type
+		if typ == "file" {
+			format, typ = "binary", "string"
+		}
+		reqBodyRef := formDataBody(
+			map[string]*openapi3.SchemaRef{
+				parameter.Name: {
+					Value: &openapi3.Schema{
+						Description:     parameter.Description,
+						Type:            typ,
+						ExtensionProps:  parameter.ExtensionProps,
+						Format:          format,
+						Enum:            parameter.Enum,
+						Min:             parameter.Minimum,
+						Max:             parameter.Maximum,
+						ExclusiveMin:    parameter.ExclusiveMin,
+						ExclusiveMax:    parameter.ExclusiveMax,
+						MinLength:       parameter.MinLength,
+						MaxLength:       parameter.MaxLength,
+						Default:         parameter.Default,
+						Items:           parameter.Items,
+						MinItems:        parameter.MinItems,
+						MaxItems:        parameter.MaxItems,
+						Pattern:         parameter.Pattern,
+						AllowEmptyValue: parameter.AllowEmptyValue,
+						UniqueItems:     parameter.UniqueItems,
+						MultipleOf:      parameter.MultipleOf,
+					},
+				},
+			},
+			map[string]bool{parameter.Name: parameter.Required},
+		)
+		return nil, reqBodyRef, nil
+
+	default:
+		required := parameter.Required
+		if parameter.In == openapi3.ParameterInPath {
+			required = true
+		}
+		result := &openapi3.Parameter{
+			In:             parameter.In,
+			Name:           parameter.Name,
+			Description:    parameter.Description,
+			Required:       required,
+			ExtensionProps: parameter.ExtensionProps,
+			Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{
+				Type:            parameter.Type,
+				Format:          parameter.Format,
+				Enum:            parameter.Enum,
+				Min:             parameter.Minimum,
+				Max:             parameter.Maximum,
+				ExclusiveMin:    parameter.ExclusiveMin,
+				ExclusiveMax:    parameter.ExclusiveMax,
+				MinLength:       parameter.MinLength,
+				MaxLength:       parameter.MaxLength,
+				Default:         parameter.Default,
+				Items:           parameter.Items,
+				MinItems:        parameter.MinItems,
+				MaxItems:        parameter.MaxItems,
+				Pattern:         parameter.Pattern,
+				AllowEmptyValue: parameter.AllowEmptyValue,
+				UniqueItems:     parameter.UniqueItems,
+				MultipleOf:      parameter.MultipleOf,
+			}}),
+		}
+		return &openapi3.ParameterRef{Value: result}, nil, nil
 	}
-	result := &openapi3.Parameter{
-		In:             in,
-		Name:           parameter.Name,
-		Description:    parameter.Description,
-		Required:       parameter.Required,
-		ExtensionProps: parameter.ExtensionProps,
+}
+
+func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool) *openapi3.RequestBodyRef {
+	if len(bodies) != len(reqs) {
+		panic(`request bodies and them being required must match`)
+	}
+	requireds := make([]string, 0, len(reqs))
+	for propName, req := range reqs {
+		if _, ok := bodies[propName]; !ok {
+			panic(`request bodies and them being required must match`)
+		}
+		if req {
+			requireds = append(requireds, propName)
+		}
+	}
+	schema := &openapi3.Schema{
+		Type:       "object",
+		Properties: ToV3Schemas(bodies),
+		Required:   requireds,
 	}
+	return &openapi3.RequestBodyRef{
+		Value: openapi3.NewRequestBody().WithFormDataSchema(schema),
+	}
+}
 
-	if parameter.Type != "" {
-		schema := &openapi3.SchemaRef{
-			Value: &openapi3.Schema{
-				Type:         parameter.Type,
-				Format:       parameter.Format,
-				Enum:         parameter.Enum,
-				Min:          parameter.Minimum,
-				Max:          parameter.Maximum,
-				ExclusiveMin: parameter.ExclusiveMin,
-				ExclusiveMax: parameter.ExclusiveMax,
-				MinLength:    parameter.MinLength,
-				MaxLength:    parameter.MaxLength,
-				Default:      parameter.Default,
-				Items:        parameter.Items,
-				MinItems:     parameter.MinItems,
-				MaxItems:     parameter.MaxItems,
-			},
+func onlyOneReqBodyParam(bodies []*openapi3.RequestBodyRef) (*openapi3.RequestBodyRef, error) {
+	var (
+		body           *openapi3.RequestBodyRef
+		formDataParams map[string]*openapi3.SchemaRef
+		formDataReqs   map[string]bool
+	)
+	for i, requestBodyRef := range bodies {
+		mediaType := requestBodyRef.Value.GetMediaType("multipart/form-data")
+		if mediaType != nil {
+			for name, schemaRef := range mediaType.Schema.Value.Properties {
+				if formDataParams == nil {
+					formDataParams = make(map[string]*openapi3.SchemaRef, len(bodies)-i)
+				}
+				if formDataReqs == nil {
+					formDataReqs = make(map[string]bool, len(bodies)-i)
+				}
+				formDataParams[name] = schemaRef
+				formDataReqs[name] = false
+				for _, req := range mediaType.Schema.Value.Required {
+					if name == req {
+						formDataReqs[name] = true
+					}
+				}
+				break
+			}
+		} else {
+			body = requestBodyRef
 		}
-		result.Schema = ToV3SchemaRef(schema)
 	}
-	return &openapi3.ParameterRef{
-		Value: result,
-	}, nil, nil
+	switch {
+	case len(formDataParams) != 0 && body != nil:
+		return nil, errors.New("body and form parameters cannot exist together for the same operation")
+	case len(formDataParams) != 0:
+		return formDataBody(formDataParams, formDataReqs), nil
+	default:
+		return body, nil
+	}
 }
 
 func ToV3Response(response *openapi2.Response) (*openapi3.ResponseRef, error) {
-	if ref := response.Ref; len(ref) > 0 {
-		return &openapi3.ResponseRef{
-			Ref: ToV3Ref(ref),
-		}, nil
+	if ref := response.Ref; ref != "" {
+		return &openapi3.ResponseRef{Ref: ToV3Ref(ref)}, nil
 	}
 	stripNonCustomExtensions(response.Extensions)
 	result := &openapi3.Response{
@@ -308,10 +355,8 @@ func ToV3Schemas(defs map[string]*openapi3.SchemaRef) map[string]*openapi3.Schem
 }
 
 func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
-	if ref := schema.Ref; len(ref) > 0 {
-		return &openapi3.SchemaRef{
-			Ref: ToV3Ref(ref),
-		}
+	if ref := schema.Ref; ref != "" {
+		return &openapi3.SchemaRef{Ref: ToV3Ref(ref)}
 	}
 	if schema.Value == nil {
 		return schema
@@ -322,8 +367,11 @@ func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
 	for k, v := range schema.Value.Properties {
 		schema.Value.Properties[k] = ToV3SchemaRef(v)
 	}
-	if schema.Value.AdditionalProperties != nil {
-		schema.Value.AdditionalProperties = ToV3SchemaRef(schema.Value.AdditionalProperties)
+	if v := schema.Value.AdditionalProperties; v != nil {
+		schema.Value.AdditionalProperties = ToV3SchemaRef(v)
+	}
+	for i, v := range schema.Value.AllOf {
+		schema.Value.AllOf[i] = ToV3SchemaRef(v)
 	}
 	return schema
 }
@@ -410,6 +458,7 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 	}, nil
 }
 
+// FromV3Swagger converts an OpenAPIv3 spec to an OpenAPIv2 spec
 func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
 	resultResponses, err := FromV3Responses(swagger.Components.Responses)
 	if err != nil {
@@ -418,6 +467,7 @@ func FromV3Swagger(swagger *openapi3.Swagger) (*openapi2.Swagger, error) {
 	stripNonCustomExtensions(swagger.Extensions)
 
 	result := &openapi2.Swagger{
+		Swagger:        "2.0",
 		Info:           *swagger.Info,
 		Definitions:    FromV3Schemas(swagger.Components.Schemas),
 		Responses:      resultResponses,
@@ -508,22 +558,23 @@ func FromV3Schemas(schemas map[string]*openapi3.SchemaRef) map[string]*openapi3.
 }
 
 func FromV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
-	if ref := schema.Ref; len(ref) > 0 {
-		return &openapi3.SchemaRef{
-			Ref: FromV3Ref(ref),
-		}
+	if ref := schema.Ref; ref != "" {
+		return &openapi3.SchemaRef{Ref: FromV3Ref(ref)}
 	}
 	if schema.Value == nil {
 		return schema
 	}
-	if schema.Value.Items != nil {
-		schema.Value.Items = FromV3SchemaRef(schema.Value.Items)
+	if v := schema.Value.Items; v != nil {
+		schema.Value.Items = FromV3SchemaRef(v)
 	}
 	for k, v := range schema.Value.Properties {
 		schema.Value.Properties[k] = FromV3SchemaRef(v)
 	}
-	if schema.Value.AdditionalProperties != nil {
-		schema.Value.AdditionalProperties = FromV3SchemaRef(schema.Value.AdditionalProperties)
+	if v := schema.Value.AdditionalProperties; v != nil {
+		schema.Value.AdditionalProperties = FromV3SchemaRef(v)
+	}
+	for i, v := range schema.Value.AllOf {
+		schema.Value.AllOf[i] = FromV3SchemaRef(v)
 	}
 	return schema
 }
@@ -581,14 +632,21 @@ func FromV3RequestBodyFormData(requestBodyRef *openapi3.RequestBodyRef) openapi2
 		return nil
 	}
 	parameters := openapi2.Parameters{}
-	for prop, schemaRef := range mediaType.Schema.Value.Properties {
+	for propName, schemaRef := range mediaType.Schema.Value.Properties {
 		val := schemaRef.Value
 		typ := val.Type
 		if val.Format == "binary" {
 			typ = "file"
 		}
+		required := false
+		for _, name := range val.Required {
+			if name == propName {
+				required = true
+				break
+			}
+		}
 		parameter := &openapi2.Parameter{
-			Name:           prop,
+			Name:           propName,
 			Description:    val.Description,
 			Type:           typ,
 			In:             "formData",
@@ -605,6 +663,12 @@ func FromV3RequestBodyFormData(requestBodyRef *openapi3.RequestBodyRef) openapi2
 			Maximum:        val.Max,
 			Minimum:        val.Min,
 			Pattern:        val.Pattern,
+			// CollectionFormat: val.CollectionFormat,
+			// Format:          val.Format,
+			AllowEmptyValue: val.AllowEmptyValue,
+			Required:        required,
+			UniqueItems:     val.UniqueItems,
+			MultipleOf:      val.MultipleOf,
 		}
 		parameters = append(parameters, parameter)
 	}
@@ -646,6 +710,11 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
 			result.Parameters = append(result.Parameters, r)
 		}
 	}
+	for _, param := range result.Parameters {
+		if param.Type == "file" {
+			result.Consumes = append(result.Consumes, "multipart/form-data")
+		}
+	}
 	if responses := operation.Responses; responses != nil {
 		resultResponses, err := FromV3Responses(responses)
 		if err != nil {
@@ -657,10 +726,8 @@ func FromV3Operation(swagger *openapi3.Swagger, operation *openapi3.Operation) (
 }
 
 func FromV3RequestBody(swagger *openapi3.Swagger, operation *openapi3.Operation, requestBodyRef *openapi3.RequestBodyRef) (*openapi2.Parameter, error) {
-	if ref := requestBodyRef.Ref; len(ref) > 0 {
-		return &openapi2.Parameter{
-			Ref: FromV3Ref(ref),
-		}, nil
+	if ref := requestBodyRef.Ref; ref != "" {
+		return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil
 	}
 	requestBody := requestBodyRef.Value
 
@@ -680,19 +747,17 @@ func FromV3RequestBody(swagger *openapi3.Swagger, operation *openapi3.Operation,
 		ExtensionProps: requestBody.ExtensionProps,
 	}
 
-	// Add JSON schema
+	// Assuming JSON
 	mediaType := requestBody.GetMediaType("application/json")
 	if mediaType != nil {
-		result.Schema = mediaType.Schema
+		result.Schema = FromV3SchemaRef(mediaType.Schema)
 	}
 	return result, nil
 }
 
 func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
-	if v := ref.Ref; len(v) > 0 {
-		return &openapi2.Parameter{
-			Ref: FromV3Ref(v),
-		}, nil
+	if ref := ref.Ref; ref != "" {
+		return &openapi2.Parameter{Ref: FromV3Ref(ref)}, nil
 	}
 	parameter := ref.Value
 	if parameter == nil {
@@ -723,6 +788,10 @@ func FromV3Parameter(ref *openapi3.ParameterRef) (*openapi2.Parameter, error) {
 		result.Items = schema.Items
 		result.MinItems = schema.MinItems
 		result.MaxItems = schema.MaxItems
+		result.AllowEmptyValue = schema.AllowEmptyValue
+		// result.CollectionFormat = schema.CollectionFormat
+		result.UniqueItems = schema.UniqueItems
+		result.MultipleOf = schema.MultipleOf
 	}
 	return result, nil
 }
@@ -740,10 +809,8 @@ func FromV3Responses(responses map[string]*openapi3.ResponseRef) (map[string]*op
 }
 
 func FromV3Response(ref *openapi3.ResponseRef) (*openapi2.Response, error) {
-	if v := ref.Ref; len(v) > 0 {
-		return &openapi2.Response{
-			Ref: FromV3Ref(v),
-		}, nil
+	if ref := ref.Ref; ref != "" {
+		return &openapi2.Response{Ref: FromV3Ref(ref)}, nil
 	}
 
 	response := ref.Value
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 3b8be81..942ecd9 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -24,9 +24,7 @@ func (mediaType *MediaType) WithSchema(schema *Schema) *MediaType {
 	if schema == nil {
 		mediaType.Schema = nil
 	} else {
-		mediaType.Schema = &SchemaRef{
-			Value: schema,
-		}
+		mediaType.Schema = &SchemaRef{Value: schema}
 	}
 	return mediaType
 }
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 427c376..fba9e34 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -73,10 +73,11 @@ type Schema struct {
 	ExclusiveMin bool `json:"exclusiveMinimum,omitempty" yaml:"exclusiveMinimum,omitempty"`
 	ExclusiveMax bool `json:"exclusiveMaximum,omitempty" yaml:"exclusiveMaximum,omitempty"`
 	// Properties
-	Nullable  bool        `json:"nullable,omitempty" yaml:"nullable,omitempty"`
-	ReadOnly  bool        `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
-	WriteOnly bool        `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
-	XML       interface{} `json:"xml,omitempty" yaml:"xml,omitempty"`
+	Nullable        bool        `json:"nullable,omitempty" yaml:"nullable,omitempty"`
+	ReadOnly        bool        `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
+	WriteOnly       bool        `json:"writeOnly,omitempty" yaml:"writeOnly,omitempty"`
+	AllowEmptyValue bool        `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
+	XML             interface{} `json:"xml,omitempty" yaml:"xml,omitempty"`
 
 	// Number
 	Min        *float64 `json:"minimum,omitempty" yaml:"minimum,omitempty"`
@@ -395,7 +396,7 @@ func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema {
 func (schema *Schema) IsEmpty() bool {
 	if schema.Type != "" || schema.Format != "" || len(schema.Enum) != 0 ||
 		schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax ||
-		!schema.Nullable ||
+		schema.Nullable ||
 		schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil ||
 		schema.MinLength != 0 || schema.MaxLength != nil || schema.Pattern != "" ||
 		schema.MinItems != 0 || schema.MaxItems != nil ||


Test patch:
diff --git a/openapi2/testdata/swagger.json b/openapi2/testdata/swagger.json
index 91484ff..57f75d9 100644
--- a/openapi2/testdata/swagger.json
+++ b/openapi2/testdata/swagger.json
@@ -1,1 +1,1 @@
-{"info":{"title":"Swagger Petstore","description":"This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0.3"},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"schemes":["https","http"],"host":"petstore.swagger.io","basePath":"/v2","paths":{"/pet":{"post":{"summary":"Add a new pet to the store","tags":["pet"],"operationId":"addPet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"summary":"Update an existing pet","tags":["pet"],"operationId":"updatePet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","tags":["pet"],"operationId":"findPetsByStatus","parameters":[{"in":"query","name":"status","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"default":"available","enum":["available","pending","sold"],"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid status value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","tags":["pet"],"operationId":"findPetsByTags","parameters":[{"in":"query","name":"tags","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid tag value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"delete":{"summary":"Deletes a pet","tags":["pet"],"operationId":"deletePet","parameters":[{"in":"header","name":"api_key","type":"string"},{"in":"path","name":"petId","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"get":{"summary":"Find pet by ID","description":"Returns a single pet","tags":["pet"],"operationId":"getPetById","parameters":[{"in":"path","name":"petId","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"api_key":[]}]},"post":{"summary":"Updates a pet in the store with form data","tags":["pet"],"operationId":"updatePetWithForm","parameters":[{"in":"path","name":"petId","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"name","description":"Updated name of the pet","type":"string"},{"in":"formData","name":"status","description":"Updated status of the pet","type":"string"}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"summary":"uploads an image","tags":["pet"],"operationId":"uploadFile","parameters":[{"in":"path","name":"petId","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"additionalMetadata","description":"Additional data to pass to server","type":"string"},{"in":"formData","name":"file","description":"file to upload","type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"consumes":["multipart/form-data"],"produces":["application/json"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","tags":["store"],"operationId":"getInventory","responses":{"200":{"description":"successful operation","schema":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"}}},"produces":["application/json"],"security":[{"api_key":[]}]}},"/store/order":{"post":{"summary":"Place an order for a pet","tags":["store"],"operationId":"placeOrder","parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/store/order/{orderId}":{"delete":{"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","tags":["store"],"operationId":"deleteOrder","parameters":[{"in":"path","name":"orderId","description":"ID of the order that needs to be deleted","required":true,"type":"integer","format":"int64","minimum":1}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value \u003e= 1 and \u003c= 10. Other values will generated exceptions","tags":["store"],"operationId":"getOrderById","parameters":[{"in":"path","name":"orderId","description":"ID of pet that needs to be fetched","required":true,"type":"integer","format":"int64","minimum":1,"maximum":10}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]}},"/user":{"post":{"summary":"Create user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"createUser","parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithArray":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithArrayInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithList":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithListInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/login":{"get":{"summary":"Logs user into the system","tags":["user"],"operationId":"loginUser","parameters":[{"in":"query","name":"username","description":"The user name for login","required":true,"type":"string"},{"in":"query","name":"password","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Expires-After":{"description":"date in UTC when token expires","type":"string"},"X-Rate-Limit":{"description":"calls per hour allowed by the user","type":"integer"}}},"400":{"description":"Invalid username/password supplied"}},"produces":["application/json","application/xml"]}},"/user/logout":{"get":{"summary":"Logs out current logged in user session","tags":["user"],"operationId":"logoutUser","responses":{"default":{"description":"successful operation"}},"produces":["application/json","application/xml"]}},"/user/{username}":{"delete":{"summary":"Delete user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"deleteUser","parameters":[{"in":"path","name":"username","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Get user by user name","tags":["user"],"operationId":"getUserByName","parameters":[{"in":"path","name":"username","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"put":{"summary":"Updated user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"updateUser","parameters":[{"in":"path","name":"username","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}}},"definitions":{"ApiResponse":{"properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"},"type":{"type":"string"}},"type":"object"},"Category":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Category"}},"Order":{"properties":{"complete":{"type":"boolean"},"id":{"format":"int64","type":"integer"},"petId":{"format":"int64","type":"integer"},"quantity":{"format":"int32","type":"integer"},"shipDate":{"format":"date-time","type":"string"},"status":{"description":"Order Status","enum":["placed","approved","delivered"],"type":"string"}},"type":"object","xml":{"name":"Order"}},"Pet":{"properties":{"category":{"$ref":"#/definitions/Category"},"id":{"format":"int64","type":"integer"},"name":{"example":"doggie","type":"string"},"photoUrls":{"items":{"type":"string","xml":{"name":"photoUrl"}},"type":"array","xml":{"wrapped":true}},"status":{"description":"pet status in the store","enum":["available","pending","sold"],"type":"string"},"tags":{"items":{"$ref":"#/definitions/Tag"},"type":"array","xml":{"wrapped":true}}},"required":["name","photoUrls"],"type":"object","xml":{"name":"Pet"}},"Tag":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Tag"}},"User":{"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"id":{"format":"int64","type":"integer"},"lastName":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"description":"User Status","format":"int32","type":"integer"},"username":{"type":"string"}},"type":"object","xml":{"name":"User"}}},"securityDefinitions":{"api_key":{"type":"apiKey","in":"header","name":"api_key"},"petstore_auth":{"type":"oauth2","flow":"implicit","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}]}
\ No newline at end of file
+{"swagger":"2.0","info":{"title":"Swagger Petstore","description":"This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0.3"},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"schemes":["https","http"],"host":"petstore.swagger.io","basePath":"/v2","paths":{"/pet":{"post":{"summary":"Add a new pet to the store","tags":["pet"],"operationId":"addPet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"summary":"Update an existing pet","tags":["pet"],"operationId":"updatePet","parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","tags":["pet"],"operationId":"findPetsByStatus","parameters":[{"in":"query","name":"status","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"default":"available","enum":["available","pending","sold"],"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid status value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","tags":["pet"],"operationId":"findPetsByTags","parameters":[{"in":"query","name":"tags","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"}}],"responses":{"200":{"description":"successful operation","schema":{"items":{"$ref":"#/definitions/Pet"},"type":"array"}},"400":{"description":"Invalid tag value"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"delete":{"summary":"Deletes a pet","tags":["pet"],"operationId":"deletePet","parameters":[{"in":"header","name":"api_key","type":"string"},{"in":"path","name":"petId","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]},"get":{"summary":"Find pet by ID","description":"Returns a single pet","tags":["pet"],"operationId":"getPetById","parameters":[{"in":"path","name":"petId","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"produces":["application/json","application/xml"],"security":[{"api_key":[]}]},"post":{"summary":"Updates a pet in the store with form data","tags":["pet"],"operationId":"updatePetWithForm","parameters":[{"in":"path","name":"petId","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"name","description":"Updated name of the pet","type":"string"},{"in":"formData","name":"status","description":"Updated status of the pet","type":"string"}],"responses":{"405":{"description":"Invalid input"}},"consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"summary":"uploads an image","tags":["pet"],"operationId":"uploadFile","parameters":[{"in":"path","name":"petId","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"in":"formData","name":"additionalMetadata","description":"Additional data to pass to server","type":"string"},{"in":"formData","name":"file","description":"file to upload","type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"consumes":["multipart/form-data"],"produces":["application/json"],"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","tags":["store"],"operationId":"getInventory","responses":{"200":{"description":"successful operation","schema":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"}}},"produces":["application/json"],"security":[{"api_key":[]}]}},"/store/order":{"post":{"summary":"Place an order for a pet","tags":["store"],"operationId":"placeOrder","parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/store/order/{orderId}":{"delete":{"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","tags":["store"],"operationId":"deleteOrder","parameters":[{"in":"path","name":"orderId","description":"ID of the order that needs to be deleted","required":true,"type":"integer","format":"int64","minimum":1}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value \u003e= 1 and \u003c= 10. Other values will generated exceptions","tags":["store"],"operationId":"getOrderById","parameters":[{"in":"path","name":"orderId","description":"ID of pet that needs to be fetched","required":true,"type":"integer","format":"int64","minimum":1,"maximum":10}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}},"produces":["application/json","application/xml"]}},"/user":{"post":{"summary":"Create user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"createUser","parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithArray":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithArrayInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/createWithList":{"post":{"summary":"Creates list of users with given input array","tags":["user"],"operationId":"createUsersWithListInput","parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"items":{"$ref":"#/definitions/User"},"type":"array"}}],"responses":{"default":{"description":"successful operation"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}},"/user/login":{"get":{"summary":"Logs user into the system","tags":["user"],"operationId":"loginUser","parameters":[{"in":"query","name":"username","description":"The user name for login","required":true,"type":"string"},{"in":"query","name":"password","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Expires-After":{"description":"date in UTC when token expires","type":"string"},"X-Rate-Limit":{"description":"calls per hour allowed by the user","type":"integer"}}},"400":{"description":"Invalid username/password supplied"}},"produces":["application/json","application/xml"]}},"/user/logout":{"get":{"summary":"Logs out current logged in user session","tags":["user"],"operationId":"logoutUser","responses":{"default":{"description":"successful operation"}},"produces":["application/json","application/xml"]}},"/user/{username}":{"delete":{"summary":"Delete user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"deleteUser","parameters":[{"in":"path","name":"username","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"get":{"summary":"Get user by user name","tags":["user"],"operationId":"getUserByName","parameters":[{"in":"path","name":"username","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}},"produces":["application/json","application/xml"]},"put":{"summary":"Updated user","description":"This can only be done by the logged in user.","tags":["user"],"operationId":"updateUser","parameters":[{"in":"path","name":"username","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}},"consumes":["application/json"],"produces":["application/json","application/xml"]}}},"definitions":{"ApiResponse":{"properties":{"code":{"format":"int32","type":"integer"},"message":{"type":"string"},"type":{"type":"string"}},"type":"object"},"Category":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Category"}},"Order":{"properties":{"complete":{"type":"boolean"},"id":{"format":"int64","type":"integer"},"petId":{"format":"int64","type":"integer"},"quantity":{"format":"int32","type":"integer"},"shipDate":{"format":"date-time","type":"string"},"status":{"description":"Order Status","enum":["placed","approved","delivered"],"type":"string"}},"type":"object","xml":{"name":"Order"}},"Pet":{"properties":{"category":{"$ref":"#/definitions/Category"},"id":{"format":"int64","type":"integer"},"name":{"example":"doggie","type":"string"},"photoUrls":{"items":{"type":"string","xml":{"name":"photoUrl"}},"type":"array","xml":{"wrapped":true}},"status":{"description":"pet status in the store","enum":["available","pending","sold"],"type":"string"},"tags":{"items":{"$ref":"#/definitions/Tag"},"type":"array","xml":{"wrapped":true}}},"required":["name","photoUrls"],"type":"object","xml":{"name":"Pet"}},"Tag":{"properties":{"id":{"format":"int64","type":"integer"},"name":{"type":"string"}},"type":"object","xml":{"name":"Tag"}},"User":{"properties":{"email":{"type":"string"},"firstName":{"type":"string"},"id":{"format":"int64","type":"integer"},"lastName":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"description":"User Status","format":"int32","type":"integer"},"username":{"type":"string"}},"type":"object","xml":{"name":"User"}}},"securityDefinitions":{"api_key":{"type":"apiKey","in":"header","name":"api_key"},"petstore_auth":{"type":"oauth2","flow":"implicit","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}]}
\ No newline at end of file
diff --git a/openapi2conv/issue187_test.go b/openapi2conv/issue187_test.go
index 0fccf9d..979866c 100644
--- a/openapi2conv/issue187_test.go
+++ b/openapi2conv/issue187_test.go
@@ -7,10 +7,11 @@ import (
 
 	"github.com/getkin/kin-openapi/openapi2"
 	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/ghodss/yaml"
 	"github.com/stretchr/testify/require"
 )
 
-func v2v3(spec2 []byte) (doc3 *openapi3.Swagger, err error) {
+func v2v3JSON(spec2 []byte) (doc3 *openapi3.Swagger, err error) {
 	var doc2 openapi2.Swagger
 	if err = json.Unmarshal(spec2, &doc2); err != nil {
 		return
@@ -19,6 +20,15 @@ func v2v3(spec2 []byte) (doc3 *openapi3.Swagger, err error) {
 	return
 }
 
+func v2v3YAML(spec2 []byte) (doc3 *openapi3.Swagger, err error) {
+	var doc2 openapi2.Swagger
+	if err = yaml.Unmarshal(spec2, &doc2); err != nil {
+		return
+	}
+	doc3, err = ToV3Swagger(&doc2)
+	return
+}
+
 func TestIssue187(t *testing.T) {
 	spec := `
 {
@@ -87,12 +97,71 @@ func TestIssue187(t *testing.T) {
   }
 }
 `
-	doc3, err := v2v3([]byte(spec))
+	doc3, err := v2v3JSON([]byte(spec))
 	require.NoError(t, err)
 
 	spec3, err := json.Marshal(doc3)
 	require.NoError(t, err)
-	const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.2","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}`
+	const expected = `{"components":{"schemas":{"model.ProductSearchAttributeRequest":{"properties":{"filterField":{"type":"string"},"filterKey":{"type":"string"},"type":{"type":"string"},"values":{"$ref":"#/components/schemas/model.ProductSearchAttributeValueRequest"}},"title":"model.ProductSearchAttributeRequest","type":"object"},"model.ProductSearchAttributeValueRequest":{"properties":{"imageUrl":{"type":"string"},"text":{"type":"string"}},"title":"model.ProductSearchAttributeValueRequest","type":"object"}}},"info":{"contact":{"email":"test@test.com","name":"Test"},"description":"Test Golang Application","title":"Test","version":"1.0"},"openapi":"3.0.3","paths":{"/me":{"get":{"operationId":"someTest","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/model.ProductSearchAttributeRequest"}}},"description":"successful operation"}},"summary":"Some test","tags":["probe"]}}}}`
+	require.Equal(t, string(spec3), expected)
+
+	err = doc3.Validate(context.Background())
+	require.NoError(t, err)
+}
+
+func TestIssue237(t *testing.T) {
+	spec := `
+swagger: '2.0'
+info:
+  version: 1.0.0
+  title: title
+paths:
+  /test:
+    get:
+      parameters:
+      - in: body
+        schema:
+          $ref: '#/definitions/TestRef'
+      responses:
+        '200':
+          description: description
+definitions:
+  TestRef:
+    type: object
+    allOf:
+    - $ref: '#/definitions/TestRef2'
+  TestRef2:
+    type: object
+`
+	doc3, err := v2v3YAML([]byte(spec))
+	require.NoError(t, err)
+
+	spec3, err := yaml.Marshal(doc3)
+	require.NoError(t, err)
+	const expected = `components:
+  schemas:
+    TestRef:
+      allOf:
+      - $ref: '#/components/schemas/TestRef2'
+      type: object
+    TestRef2:
+      type: object
+info:
+  title: title
+  version: 1.0.0
+openapi: 3.0.3
+paths:
+  /test:
+    get:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/TestRef'
+      responses:
+        "200":
+          description: description
+`
 	require.Equal(t, string(spec3), expected)
 
 	err = doc3.Validate(context.Background())
diff --git a/openapi2conv/openapi2_conv_test.go b/openapi2conv/openapi2_conv_test.go
index 58191ff..8ccc32c 100644
--- a/openapi2conv/openapi2_conv_test.go
+++ b/openapi2conv/openapi2_conv_test.go
@@ -1,6 +1,7 @@
 package openapi2conv
 
 import (
+	"context"
 	"encoding/json"
 	"testing"
 
@@ -10,135 +11,168 @@ import (
 )
 
 func TestConvOpenAPIV3ToV2(t *testing.T) {
-	var swagger3 openapi3.Swagger
-	err := json.Unmarshal([]byte(exampleV3), &swagger3)
+	var doc3 openapi3.Swagger
+	err := json.Unmarshal([]byte(exampleV3), &doc3)
 	require.NoError(t, err)
+	{
+		// Refs need resolving before we can Validate
+		sl := openapi3.NewSwaggerLoader()
+		err = sl.ResolveRefsIn(&doc3, nil)
+		require.NoError(t, err)
+		err = doc3.Validate(context.Background())
+		require.NoError(t, err)
+	}
 
-	actualV2, err := FromV3Swagger(&swagger3)
+	spec2, err := FromV3Swagger(&doc3)
 	require.NoError(t, err)
-	data, err := json.Marshal(actualV2)
+	data, err := json.Marshal(spec2)
 	require.NoError(t, err)
 	require.JSONEq(t, exampleV2, string(data))
 }
 
 func TestConvOpenAPIV2ToV3(t *testing.T) {
-	var swagger2 openapi2.Swagger
-	err := json.Unmarshal([]byte(exampleV2), &swagger2)
+	var doc2 openapi2.Swagger
+	err := json.Unmarshal([]byte(exampleV2), &doc2)
 	require.NoError(t, err)
 
-	actualV3, err := ToV3Swagger(&swagger2)
+	spec3, err := ToV3Swagger(&doc2)
 	require.NoError(t, err)
-	data, err := json.Marshal(actualV3)
+	err = spec3.Validate(context.Background())
+	require.NoError(t, err)
+	data, err := json.Marshal(spec3)
 	require.NoError(t, err)
 	require.JSONEq(t, exampleV3, string(data))
 }
 
 const exampleV2 = `
 {
-  "x-root": "root extension 1",
-  "x-root2": "root extension 2",
-  "info": {"title":"MyAPI","version":"0.1","x-info":"info extension"},
-  "schemes": ["https"],
-  "host": "test.example.com",
+  "swagger": "2.0",
   "basePath": "/v2",
+  "definitions": {
+    "Error": {
+      "description": "Error response.",
+      "properties": {
+        "message": {
+          "type": "string"
+        }
+      },
+      "required": [
+        "message"
+      ],
+      "type": "object"
+    },
+    "Item": {
+      "additionalProperties": true,
+      "properties": {
+        "foo": {
+          "type": "string"
+        },
+        "quux": {
+          "$ref": "#/definitions/ItemExtension"
+        }
+      },
+      "type": "object"
+    },
+    "ItemExtension": {
+      "description": "It could be anything.",
+      "type": "boolean"
+    }
+  },
   "externalDocs": {
-    "url": "https://example/doc/",
-    "description": "Example Documentation"
+    "description": "Example Documentation",
+    "url": "https://example/doc/"
   },
-  "tags": [
-    {
-      "name": "Example",
-      "description": "An example tag."
+  "host": "test.example.com",
+  "info": {
+    "title": "MyAPI",
+    "version": "0.1",
+    "x-info": "info extension"
+  },
+  "parameters": {
+    "banana": {
+      "in": "path",
+      "name": "banana",
+      "required": true,
+      "type": "string"
     }
-  ],
+  },
   "paths": {
     "/another/{banana}/{id}": {
-        "parameters": [
-		  {
-            "$ref": "#/parameters/banana"
-          },
-          {
-            "in": "path",
-            "name": "id",
-			"type": "integer",
-			"required": true
-          }
-		]
+      "parameters": [
+        {
+          "$ref": "#/parameters/banana"
+        },
+        {
+          "in": "path",
+          "name": "id",
+          "required": true,
+          "type": "integer"
+        }
+      ]
     },
     "/example": {
-      "x-path": "path extension 1",
-      "x-path2": "path extension 2",
-      "delete": {
-        "x-operation": "operation extension 1",
-        "description": "example delete",
+      "get": {
+        "description": "example get",
         "responses": {
-          "default": {
-            "description": "default response"
-          },
           "403": {
             "$ref": "#/responses/ForbiddenError"
           },
           "404": {
             "description": "404 response"
+          },
+          "default": {
+            "description": "default response"
           }
-        }
+        },
+        "x-operation": "operation extension 1"
       },
-      "get": {
-        "operationId": "example-get",
-        "summary": "example get",
-        "description": "example get",
-        "tags": [
-          "Example"
-        ],
+      "delete": {
+        "description": "example delete",
+        "operationId": "example-delete",
         "parameters": [
           {
-            "x-parameter": "parameter extension 1",
             "in": "query",
-            "name": "x"
+            "name": "x",
+            "type": "string",
+            "x-parameter": "parameter extension 1"
           },
           {
-            "in": "query",
-            "name": "y",
+            "default": 250,
             "description": "The y parameter",
-            "type": "integer",
-            "minimum": 1,
+            "in": "query",
             "maximum": 10000,
-            "default": 250
+            "minimum": 1,
+            "name": "y",
+            "type": "integer"
           },
           {
-            "in": "query",
-            "name": "bbox",
             "description": "Only return results that intersect the provided bounding box.",
-            "maxItems": 4,
-            "minItems": 4,
-            "type": "array",
+            "in": "query",
             "items": {
               "type": "number"
-            }
-          },
-          {
-            "in": "body",
-            "x-requestBody": "requestbody extension 1",
-            "name": "body",
-            "schema": {}
+            },
+            "maxItems": 4,
+            "minItems": 4,
+            "name": "bbox",
+            "type": "array"
           }
         ],
         "responses": {
           "200": {
             "description": "ok",
             "schema": {
-              "type": "array",
               "items": {
                 "$ref": "#/definitions/Item"
-              }
+              },
+              "type": "array"
             }
           },
-          "default": {
-            "x-response": "response extension 1",
-            "description": "default response"
-          },
           "404": {
             "description": "404 response"
+          },
+          "default": {
+            "description": "default response",
+            "x-response": "response extension 1"
           }
         },
         "security": [
@@ -149,43 +183,86 @@ const exampleV2 = `
             ],
             "get_security_1": []
           }
+        ],
+        "summary": "example get",
+        "tags": [
+          "Example"
         ]
       },
       "head": {
         "description": "example head",
-        "responses": {}
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
+      },
+      "options": {
+        "description": "example options",
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "patch": {
         "description": "example patch",
-        "responses": {}
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "schema": {
+              "allOf": [{"$ref": "#/definitions/Item"}]
+            },
+            "x-requestBody": "requestbody extension 1"
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "post": {
+        "consumes": ["multipart/form-data"],
         "description": "example post",
-        "responses": {},
         "parameters": [
           {
+            "description": "File Id",
+            "in": "query",
+            "name": "id",
+            "type": "integer"
+          },
+          {
+            "description": "param description",
             "in": "formData",
             "name": "fileUpload",
             "type": "file",
-            "description": "param description",
             "x-mimetype": "text/plain"
           },
           {
+            "description": "Description of file contents",
             "in": "formData",
-            "name":"note",
-            "type": "integer",
-            "description": "Description of file contents"
+            "name": "note",
+            "type": "integer"
           }
-        ]
+        ],
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "put": {
         "description": "example put",
-        "responses": {}
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
-      "options": {
-        "description": "example options",
-        "responses": {}
-      }
+      "x-path": "path extension 1",
+      "x-path2": "path extension 2"
     }
   },
   "responses": {
@@ -196,40 +273,9 @@ const exampleV2 = `
       }
     }
   },
-  "definitions": {
-    "Item": {
-      "type": "object",
-      "properties": {
-        "foo": {
-          "type": "string"
-        }
-      },
-	  "additionalProperties": {
-        "$ref": "#/definitions/ItemExtension"
-      }
-    },
-    "ItemExtension": {
-        "description": "It could be anything."
-    },
-    "Error": {
-      "description": "Error response.",
-      "type": "object",
-      "required": [
-        "message"
-      ],
-      "properties": {
-        "message": {
-          "type": "string"
-        }
-      }
-    }
-  },
-  "parameters": {
-    "banana": {
-	  "in": "path",
-      "type": "string"
-    }
-  },
+  "schemes": [
+    "https"
+  ],
   "security": [
     {
       "default_security_0": [
@@ -238,21 +284,31 @@ const exampleV2 = `
       ],
       "default_security_1": []
     }
-  ]
+  ],
+  "tags": [
+    {
+      "description": "An example tag.",
+      "name": "Example"
+    }
+  ],
+  "x-root": "root extension 1",
+  "x-root2": "root extension 2"
 }
 `
 
 const exampleV3 = `
 {
-  "x-root": "root extension 1",
-  "x-root2": "root extension 2",
-  "openapi": "3.0.2",
-  "info": {"title":"MyAPI","version":"0.1","x-info":"info extension"},
-  "externalDocs": {
-    "url": "https://example/doc/",
-    "description": "Example Documentation"
-  },
   "components": {
+    "parameters": {
+      "banana": {
+        "in": "path",
+        "name": "banana",
+        "required": true,
+        "schema": {
+          "type": "string"
+        }
+      }
+    },
     "responses": {
       "ForbiddenError": {
         "content": {
@@ -265,29 +321,7 @@ const exampleV3 = `
         "description": "Insufficient permission to perform the requested action."
       }
     },
-    "parameters": {
-      "banana": {
-	    "in": "path",
-        "schema": {
-          "type": "string"
-        }
-      }
-    },
     "schemas": {
-      "Item": {
-        "type": "object",
-        "properties": {
-          "foo": {
-            "type": "string"
-          }
-        },
-	    "additionalProperties": {
-          "$ref": "#/components/schemas/ItemExtension"
-	    }
-      },
-	  "ItemExtension": {
-		"description": "It could be anything."
-	  },
       "Error": {
         "description": "Error response.",
         "properties": {
@@ -299,66 +333,76 @@ const exampleV3 = `
           "message"
         ],
         "type": "object"
+      },
+      "Item": {
+        "additionalProperties": true,
+        "properties": {
+          "foo": {
+            "type": "string"
+          },
+          "quux": {
+            "$ref": "#/components/schemas/ItemExtension"
+          }
+        },
+        "type": "object"
+      },
+      "ItemExtension": {
+        "type": "boolean",
+        "description": "It could be anything."
       }
     }
   },
-  "tags": [
-    {
-      "name": "Example",
-      "description": "An example tag."
-    }
-  ],
-  "servers": [
-    {
-      "url": "https://test.example.com/v2"
-    }
-  ],
+  "externalDocs": {
+    "description": "Example Documentation",
+    "url": "https://example/doc/"
+  },
+  "info": {
+    "title": "MyAPI",
+    "version": "0.1",
+    "x-info": "info extension"
+  },
+  "openapi": "3.0.3",
   "paths": {
     "/another/{banana}/{id}": {
-        "parameters": [
-		  {
-            "$ref": "#/components/parameters/banana"
-          },
-          {
-            "in": "path",
-            "name": "id",
-            "schema": {
-              "type": "integer"
-            },
-			"required": true
+      "parameters": [
+        {
+          "$ref": "#/components/parameters/banana"
+        },
+        {
+          "in": "path",
+          "name": "id",
+          "required": true,
+          "schema": {
+            "type": "integer"
           }
-		]
+        }
+      ]
     },
     "/example": {
-      "x-path": "path extension 1",
-      "x-path2": "path extension 2",
-      "delete": {
-        "x-operation": "operation extension 1",
-        "description": "example delete",
+      "get": {
+        "description": "example get",
         "responses": {
-          "default": {
-            "description": "default response"
-          },
           "403": {
             "$ref": "#/components/responses/ForbiddenError"
           },
           "404": {
             "description": "404 response"
+          },
+          "default": {
+            "description": "default response"
           }
-        }
+        },
+        "x-operation": "operation extension 1"
       },
-      "get": {
-        "operationId": "example-get",
-        "summary": "example get",
-        "description": "example get",
-        "tags": [
-          "Example"
-        ],
+      "delete": {
+        "description": "example delete",
+        "operationId": "example-delete",
         "parameters": [
           {
-            "x-parameter": "parameter extension 1",
             "in": "query",
-            "name": "x"
+            "name": "x",
+            "schema": {"type": "string"},
+            "x-parameter": "parameter extension 1"
           },
           {
             "description": "The y parameter",
@@ -376,26 +420,17 @@ const exampleV3 = `
             "in": "query",
             "name": "bbox",
             "schema": {
-              "type": "array",
               "items": {
                 "type": "number"
               },
+              "maxItems": 4,
               "minItems": 4,
-              "maxItems": 4
+              "type": "array"
             }
           }
         ],
-        "requestBody": {
-          "x-requestBody": "requestbody extension 1",
-          "content": {
-            "application/json": {
-              "schema": {}
-            }
-          }
-        },
         "responses": {
           "200": {
-            "description": "ok",
             "content": {
               "application/json": {
                 "schema": {
@@ -405,14 +440,15 @@ const exampleV3 = `
                   "type": "array"
                 }
               }
-            }
-          },
-          "default": {
-            "x-response": "response extension 1",
-            "description": "default response"
+            },
+            "description": "ok"
           },
           "404": {
             "description": "404 response"
+          },
+          "default": {
+            "description": "default response",
+            "x-response": "response extension 1"
           }
         },
         "security": [
@@ -423,23 +459,58 @@ const exampleV3 = `
             ],
             "get_security_1": []
           }
+        ],
+        "summary": "example get",
+        "tags": [
+          "Example"
         ]
       },
       "head": {
         "description": "example head",
-        "responses": {}
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "options": {
         "description": "example options",
-        "responses": {}
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "patch": {
         "description": "example patch",
-        "responses": {}
+        "requestBody": {
+          "content": {
+            "application/json": {
+              "schema": {
+                "allOf": [{"$ref": "#/components/schemas/Item"}]
+              }
+            }
+          },
+          "x-requestBody": "requestbody extension 1"
+        },
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
       },
       "post": {
         "description": "example post",
-        "responses": {},
+        "parameters": [
+          {
+            "description": "File Id",
+            "in": "query",
+            "name": "id",
+            "schema": {
+              "type": "integer"
+            }
+          }
+        ],
         "requestBody": {
           "content": {
             "multipart/form-data": {
@@ -447,25 +518,36 @@ const exampleV3 = `
                 "properties": {
                   "fileUpload": {
                     "description": "param description",
-                    "format": "binary",
                     "type": "string",
+                    "format": "binary",
                     "x-mimetype": "text/plain"
                   },
-                  "note":{
-                    "type": "integer",
-                    "description": "Description of file contents"
+                  "note": {
+                    "description": "Description of file contents",
+                    "type": "integer"
                   }
                 },
                 "type": "object"
               }
             }
           }
+        },
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
         }
       },
       "put": {
         "description": "example put",
-        "responses": {}
-      }
+        "responses": {
+          "default": {
+            "description": "default response"
+          }
+        }
+      },
+      "x-path": "path extension 1",
+      "x-path2": "path extension 2"
     }
   },
   "security": [
@@ -476,6 +558,19 @@ const exampleV3 = `
       ],
       "default_security_1": []
     }
-  ]
+  ],
+  "servers": [
+    {
+      "url": "https://test.example.com/v2"
+    }
+  ],
+  "tags": [
+    {
+      "description": "An example tag.",
+      "name": "Example"
+    }
+  ],
+  "x-root": "root extension 1",
+  "x-root2": "root extension 2"
 }
 `