Repository: getkin/kin-openapi
Pull request #730 (2023-01-03 08:47:07)

openapi3filter: RegisterBodyDecoder for application/zip
Hi.

Added `application/zip` to body decoder.

I have verified that it is possible to read in zip format using [archive/zip](https://pkg.go.dev/archive/zip).


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index e1648fc..dab35cb 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -52,6 +52,11 @@ jobs:
 
     - uses: actions/checkout@v2
 
+    - name: Check codegen
+      run: |
+        ./refs.sh | tee openapi3/refs.go
+        git --no-pager diff --exit-code
+
     - run: go mod download && go mod tidy && go mod verify
     - run: git --no-pager diff --exit-code
 
@@ -102,22 +107,32 @@ jobs:
       run: |
         [[ "$(git grep -F yaml. -- openapi3/ | grep -v _test.go | wc -l)" = 1 ]]
 
+    - if: runner.os == 'Linux'
+      name: Ensure non-pointer MarshalJSON
+      run: |
+        ! git grep -InE 'func.+[*].+[)].MarshalJSON[(][)]'
+
     - if: runner.os == 'Linux'
       name: Missing specification object link to definition
       run: |
         [[ 31 -eq $(git grep -InE '^// See https:.+OpenAPI-Specification.+3[.]0[.]3[.]md#.+bject$' openapi3/*.go | grep -v _test.go | grep -v doc.go | wc -l) ]]
 
     - if: runner.os == 'Linux'
-      name: Style around ExtensionProps embedding
+      name: Missing validation of unknown fields in extensions
+      run: |
+        [[ $(git grep -InF 'return validateExtensions' -- openapi3 | wc -l) -eq $(git grep -InE '^\s+Extensions.+`' -- openapi3 | wc -l) ]]
+
+    - if: runner.os == 'Linux'
+      name: Style around Extensions embedding
       run: |
-        ! ag -B2 -A2 'type.[A-Z].+struct..\n.+ExtensionProps\n[^\n]' openapi3/*.go
+        ! ag -B2 -A2 'type.[A-Z].+struct..\n.+Extensions\n[^\n]' openapi3/*.go
 
     - if: runner.os == 'Linux'
       name: Ensure all exported fields are mentioned in Validate() impls
       run: |
         for ty in $TYPES; do
           # Ensure definition
-          if ! ag 'type.[A-Z].+struct..\n.+ExtensionProps' openapi3/*.go | grep -F "type $ty struct"; then
+          if ! ag 'type.[A-Z].+struct..\n.+Extensions' openapi3/*.go | grep -F "type $ty struct"; then
             echo "OAI type $ty is not defined" && exit 1
           fi
 
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
new file mode 100644
index 0000000..e1f8d12
--- /dev/null
+++ b/.github/workflows/shellcheck.yml
@@ -0,0 +1,18 @@
+name: ShellCheck
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  shellcheck:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Checkout
+      uses: actions/checkout@v2
+
+    - name: Run shellcheck
+      uses: ludeeus/action-shellcheck@1.1.0
+      with:
+        check_together: 'yes'
+        severity: error
diff --git a/README.md b/README.md
index 850b11b..27d6699 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,9 @@ func arrayUniqueItemsChecker(items []interface{}) bool {
 
 ### v0.113.0
 * The string format `email` has been removed by default. To use it please call `openapi3.DefineStringFormat("email", openapi3.FormatOfStringForEmail)`.
+* Field `openapi3.T.Components` is now a pointer.
+* Fields `openapi3.Schema.AdditionalProperties` and `openapi3.Schema.AdditionalPropertiesAllowed` are replaced by `openapi3.Schema.AdditionalProperties.Schema` and `openapi3.Schema.AdditionalProperties.Has` respectively.
+* Type `openapi3.ExtensionProps` is now just `map[string]interface{}` and extensions are accessible through the `Extensions` field.
 
 ### v0.112.0
 * `(openapi3.ValidationOptions).ExamplesValidationDisabled` has been unexported.
diff --git a/go.mod b/go.mod
index 942b319..12a2f1a 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/invopop/yaml v0.1.0
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
+	github.com/perimeterx/marshmallow v1.1.4
 	github.com/stretchr/testify v1.8.1
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index 4982bc7..4d05787 100644
--- a/go.sum
+++ b/go.sum
@@ -5,20 +5,27 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
+github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
 github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
+github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -29,6 +36,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/jsoninfo/doc.go b/jsoninfo/doc.go
deleted file mode 100644
index e59ec2c..0000000
--- a/jsoninfo/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package jsoninfo provides information and functions for marshalling/unmarshalling JSON.
-package jsoninfo
diff --git a/jsoninfo/marshal.go b/jsoninfo/marshal.go
deleted file mode 100644
index f2abf7c..0000000
--- a/jsoninfo/marshal.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package jsoninfo
-
-import (
-	"encoding/json"
-	"fmt"
-	"reflect"
-)
-
-// MarshalStrictStruct function:
-//   - Marshals struct fields, ignoring MarshalJSON() and fields without 'json' tag.
-//   - Correctly handles StrictStruct semantics.
-func MarshalStrictStruct(value StrictStruct) ([]byte, error) {
-	encoder := NewObjectEncoder()
-	if err := value.EncodeWith(encoder, value); err != nil {
-		return nil, err
-	}
-	return encoder.Bytes()
-}
-
-type ObjectEncoder struct {
-	result map[string]json.RawMessage
-}
-
-func NewObjectEncoder() *ObjectEncoder {
-	return &ObjectEncoder{
-		result: make(map[string]json.RawMessage),
-	}
-}
-
-// Bytes returns the result of encoding.
-func (encoder *ObjectEncoder) Bytes() ([]byte, error) {
-	return json.Marshal(encoder.result)
-}
-
-// EncodeExtension adds a key/value to the current JSON object.
-func (encoder *ObjectEncoder) EncodeExtension(key string, value interface{}) error {
-	data, err := json.Marshal(value)
-	if err != nil {
-		return err
-	}
-	encoder.result[key] = data
-	return nil
-}
-
-// EncodeExtensionMap adds all properties to the result.
-func (encoder *ObjectEncoder) EncodeExtensionMap(value map[string]json.RawMessage) error {
-	if value != nil {
-		result := encoder.result
-		for k, v := range value {
-			result[k] = v
-		}
-	}
-	return nil
-}
-
-func (encoder *ObjectEncoder) EncodeStructFieldsAndExtensions(value interface{}) error {
-	reflection := reflect.ValueOf(value)
-
-	// Follow "encoding/json" semantics
-	if reflection.Kind() != reflect.Ptr {
-		// Panic because this is a clear programming error
-		panic(fmt.Errorf("value %s is not a pointer", reflection.Type().String()))
-	}
-	if reflection.IsNil() {
-		// Panic because this is a clear programming error
-		panic(fmt.Errorf("value %s is nil", reflection.Type().String()))
-	}
-
-	// Take the element
-	reflection = reflection.Elem()
-
-	// Obtain typeInfo
-	typeInfo := GetTypeInfo(reflection.Type())
-
-	// Declare result
-	result := encoder.result
-
-	// Supported fields
-iteration:
-	for _, field := range typeInfo.Fields {
-		// Fields without JSON tag are ignored
-		if !field.HasJSONTag {
-			continue
-		}
-
-		// Marshal
-		fieldValue := reflection.FieldByIndex(field.Index)
-		if v, ok := fieldValue.Interface().(json.Marshaler); ok {
-			if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
-				if field.JSONOmitEmpty {
-					continue iteration
-				}
-				result[field.JSONName] = []byte("null")
-				continue
-			}
-			fieldData, err := v.MarshalJSON()
-			if err != nil {
-				return err
-			}
-			result[field.JSONName] = fieldData
-			continue
-		}
-		switch fieldValue.Kind() {
-		case reflect.Ptr, reflect.Interface:
-			if fieldValue.IsNil() {
-				if field.JSONOmitEmpty {
-					continue iteration
-				}
-				result[field.JSONName] = []byte("null")
-				continue
-			}
-		case reflect.Struct:
-		case reflect.Map:
-			if field.JSONOmitEmpty && (fieldValue.IsNil() || fieldValue.Len() == 0) {
-				continue iteration
-			}
-		case reflect.Slice:
-			if field.JSONOmitEmpty && fieldValue.Len() == 0 {
-				continue iteration
-			}
-		case reflect.Bool:
-			x := fieldValue.Bool()
-			if field.JSONOmitEmpty && !x {
-				continue iteration
-			}
-			s := "false"
-			if x {
-				s = "true"
-			}
-			result[field.JSONName] = []byte(s)
-			continue iteration
-		case reflect.Int64, reflect.Int, reflect.Int32:
-			if field.JSONOmitEmpty && fieldValue.Int() == 0 {
-				continue iteration
-			}
-		case reflect.Uint64, reflect.Uint, reflect.Uint32:
-			if field.JSONOmitEmpty && fieldValue.Uint() == 0 {
-				continue iteration
-			}
-		case reflect.Float64:
-			if field.JSONOmitEmpty && fieldValue.Float() == 0.0 {
-				continue iteration
-			}
-		case reflect.String:
-			if field.JSONOmitEmpty && len(fieldValue.String()) == 0 {
-				continue iteration
-			}
-		default:
-			panic(fmt.Errorf("field %q has unsupported type %s", field.JSONName, field.Type.String()))
-		}
-
-		// No special treament is needed
-		// Use plain old "encoding/json".Marshal
-		fieldData, err := json.Marshal(fieldValue.Addr().Interface())
-		if err != nil {
-			return err
-		}
-		result[field.JSONName] = fieldData
-	}
-
-	return nil
-}
diff --git a/jsoninfo/marshal_ref.go b/jsoninfo/marshal_ref.go
deleted file mode 100644
index 29575e9..0000000
--- a/jsoninfo/marshal_ref.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package jsoninfo
-
-import (
-	"encoding/json"
-)
-
-func MarshalRef(value string, otherwise interface{}) ([]byte, error) {
-	if value != "" {
-		return json.Marshal(&refProps{
-			Ref: value,
-		})
-	}
-	return json.Marshal(otherwise)
-}
-
-func UnmarshalRef(data []byte, destRef *string, destOtherwise interface{}) error {
-	refProps := &refProps{}
-	if err := json.Unmarshal(data, refProps); err == nil {
-		ref := refProps.Ref
-		if ref != "" {
-			*destRef = ref
-			return nil
-		}
-	}
-	return json.Unmarshal(data, destOtherwise)
-}
-
-type refProps struct {
-	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
-}
diff --git a/jsoninfo/strict_struct.go b/jsoninfo/strict_struct.go
deleted file mode 100644
index 6b4d839..0000000
--- a/jsoninfo/strict_struct.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package jsoninfo
-
-type StrictStruct interface {
-	EncodeWith(encoder *ObjectEncoder, value interface{}) error
-	DecodeWith(decoder *ObjectDecoder, value interface{}) error
-}
diff --git a/jsoninfo/type_info.go b/jsoninfo/type_info.go
deleted file mode 100644
index 3dbb8d5..0000000
--- a/jsoninfo/type_info.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package jsoninfo
-
-import (
-	"reflect"
-	"sort"
-	"sync"
-)
-
-var (
-	typeInfos      = map[reflect.Type]*TypeInfo{}
-	typeInfosMutex sync.RWMutex
-)
-
-// TypeInfo contains information about JSON serialization of a type
-type TypeInfo struct {
-	Type   reflect.Type
-	Fields []FieldInfo
-}
-
-func GetTypeInfoForValue(value interface{}) *TypeInfo {
-	return GetTypeInfo(reflect.TypeOf(value))
-}
-
-// GetTypeInfo returns TypeInfo for the given type.
-func GetTypeInfo(t reflect.Type) *TypeInfo {
-	for t.Kind() == reflect.Ptr {
-		t = t.Elem()
-	}
-	typeInfosMutex.RLock()
-	typeInfo, exists := typeInfos[t]
-	typeInfosMutex.RUnlock()
-	if exists {
-		return typeInfo
-	}
-	if t.Kind() != reflect.Struct {
-		typeInfo = &TypeInfo{
-			Type: t,
-		}
-	} else {
-		// Allocate
-		typeInfo = &TypeInfo{
-			Type:   t,
-			Fields: make([]FieldInfo, 0, 16),
-		}
-
-		// Add fields
-		typeInfo.Fields = AppendFields(nil, nil, t)
-
-		// Sort fields
-		sort.Sort(sortableFieldInfos(typeInfo.Fields))
-	}
-
-	// Publish
-	typeInfosMutex.Lock()
-	typeInfos[t] = typeInfo
-	typeInfosMutex.Unlock()
-	return typeInfo
-}
-
-// FieldNames returns all field names
-func (typeInfo *TypeInfo) FieldNames() []string {
-	fields := typeInfo.Fields
-	names := make([]string, 0, len(fields))
-	for _, field := range fields {
-		names = append(names, field.JSONName)
-	}
-	return names
-}
diff --git a/jsoninfo/unmarshal.go b/jsoninfo/unmarshal.go
deleted file mode 100644
index 16886ad..0000000
--- a/jsoninfo/unmarshal.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package jsoninfo
-
-import (
-	"encoding/json"
-	"fmt"
-	"reflect"
-)
-
-// UnmarshalStrictStruct function:
-//   - Unmarshals struct fields, ignoring UnmarshalJSON(...) and fields without 'json' tag.
-//   - Correctly handles StrictStruct
-func UnmarshalStrictStruct(data []byte, value StrictStruct) error {
-	decoder, err := NewObjectDecoder(data)
-	if err != nil {
-		return err
-	}
-	return value.DecodeWith(decoder, value)
-}
-
-type ObjectDecoder struct {
-	Data            []byte
-	remainingFields map[string]json.RawMessage
-}
-
-func NewObjectDecoder(data []byte) (*ObjectDecoder, error) {
-	var remainingFields map[string]json.RawMessage
-	if err := json.Unmarshal(data, &remainingFields); err != nil {
-		return nil, fmt.Errorf("failed to unmarshal extension properties: %w (%s)", err, data)
-	}
-	return &ObjectDecoder{
-		Data:            data,
-		remainingFields: remainingFields,
-	}, nil
-}
-
-// DecodeExtensionMap returns all properties that were not decoded previously.
-func (decoder *ObjectDecoder) DecodeExtensionMap() map[string]json.RawMessage {
-	return decoder.remainingFields
-}
-
-func (decoder *ObjectDecoder) DecodeStructFieldsAndExtensions(value interface{}) error {
-	reflection := reflect.ValueOf(value)
-	if reflection.Kind() != reflect.Ptr {
-		panic(fmt.Errorf("value %T is not a pointer", value))
-	}
-	if reflection.IsNil() {
-		panic(fmt.Errorf("value %T is nil", value))
-	}
-	reflection = reflection.Elem()
-	for (reflection.Kind() == reflect.Interface || reflection.Kind() == reflect.Ptr) && !reflection.IsNil() {
-		reflection = reflection.Elem()
-	}
-	reflectionType := reflection.Type()
-	if reflectionType.Kind() != reflect.Struct {
-		panic(fmt.Errorf("value %T is not a struct", value))
-	}
-	typeInfo := GetTypeInfo(reflectionType)
-
-	// Supported fields
-	fields := typeInfo.Fields
-	remainingFields := decoder.remainingFields
-	for fieldIndex, field := range fields {
-		// Fields without JSON tag are ignored
-		if !field.HasJSONTag {
-			continue
-		}
-
-		// Get data
-		fieldData, exists := remainingFields[field.JSONName]
-		if !exists {
-			continue
-		}
-
-		// Unmarshal
-		if field.TypeIsUnmarshaller {
-			fieldType := field.Type
-			isPtr := false
-			if fieldType.Kind() == reflect.Ptr {
-				fieldType = fieldType.Elem()
-				isPtr = true
-			}
-			fieldValue := reflect.New(fieldType)
-			if err := fieldValue.Interface().(json.Unmarshaler).UnmarshalJSON(fieldData); err != nil {
-				if field.MultipleFields {
-					i := fieldIndex + 1
-					if i < len(fields) && fields[i].JSONName == field.JSONName {
-						continue
-					}
-				}
-				return fmt.Errorf("failed to unmarshal property %q (%s): %w",
-					field.JSONName, fieldValue.Type().String(), err)
-			}
-			if !isPtr {
-				fieldValue = fieldValue.Elem()
-			}
-			reflection.FieldByIndex(field.Index).Set(fieldValue)
-
-			// Remove the field from remaining fields
-			delete(remainingFields, field.JSONName)
-		} else {
-			fieldPtr := reflection.FieldByIndex(field.Index)
-			if fieldPtr.Kind() != reflect.Ptr || fieldPtr.IsNil() {
-				fieldPtr = fieldPtr.Addr()
-			}
-			if err := json.Unmarshal(fieldData, fieldPtr.Interface()); err != nil {
-				if field.MultipleFields {
-					i := fieldIndex + 1
-					if i < len(fields) && fields[i].JSONName == field.JSONName {
-						continue
-					}
-				}
-				return fmt.Errorf("failed to unmarshal property %q (%s): %w",
-					field.JSONName, fieldPtr.Type().String(), err)
-			}
-
-			// Remove the field from remaining fields
-			delete(remainingFields, field.JSONName)
-		}
-	}
-	return nil
-}
diff --git a/jsoninfo/unsupported_properties_error.go b/jsoninfo/unsupported_properties_error.go
deleted file mode 100644
index f69aafd..0000000
--- a/jsoninfo/unsupported_properties_error.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package jsoninfo
-
-import (
-	"encoding/json"
-	"fmt"
-	"sort"
-)
-
-// UnsupportedPropertiesError is a helper for extensions that want to refuse
-// unsupported JSON object properties.
-//
-// It produces a helpful error message.
-type UnsupportedPropertiesError struct {
-	Value                 interface{}
-	UnsupportedProperties map[string]json.RawMessage
-}
-
-func NewUnsupportedPropertiesError(v interface{}, m map[string]json.RawMessage) error {
-	return &UnsupportedPropertiesError{
-		Value:                 v,
-		UnsupportedProperties: m,
-	}
-}
-
-func (err *UnsupportedPropertiesError) Error() string {
-	m := err.UnsupportedProperties
-	typeInfo := GetTypeInfoForValue(err.Value)
-	if m == nil || typeInfo == nil {
-		return fmt.Sprintf("invalid %T", *err)
-	}
-	keys := make([]string, 0, len(m))
-	for k := range m {
-		keys = append(keys, k)
-	}
-	sort.Strings(keys)
-	supported := typeInfo.FieldNames()
-	if len(supported) == 0 {
-		return fmt.Sprintf("type \"%T\" doesn't take any properties. Unsupported properties: %+v",
-			err.Value, keys)
-	}
-	return fmt.Sprintf("unsupported properties: %+v (supported properties are: %+v)", keys, supported)
-}
diff --git a/openapi2/header.go b/openapi2/header.go
new file mode 100644
index 0000000..a51f99d
--- /dev/null
+++ b/openapi2/header.go
@@ -0,0 +1,15 @@
+package openapi2
+
+type Header struct {
+	Parameter
+}
+
+// MarshalJSON returns the JSON encoding of Header.
+func (header Header) MarshalJSON() ([]byte, error) {
+	return header.Parameter.MarshalJSON()
+}
+
+// UnmarshalJSON sets Header to a copy of data.
+func (header *Header) UnmarshalJSON(data []byte) error {
+	return header.Parameter.UnmarshalJSON(data)
+}
diff --git a/openapi2/openapi2.go b/openapi2/openapi2.go
index 4927ade..88835db 100644
--- a/openapi2/openapi2.go
+++ b/openapi2/openapi2.go
@@ -1,19 +1,17 @@
 package openapi2
 
 import (
-	"fmt"
-	"net/http"
-	"sort"
+	"encoding/json"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
 // T is the root of an OpenAPI v2 document
 type T struct {
-	openapi3.ExtensionProps
-	Swagger             string                         `json:"swagger" yaml:"swagger"`
-	Info                openapi3.Info                  `json:"info" yaml:"info"`
+	Extensions map[string]interface{} `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"`
@@ -30,253 +28,90 @@ type T struct {
 }
 
 // MarshalJSON returns the JSON encoding of T.
-func (doc *T) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(doc)
-}
-
-// UnmarshalJSON sets T to a copy of data.
-func (doc *T) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, doc)
-}
-
-func (doc *T) AddOperation(path string, method string, operation *Operation) {
-	if doc.Paths == nil {
-		doc.Paths = make(map[string]*PathItem)
+func (doc T) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 15+len(doc.Extensions))
+	for k, v := range doc.Extensions {
+		m[k] = v
 	}
-	pathItem := doc.Paths[path]
-	if pathItem == nil {
-		pathItem = &PathItem{}
-		doc.Paths[path] = pathItem
+	m["swagger"] = doc.Swagger
+	m["info"] = doc.Info
+	if x := doc.ExternalDocs; x != nil {
+		m["externalDocs"] = x
 	}
-	pathItem.SetOperation(method, operation)
-}
-
-type PathItem struct {
-	openapi3.ExtensionProps
-	Ref        string     `json:"$ref,omitempty" yaml:"$ref,omitempty"`
-	Delete     *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
-	Get        *Operation `json:"get,omitempty" yaml:"get,omitempty"`
-	Head       *Operation `json:"head,omitempty" yaml:"head,omitempty"`
-	Options    *Operation `json:"options,omitempty" yaml:"options,omitempty"`
-	Patch      *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
-	Post       *Operation `json:"post,omitempty" yaml:"post,omitempty"`
-	Put        *Operation `json:"put,omitempty" yaml:"put,omitempty"`
-	Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
-}
-
-// MarshalJSON returns the JSON encoding of PathItem.
-func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(pathItem)
-}
-
-// UnmarshalJSON sets PathItem to a copy of data.
-func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, pathItem)
-}
-
-func (pathItem *PathItem) Operations() map[string]*Operation {
-	operations := make(map[string]*Operation)
-	if v := pathItem.Delete; v != nil {
-		operations[http.MethodDelete] = v
+	if x := doc.Schemes; len(x) != 0 {
+		m["schemes"] = x
 	}
-	if v := pathItem.Get; v != nil {
-		operations[http.MethodGet] = v
+	if x := doc.Consumes; len(x) != 0 {
+		m["consumes"] = x
 	}
-	if v := pathItem.Head; v != nil {
-		operations[http.MethodHead] = v
+	if x := doc.Produces; len(x) != 0 {
+		m["produces"] = x
 	}
-	if v := pathItem.Options; v != nil {
-		operations[http.MethodOptions] = v
+	if x := doc.Host; x != "" {
+		m["host"] = x
 	}
-	if v := pathItem.Patch; v != nil {
-		operations[http.MethodPatch] = v
+	if x := doc.BasePath; x != "" {
+		m["basePath"] = x
 	}
-	if v := pathItem.Post; v != nil {
-		operations[http.MethodPost] = v
+	if x := doc.Paths; len(x) != 0 {
+		m["paths"] = x
 	}
-	if v := pathItem.Put; v != nil {
-		operations[http.MethodPut] = v
+	if x := doc.Definitions; len(x) != 0 {
+		m["definitions"] = x
 	}
-	return operations
-}
-
-func (pathItem *PathItem) GetOperation(method string) *Operation {
-	switch method {
-	case http.MethodDelete:
-		return pathItem.Delete
-	case http.MethodGet:
-		return pathItem.Get
-	case http.MethodHead:
-		return pathItem.Head
-	case http.MethodOptions:
-		return pathItem.Options
-	case http.MethodPatch:
-		return pathItem.Patch
-	case http.MethodPost:
-		return pathItem.Post
-	case http.MethodPut:
-		return pathItem.Put
-	default:
-		panic(fmt.Errorf("unsupported HTTP method %q", method))
+	if x := doc.Parameters; len(x) != 0 {
+		m["parameters"] = x
 	}
-}
-
-func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
-	switch method {
-	case http.MethodDelete:
-		pathItem.Delete = operation
-	case http.MethodGet:
-		pathItem.Get = operation
-	case http.MethodHead:
-		pathItem.Head = operation
-	case http.MethodOptions:
-		pathItem.Options = operation
-	case http.MethodPatch:
-		pathItem.Patch = operation
-	case http.MethodPost:
-		pathItem.Post = operation
-	case http.MethodPut:
-		pathItem.Put = operation
-	default:
-		panic(fmt.Errorf("unsupported HTTP method %q", method))
+	if x := doc.Responses; len(x) != 0 {
+		m["responses"] = x
 	}
-}
-
-type Operation struct {
-	openapi3.ExtensionProps
-	Summary      string                 `json:"summary,omitempty" yaml:"summary,omitempty"`
-	Description  string                 `json:"description,omitempty" yaml:"description,omitempty"`
-	Deprecated   bool                   `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
-	ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
-	Tags         []string               `json:"tags,omitempty" yaml:"tags,omitempty"`
-	OperationID  string                 `json:"operationId,omitempty" yaml:"operationId,omitempty"`
-	Parameters   Parameters             `json:"parameters,omitempty" yaml:"parameters,omitempty"`
-	Responses    map[string]*Response   `json:"responses" yaml:"responses"`
-	Consumes     []string               `json:"consumes,omitempty" yaml:"consumes,omitempty"`
-	Produces     []string               `json:"produces,omitempty" yaml:"produces,omitempty"`
-	Schemes      []string               `json:"schemes,omitempty" yaml:"schemes,omitempty"`
-	Security     *SecurityRequirements  `json:"security,omitempty" yaml:"security,omitempty"`
-}
-
-// MarshalJSON returns the JSON encoding of Operation.
-func (operation *Operation) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(operation)
-}
-
-// UnmarshalJSON sets Operation to a copy of data.
-func (operation *Operation) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, operation)
-}
-
-type Parameters []*Parameter
-
-var _ sort.Interface = Parameters{}
-
-func (ps Parameters) Len() int      { return len(ps) }
-func (ps Parameters) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
-func (ps Parameters) Less(i, j int) bool {
-	if ps[i].Name != ps[j].Name {
-		return ps[i].Name < ps[j].Name
+	if x := doc.SecurityDefinitions; len(x) != 0 {
+		m["securityDefinitions"] = x
 	}
-	if ps[i].In != ps[j].In {
-		return ps[i].In < ps[j].In
+	if x := doc.Security; len(x) != 0 {
+		m["security"] = x
 	}
-	return ps[i].Ref < ps[j].Ref
-}
-
-type Parameter struct {
-	openapi3.ExtensionProps
-	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             string              `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             []interface{}       `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          interface{}         `json:"default,omitempty" yaml:"default,omitempty"`
-}
-
-// MarshalJSON returns the JSON encoding of Parameter.
-func (parameter *Parameter) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(parameter)
-}
-
-// UnmarshalJSON sets Parameter to a copy of data.
-func (parameter *Parameter) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, parameter)
-}
-
-type Response struct {
-	openapi3.ExtensionProps
-	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]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
-}
-
-// MarshalJSON returns the JSON encoding of Response.
-func (response *Response) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(response)
-}
-
-// UnmarshalJSON sets Response to a copy of data.
-func (response *Response) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, response)
-}
-
-type Header struct {
-	Parameter
-}
-
-// MarshalJSON returns the JSON encoding of Header.
-func (header *Header) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(header)
-}
-
-// UnmarshalJSON sets Header to a copy of data.
-func (header *Header) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, header)
-}
-
-type SecurityRequirements []map[string][]string
-
-type SecurityScheme struct {
-	openapi3.ExtensionProps
-	Ref              string            `json:"$ref,omitempty" yaml:"$ref,omitempty"`
-	Description      string            `json:"description,omitempty" yaml:"description,omitempty"`
-	Type             string            `json:"type,omitempty" yaml:"type,omitempty"`
-	In               string            `json:"in,omitempty" yaml:"in,omitempty"`
-	Name             string            `json:"name,omitempty" yaml:"name,omitempty"`
-	Flow             string            `json:"flow,omitempty" yaml:"flow,omitempty"`
-	AuthorizationURL string            `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
-	TokenURL         string            `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
-	Scopes           map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
-	Tags             openapi3.Tags     `json:"tags,omitempty" yaml:"tags,omitempty"`
+	if x := doc.Tags; len(x) != 0 {
+		m["tags"] = x
+	}
+	return json.Marshal(m)
 }
 
-// MarshalJSON returns the JSON encoding of SecurityScheme.
-func (securityScheme *SecurityScheme) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(securityScheme)
+// UnmarshalJSON sets T to a copy of data.
+func (doc *T) UnmarshalJSON(data []byte) error {
+	type TBis T
+	var x TBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "swagger")
+	delete(x.Extensions, "info")
+	delete(x.Extensions, "externalDocs")
+	delete(x.Extensions, "schemes")
+	delete(x.Extensions, "consumes")
+	delete(x.Extensions, "produces")
+	delete(x.Extensions, "host")
+	delete(x.Extensions, "basePath")
+	delete(x.Extensions, "paths")
+	delete(x.Extensions, "definitions")
+	delete(x.Extensions, "parameters")
+	delete(x.Extensions, "responses")
+	delete(x.Extensions, "securityDefinitions")
+	delete(x.Extensions, "security")
+	delete(x.Extensions, "tags")
+	*doc = T(x)
+	return nil
 }
 
-// UnmarshalJSON sets SecurityScheme to a copy of data.
-func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, securityScheme)
+func (doc *T) AddOperation(path string, method string, operation *Operation) {
+	if doc.Paths == nil {
+		doc.Paths = make(map[string]*PathItem)
+	}
+	pathItem := doc.Paths[path]
+	if pathItem == nil {
+		pathItem = &PathItem{}
+		doc.Paths[path] = pathItem
+	}
+	pathItem.SetOperation(method, operation)
 }
diff --git a/openapi2/operation.go b/openapi2/operation.go
new file mode 100644
index 0000000..b29f67d
--- /dev/null
+++ b/openapi2/operation.go
@@ -0,0 +1,91 @@
+package openapi2
+
+import (
+	"encoding/json"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type Operation struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	Summary      string                 `json:"summary,omitempty" yaml:"summary,omitempty"`
+	Description  string                 `json:"description,omitempty" yaml:"description,omitempty"`
+	Deprecated   bool                   `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
+	ExternalDocs *openapi3.ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
+	Tags         []string               `json:"tags,omitempty" yaml:"tags,omitempty"`
+	OperationID  string                 `json:"operationId,omitempty" yaml:"operationId,omitempty"`
+	Parameters   Parameters             `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+	Responses    map[string]*Response   `json:"responses" yaml:"responses"`
+	Consumes     []string               `json:"consumes,omitempty" yaml:"consumes,omitempty"`
+	Produces     []string               `json:"produces,omitempty" yaml:"produces,omitempty"`
+	Schemes      []string               `json:"schemes,omitempty" yaml:"schemes,omitempty"`
+	Security     *SecurityRequirements  `json:"security,omitempty" yaml:"security,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of Operation.
+func (operation Operation) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 12+len(operation.Extensions))
+	for k, v := range operation.Extensions {
+		m[k] = v
+	}
+	if x := operation.Summary; x != "" {
+		m["summary"] = x
+	}
+	if x := operation.Description; x != "" {
+		m["description"] = x
+	}
+	if x := operation.Deprecated; x {
+		m["deprecated"] = x
+	}
+	if x := operation.ExternalDocs; x != nil {
+		m["externalDocs"] = x
+	}
+	if x := operation.Tags; len(x) != 0 {
+		m["tags"] = x
+	}
+	if x := operation.OperationID; x != "" {
+		m["operationId"] = x
+	}
+	if x := operation.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	m["responses"] = operation.Responses
+	if x := operation.Consumes; len(x) != 0 {
+		m["consumes"] = x
+	}
+	if x := operation.Produces; len(x) != 0 {
+		m["produces"] = x
+	}
+	if x := operation.Schemes; len(x) != 0 {
+		m["schemes"] = x
+	}
+	if x := operation.Security; x != nil {
+		m["security"] = x
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Operation to a copy of data.
+func (operation *Operation) UnmarshalJSON(data []byte) error {
+	type OperationBis Operation
+	var x OperationBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "summary")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "deprecated")
+	delete(x.Extensions, "externalDocs")
+	delete(x.Extensions, "tags")
+	delete(x.Extensions, "operationId")
+	delete(x.Extensions, "parameters")
+	delete(x.Extensions, "responses")
+	delete(x.Extensions, "consumes")
+	delete(x.Extensions, "produces")
+	delete(x.Extensions, "schemes")
+	delete(x.Extensions, "security")
+	*operation = Operation(x)
+	return nil
+}
diff --git a/openapi2/parameter.go b/openapi2/parameter.go
new file mode 100644
index 0000000..d2c71c6
--- /dev/null
+++ b/openapi2/parameter.go
@@ -0,0 +1,176 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"sort"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type Parameters []*Parameter
+
+var _ sort.Interface = Parameters{}
+
+func (ps Parameters) Len() int      { return len(ps) }
+func (ps Parameters) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
+func (ps Parameters) Less(i, j int) bool {
+	if ps[i].Name != ps[j].Name {
+		return ps[i].Name < ps[j].Name
+	}
+	if ps[i].In != ps[j].In {
+		return ps[i].In < ps[j].In
+	}
+	return ps[i].Ref < ps[j].Ref
+}
+
+type Parameter struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	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             string              `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             []interface{}       `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          interface{}         `json:"default,omitempty" yaml:"default,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of Parameter.
+func (parameter Parameter) MarshalJSON() ([]byte, error) {
+	if ref := parameter.Ref; ref != "" {
+		return json.Marshal(openapi3.Ref{Ref: ref})
+	}
+
+	m := make(map[string]interface{}, 24+len(parameter.Extensions))
+	for k, v := range parameter.Extensions {
+		m[k] = v
+	}
+
+	if x := parameter.In; x != "" {
+		m["in"] = x
+	}
+	if x := parameter.Name; x != "" {
+		m["name"] = x
+	}
+	if x := parameter.Description; x != "" {
+		m["description"] = x
+	}
+	if x := parameter.CollectionFormat; x != "" {
+		m["collectionFormat"] = x
+	}
+	if x := parameter.Type; x != "" {
+		m["type"] = x
+	}
+	if x := parameter.Format; x != "" {
+		m["format"] = x
+	}
+	if x := parameter.Pattern; x != "" {
+		m["pattern"] = x
+	}
+	if x := parameter.AllowEmptyValue; x {
+		m["allowEmptyValue"] = x
+	}
+	if x := parameter.Required; x {
+		m["required"] = x
+	}
+	if x := parameter.UniqueItems; x {
+		m["uniqueItems"] = x
+	}
+	if x := parameter.ExclusiveMin; x {
+		m["exclusiveMinimum"] = x
+	}
+	if x := parameter.ExclusiveMax; x {
+		m["exclusiveMaximum"] = x
+	}
+	if x := parameter.Schema; x != nil {
+		m["schema"] = x
+	}
+	if x := parameter.Items; x != nil {
+		m["items"] = x
+	}
+	if x := parameter.Enum; x != nil {
+		m["enum"] = x
+	}
+	if x := parameter.MultipleOf; x != nil {
+		m["multipleOf"] = x
+	}
+	if x := parameter.Minimum; x != nil {
+		m["minimum"] = x
+	}
+	if x := parameter.Maximum; x != nil {
+		m["maximum"] = x
+	}
+	if x := parameter.MaxLength; x != nil {
+		m["maxLength"] = x
+	}
+	if x := parameter.MaxItems; x != nil {
+		m["maxItems"] = x
+	}
+	if x := parameter.MinLength; x != 0 {
+		m["minLength"] = x
+	}
+	if x := parameter.MinItems; x != 0 {
+		m["minItems"] = x
+	}
+	if x := parameter.Default; x != nil {
+		m["default"] = x
+	}
+
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Parameter to a copy of data.
+func (parameter *Parameter) UnmarshalJSON(data []byte) error {
+	type ParameterBis Parameter
+	var x ParameterBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "$ref")
+
+	delete(x.Extensions, "in")
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "collectionFormat")
+	delete(x.Extensions, "type")
+	delete(x.Extensions, "format")
+	delete(x.Extensions, "pattern")
+	delete(x.Extensions, "allowEmptyValue")
+	delete(x.Extensions, "required")
+	delete(x.Extensions, "uniqueItems")
+	delete(x.Extensions, "exclusiveMinimum")
+	delete(x.Extensions, "exclusiveMaximum")
+	delete(x.Extensions, "schema")
+	delete(x.Extensions, "items")
+	delete(x.Extensions, "enum")
+	delete(x.Extensions, "multipleOf")
+	delete(x.Extensions, "minimum")
+	delete(x.Extensions, "maximum")
+	delete(x.Extensions, "maxLength")
+	delete(x.Extensions, "maxItems")
+	delete(x.Extensions, "minLength")
+	delete(x.Extensions, "minItems")
+	delete(x.Extensions, "default")
+
+	*parameter = Parameter(x)
+	return nil
+}
diff --git a/openapi2/path_item.go b/openapi2/path_item.go
new file mode 100644
index 0000000..95c060e
--- /dev/null
+++ b/openapi2/path_item.go
@@ -0,0 +1,150 @@
+package openapi2
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type PathItem struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
+
+	Delete     *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
+	Get        *Operation `json:"get,omitempty" yaml:"get,omitempty"`
+	Head       *Operation `json:"head,omitempty" yaml:"head,omitempty"`
+	Options    *Operation `json:"options,omitempty" yaml:"options,omitempty"`
+	Patch      *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
+	Post       *Operation `json:"post,omitempty" yaml:"post,omitempty"`
+	Put        *Operation `json:"put,omitempty" yaml:"put,omitempty"`
+	Parameters Parameters `json:"parameters,omitempty" yaml:"parameters,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of PathItem.
+func (pathItem PathItem) MarshalJSON() ([]byte, error) {
+	if ref := pathItem.Ref; ref != "" {
+		return json.Marshal(openapi3.Ref{Ref: ref})
+	}
+
+	m := make(map[string]interface{}, 8+len(pathItem.Extensions))
+	for k, v := range pathItem.Extensions {
+		m[k] = v
+	}
+	if x := pathItem.Delete; x != nil {
+		m["delete"] = x
+	}
+	if x := pathItem.Get; x != nil {
+		m["get"] = x
+	}
+	if x := pathItem.Head; x != nil {
+		m["head"] = x
+	}
+	if x := pathItem.Options; x != nil {
+		m["options"] = x
+	}
+	if x := pathItem.Patch; x != nil {
+		m["patch"] = x
+	}
+	if x := pathItem.Post; x != nil {
+		m["post"] = x
+	}
+	if x := pathItem.Put; x != nil {
+		m["put"] = x
+	}
+	if x := pathItem.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets PathItem to a copy of data.
+func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
+	type PathItemBis PathItem
+	var x PathItemBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "$ref")
+	delete(x.Extensions, "delete")
+	delete(x.Extensions, "get")
+	delete(x.Extensions, "head")
+	delete(x.Extensions, "options")
+	delete(x.Extensions, "patch")
+	delete(x.Extensions, "post")
+	delete(x.Extensions, "put")
+	delete(x.Extensions, "parameters")
+	*pathItem = PathItem(x)
+	return nil
+}
+
+func (pathItem *PathItem) Operations() map[string]*Operation {
+	operations := make(map[string]*Operation)
+	if v := pathItem.Delete; v != nil {
+		operations[http.MethodDelete] = v
+	}
+	if v := pathItem.Get; v != nil {
+		operations[http.MethodGet] = v
+	}
+	if v := pathItem.Head; v != nil {
+		operations[http.MethodHead] = v
+	}
+	if v := pathItem.Options; v != nil {
+		operations[http.MethodOptions] = v
+	}
+	if v := pathItem.Patch; v != nil {
+		operations[http.MethodPatch] = v
+	}
+	if v := pathItem.Post; v != nil {
+		operations[http.MethodPost] = v
+	}
+	if v := pathItem.Put; v != nil {
+		operations[http.MethodPut] = v
+	}
+	return operations
+}
+
+func (pathItem *PathItem) GetOperation(method string) *Operation {
+	switch method {
+	case http.MethodDelete:
+		return pathItem.Delete
+	case http.MethodGet:
+		return pathItem.Get
+	case http.MethodHead:
+		return pathItem.Head
+	case http.MethodOptions:
+		return pathItem.Options
+	case http.MethodPatch:
+		return pathItem.Patch
+	case http.MethodPost:
+		return pathItem.Post
+	case http.MethodPut:
+		return pathItem.Put
+	default:
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
+	}
+}
+
+func (pathItem *PathItem) SetOperation(method string, operation *Operation) {
+	switch method {
+	case http.MethodDelete:
+		pathItem.Delete = operation
+	case http.MethodGet:
+		pathItem.Get = operation
+	case http.MethodHead:
+		pathItem.Head = operation
+	case http.MethodOptions:
+		pathItem.Options = operation
+	case http.MethodPatch:
+		pathItem.Patch = operation
+	case http.MethodPost:
+		pathItem.Post = operation
+	case http.MethodPut:
+		pathItem.Put = operation
+	default:
+		panic(fmt.Errorf("unsupported HTTP method %q", method))
+	}
+}
diff --git a/openapi2/response.go b/openapi2/response.go
new file mode 100644
index 0000000..bd18f88
--- /dev/null
+++ b/openapi2/response.go
@@ -0,0 +1,60 @@
+package openapi2
+
+import (
+	"encoding/json"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type Response struct {
+	Extensions map[string]interface{} `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]interface{} `json:"examples,omitempty" yaml:"examples,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of Response.
+func (response Response) MarshalJSON() ([]byte, error) {
+	if ref := response.Ref; ref != "" {
+		return json.Marshal(openapi3.Ref{Ref: ref})
+	}
+
+	m := make(map[string]interface{}, 4+len(response.Extensions))
+	for k, v := range response.Extensions {
+		m[k] = v
+	}
+	if x := response.Description; x != "" {
+		m["description"] = x
+	}
+	if x := response.Schema; x != nil {
+		m["schema"] = x
+	}
+	if x := response.Headers; len(x) != 0 {
+		m["headers"] = x
+	}
+	if x := response.Examples; len(x) != 0 {
+		m["examples"] = x
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Response to a copy of data.
+func (response *Response) UnmarshalJSON(data []byte) error {
+	type ResponseBis Response
+	var x ResponseBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "$ref")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "schema")
+	delete(x.Extensions, "headers")
+	delete(x.Extensions, "examples")
+	*response = Response(x)
+	return nil
+}
diff --git a/openapi2/security_scheme.go b/openapi2/security_scheme.go
new file mode 100644
index 0000000..5a8c278
--- /dev/null
+++ b/openapi2/security_scheme.go
@@ -0,0 +1,87 @@
+package openapi2
+
+import (
+	"encoding/json"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+type SecurityRequirements []map[string][]string
+
+type SecurityScheme struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
+
+	Description      string            `json:"description,omitempty" yaml:"description,omitempty"`
+	Type             string            `json:"type,omitempty" yaml:"type,omitempty"`
+	In               string            `json:"in,omitempty" yaml:"in,omitempty"`
+	Name             string            `json:"name,omitempty" yaml:"name,omitempty"`
+	Flow             string            `json:"flow,omitempty" yaml:"flow,omitempty"`
+	AuthorizationURL string            `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
+	TokenURL         string            `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
+	Scopes           map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"`
+	Tags             openapi3.Tags     `json:"tags,omitempty" yaml:"tags,omitempty"`
+}
+
+// MarshalJSON returns the JSON encoding of SecurityScheme.
+func (securityScheme SecurityScheme) MarshalJSON() ([]byte, error) {
+	if ref := securityScheme.Ref; ref != "" {
+		return json.Marshal(openapi3.Ref{Ref: ref})
+	}
+
+	m := make(map[string]interface{}, 10+len(securityScheme.Extensions))
+	for k, v := range securityScheme.Extensions {
+		m[k] = v
+	}
+	if x := securityScheme.Description; x != "" {
+		m["description"] = x
+	}
+	if x := securityScheme.Type; x != "" {
+		m["type"] = x
+	}
+	if x := securityScheme.In; x != "" {
+		m["in"] = x
+	}
+	if x := securityScheme.Name; x != "" {
+		m["name"] = x
+	}
+	if x := securityScheme.Flow; x != "" {
+		m["flow"] = x
+	}
+	if x := securityScheme.AuthorizationURL; x != "" {
+		m["authorizationUrl"] = x
+	}
+	if x := securityScheme.TokenURL; x != "" {
+		m["tokenUrl"] = x
+	}
+	if x := securityScheme.Scopes; len(x) != 0 {
+		m["scopes"] = x
+	}
+	if x := securityScheme.Tags; len(x) != 0 {
+		m["tags"] = x
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets SecurityScheme to a copy of data.
+func (securityScheme *SecurityScheme) UnmarshalJSON(data []byte) error {
+	type SecuritySchemeBis SecurityScheme
+	var x SecuritySchemeBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "$ref")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "type")
+	delete(x.Extensions, "in")
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "flow")
+	delete(x.Extensions, "authorizationUrl")
+	delete(x.Extensions, "tokenUrl")
+	delete(x.Extensions, "scopes")
+	delete(x.Extensions, "tags")
+	*securityScheme = SecurityScheme(x)
+	return nil
+}
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 989b685..c80e672 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -1,7 +1,6 @@
 package openapi2conv
 
 import (
-	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
@@ -14,15 +13,13 @@ import (
 
 // ToV3 converts an OpenAPIv2 spec to an OpenAPIv3 spec
 func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
-	stripNonCustomExtensions(doc2.Extensions)
-
 	doc3 := &openapi3.T{
-		OpenAPI:        "3.0.3",
-		Info:           &doc2.Info,
-		Components:     openapi3.Components{},
-		Tags:           doc2.Tags,
-		ExtensionProps: doc2.ExtensionProps,
-		ExternalDocs:   doc2.ExternalDocs,
+		OpenAPI:      "3.0.3",
+		Info:         &doc2.Info,
+		Components:   &openapi3.Components{},
+		Tags:         doc2.Tags,
+		Extensions:   stripNonExtensions(doc2.Extensions),
+		ExternalDocs: doc2.ExternalDocs,
 	}
 
 	if host := doc2.Host; host != "" {
@@ -53,7 +50,7 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
 		doc3.Components.Parameters = make(map[string]*openapi3.ParameterRef)
 		doc3.Components.RequestBodies = make(map[string]*openapi3.RequestBodyRef)
 		for k, parameter := range parameters {
-			v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(&doc3.Components, parameter, doc2.Consumes)
+			v3Parameter, v3RequestBody, v3SchemaMap, err := ToV3Parameter(doc3.Components, parameter, doc2.Consumes)
 			switch {
 			case err != nil:
 				return nil, err
@@ -72,7 +69,7 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
 	if paths := doc2.Paths; len(paths) != 0 {
 		doc3Paths := make(map[string]*openapi3.PathItem, len(paths))
 		for path, pathItem := range paths {
-			r, err := ToV3PathItem(doc2, &doc3.Components, pathItem, doc2.Consumes)
+			r, err := ToV3PathItem(doc2, doc3.Components, pathItem, doc2.Consumes)
 			if err != nil {
 				return nil, err
 			}
@@ -119,9 +116,8 @@ func ToV3(doc2 *openapi2.T) (*openapi3.T, error) {
 }
 
 func ToV3PathItem(doc2 *openapi2.T, components *openapi3.Components, pathItem *openapi2.PathItem, consumes []string) (*openapi3.PathItem, error) {
-	stripNonCustomExtensions(pathItem.Extensions)
 	doc3 := &openapi3.PathItem{
-		ExtensionProps: pathItem.ExtensionProps,
+		Extensions: stripNonExtensions(pathItem.Extensions),
 	}
 	for method, operation := range pathItem.Operations() {
 		doc3Operation, err := ToV3Operation(doc2, components, pathItem, operation, consumes)
@@ -150,14 +146,13 @@ func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *
 	if operation == nil {
 		return nil, nil
 	}
-	stripNonCustomExtensions(operation.Extensions)
 	doc3 := &openapi3.Operation{
-		OperationID:    operation.OperationID,
-		Summary:        operation.Summary,
-		Description:    operation.Description,
-		Deprecated:     operation.Deprecated,
-		Tags:           operation.Tags,
-		ExtensionProps: operation.ExtensionProps,
+		OperationID: operation.OperationID,
+		Summary:     operation.Summary,
+		Description: operation.Description,
+		Deprecated:  operation.Deprecated,
+		Tags:        operation.Tags,
+		Extensions:  stripNonExtensions(operation.Extensions),
 	}
 	if v := operation.Security; v != nil {
 		doc3Security := ToV3SecurityRequirements(*v)
@@ -230,24 +225,22 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 		}
 		return &openapi3.ParameterRef{Ref: ToV3Ref(ref)}, nil, nil, nil
 	}
-	stripNonCustomExtensions(parameter.Extensions)
 
 	switch parameter.In {
 	case "body":
 		result := &openapi3.RequestBody{
-			Description:    parameter.Description,
-			Required:       parameter.Required,
-			ExtensionProps: parameter.ExtensionProps,
+			Description: parameter.Description,
+			Required:    parameter.Required,
+			Extensions:  stripNonExtensions(parameter.Extensions),
 		}
 		if parameter.Name != "" {
 			if result.Extensions == nil {
-				result.Extensions = make(map[string]interface{})
+				result.Extensions = make(map[string]interface{}, 1)
 			}
 			result.Extensions["x-originalParamName"] = parameter.Name
 		}
 
 		if schemaRef := parameter.Schema; schemaRef != nil {
-			// Assuming JSON
 			result.WithSchemaRef(ToV3SchemaRef(schemaRef), consumes)
 		}
 		return nil, &openapi3.RequestBodyRef{Value: result}, nil, nil
@@ -257,39 +250,37 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 		if typ == "file" {
 			format, typ = "binary", "string"
 		}
-		if parameter.ExtensionProps.Extensions == nil {
-			parameter.ExtensionProps.Extensions = make(map[string]interface{})
+		if parameter.Extensions == nil {
+			parameter.Extensions = make(map[string]interface{}, 1)
 		}
-		parameter.ExtensionProps.Extensions["x-formData-name"] = parameter.Name
+		parameter.Extensions["x-formData-name"] = parameter.Name
 		var required []string
 		if parameter.Required {
 			required = []string{parameter.Name}
 		}
-		schemaRef := &openapi3.SchemaRef{
-			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,
-				Required:        required,
-			},
-		}
-		schemaRefMap := make(map[string]*openapi3.SchemaRef)
+		schemaRef := &openapi3.SchemaRef{Value: &openapi3.Schema{
+			Description:     parameter.Description,
+			Type:            typ,
+			Extensions:      stripNonExtensions(parameter.Extensions),
+			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,
+			Required:        required,
+		}}
+		schemaRefMap := make(map[string]*openapi3.SchemaRef, 1)
 		schemaRefMap[parameter.Name] = schemaRef
 		return nil, nil, schemaRefMap, nil
 
@@ -304,11 +295,11 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 			schemaRefRef = schemaRef.Ref
 		}
 		result := &openapi3.Parameter{
-			In:             parameter.In,
-			Name:           parameter.Name,
-			Description:    parameter.Description,
-			Required:       required,
-			ExtensionProps: parameter.ExtensionProps,
+			In:          parameter.In,
+			Name:        parameter.Name,
+			Description: parameter.Description,
+			Required:    required,
+			Extensions:  stripNonExtensions(parameter.Extensions),
 			Schema: ToV3SchemaRef(&openapi3.SchemaRef{Value: &openapi3.Schema{
 				Type:            parameter.Type,
 				Format:          parameter.Format,
@@ -417,10 +408,9 @@ func ToV3Response(response *openapi2.Response, produces []string) (*openapi3.Res
 	if ref := response.Ref; ref != "" {
 		return &openapi3.ResponseRef{Ref: ToV3Ref(ref)}, nil
 	}
-	stripNonCustomExtensions(response.Extensions)
 	result := &openapi3.Response{
-		Description:    &response.Description,
-		ExtensionProps: response.ExtensionProps,
+		Description: &response.Description,
+		Extensions:  stripNonExtensions(response.Extensions),
 	}
 
 	// Default to "application/json" if "produces" is not specified.
@@ -479,19 +469,14 @@ func ToV3SchemaRef(schema *openapi3.SchemaRef) *openapi3.SchemaRef {
 	for k, v := range schema.Value.Properties {
 		schema.Value.Properties[k] = ToV3SchemaRef(v)
 	}
-	if v := schema.Value.AdditionalProperties; v != nil {
-		schema.Value.AdditionalProperties = ToV3SchemaRef(v)
+	if v := schema.Value.AdditionalProperties.Schema; v != nil {
+		schema.Value.AdditionalProperties.Schema = ToV3SchemaRef(v)
 	}
 	for i, v := range schema.Value.AllOf {
 		schema.Value.AllOf[i] = ToV3SchemaRef(v)
 	}
 	if val, ok := schema.Value.Extensions["x-nullable"]; ok {
-		var nullable bool
-
-		if err := json.Unmarshal(val.(json.RawMessage), &nullable); err == nil {
-			schema.Value.Nullable = nullable
-		}
-
+		schema.Value.Nullable, _ = val.(bool)
 		delete(schema.Value.Extensions, "x-nullable")
 	}
 
@@ -539,10 +524,9 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 	if securityScheme == nil {
 		return nil, nil
 	}
-	stripNonCustomExtensions(securityScheme.Extensions)
 	result := &openapi3.SecurityScheme{
-		Description:    securityScheme.Description,
-		ExtensionProps: securityScheme.ExtensionProps,
+		Description: securityScheme.Description,
+		Extensions:  stripNonExtensions(securityScheme.Extensions),
 	}
 	switch securityScheme.Type {
 	case "basic":
@@ -586,21 +570,20 @@ func ToV3SecurityScheme(securityScheme *openapi2.SecurityScheme) (*openapi3.Secu
 
 // FromV3 converts an OpenAPIv3 spec to an OpenAPIv2 spec
 func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
-	doc2Responses, err := FromV3Responses(doc3.Components.Responses, &doc3.Components)
+	doc2Responses, err := FromV3Responses(doc3.Components.Responses, doc3.Components)
 	if err != nil {
 		return nil, err
 	}
-	stripNonCustomExtensions(doc3.Extensions)
-	schemas, parameters := FromV3Schemas(doc3.Components.Schemas, &doc3.Components)
+	schemas, parameters := FromV3Schemas(doc3.Components.Schemas, doc3.Components)
 	doc2 := &openapi2.T{
-		Swagger:        "2.0",
-		Info:           *doc3.Info,
-		Definitions:    schemas,
-		Parameters:     parameters,
-		Responses:      doc2Responses,
-		Tags:           doc3.Tags,
-		ExtensionProps: doc3.ExtensionProps,
-		ExternalDocs:   doc3.ExternalDocs,
+		Swagger:      "2.0",
+		Info:         *doc3.Info,
+		Definitions:  schemas,
+		Parameters:   parameters,
+		Responses:    doc2Responses,
+		Tags:         doc3.Tags,
+		Extensions:   stripNonExtensions(doc3.Extensions),
+		ExternalDocs: doc3.ExternalDocs,
 	}
 
 	isHTTPS := false
@@ -633,8 +616,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
 			continue
 		}
 		doc2.AddOperation(path, "GET", nil)
-		stripNonCustomExtensions(pathItem.Extensions)
-		addPathExtensions(doc2, path, pathItem.ExtensionProps)
+		addPathExtensions(doc2, path, stripNonExtensions(pathItem.Extensions))
 		for method, operation := range pathItem.Operations() {
 			if operation == nil {
 				continue
@@ -647,7 +629,7 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
 		}
 		params := openapi2.Parameters{}
 		for _, param := range pathItem.Parameters {
-			p, err := FromV3Parameter(param, &doc3.Components)
+			p, err := FromV3Parameter(param, doc3.Components)
 			if err != nil {
 				return nil, err
 			}
@@ -658,13 +640,13 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
 	}
 
 	for name, param := range doc3.Components.Parameters {
-		if doc2.Parameters[name], err = FromV3Parameter(param, &doc3.Components); err != nil {
+		if doc2.Parameters[name], err = FromV3Parameter(param, doc3.Components); err != nil {
 			return nil, err
 		}
 	}
 
 	for name, requestBodyRef := range doc3.Components.RequestBodies {
-		bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, &doc3.Components)
+		bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, requestBodyRef, doc3.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -733,7 +715,7 @@ func fromV3RequestBodies(name string, requestBodyRef *openapi3.RequestBodyRef, c
 
 			paramName := name
 			if originalName, ok := requestBodyRef.Value.Extensions["x-originalParamName"]; ok {
-				json.Unmarshal(originalName.(json.RawMessage), &paramName)
+				paramName = originalName.(string)
 			}
 
 			var r *openapi2.Parameter
@@ -786,11 +768,11 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 			required := false
 
 			value, _ := schema.Value.Extensions["x-formData-name"]
-			var originalName string
-			json.Unmarshal(value.(json.RawMessage), &originalName)
+			originalName, _ := value.(string)
 			for _, prop := range schema.Value.Required {
 				if originalName == prop {
 					required = true
+					break
 				}
 			}
 			return nil, &openapi2.Parameter{
@@ -812,7 +794,7 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 				AllowEmptyValue: schema.Value.AllowEmptyValue,
 				UniqueItems:     schema.Value.UniqueItems,
 				MultipleOf:      schema.Value.MultipleOf,
-				ExtensionProps:  schema.Value.ExtensionProps,
+				Extensions:      stripNonExtensions(schema.Value.Extensions),
 				Required:        required,
 			}
 		}
@@ -828,8 +810,8 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 	for _, key := range keys {
 		schema.Value.Properties[key], _ = FromV3SchemaRef(schema.Value.Properties[key], components)
 	}
-	if v := schema.Value.AdditionalProperties; v != nil {
-		schema.Value.AdditionalProperties, _ = FromV3SchemaRef(v, components)
+	if v := schema.Value.AdditionalProperties.Schema; v != nil {
+		schema.Value.AdditionalProperties.Schema, _ = FromV3SchemaRef(v, components)
 	}
 	for i, v := range schema.Value.AllOf {
 		schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
@@ -854,9 +836,8 @@ func FromV3SecurityRequirements(requirements openapi3.SecurityRequirements) open
 }
 
 func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.PathItem, error) {
-	stripNonCustomExtensions(pathItem.Extensions)
 	result := &openapi2.PathItem{
-		ExtensionProps: pathItem.ExtensionProps,
+		Extensions: stripNonExtensions(pathItem.Extensions),
 	}
 	for method, operation := range pathItem.Operations() {
 		r, err := FromV3Operation(doc3, operation)
@@ -866,7 +847,7 @@ func FromV3PathItem(doc3 *openapi3.T, pathItem *openapi3.PathItem) (*openapi2.Pa
 		result.SetOperation(method, r)
 	}
 	for _, parameter := range pathItem.Parameters {
-		p, err := FromV3Parameter(parameter, &doc3.Components)
+		p, err := FromV3Parameter(parameter, doc3.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -910,23 +891,23 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
 			}
 		}
 		parameter := &openapi2.Parameter{
-			Name:           propName,
-			Description:    val.Description,
-			Type:           typ,
-			In:             "formData",
-			ExtensionProps: val.ExtensionProps,
-			Enum:           val.Enum,
-			ExclusiveMin:   val.ExclusiveMin,
-			ExclusiveMax:   val.ExclusiveMax,
-			MinLength:      val.MinLength,
-			MaxLength:      val.MaxLength,
-			Default:        val.Default,
-			Items:          val.Items,
-			MinItems:       val.MinItems,
-			MaxItems:       val.MaxItems,
-			Maximum:        val.Max,
-			Minimum:        val.Min,
-			Pattern:        val.Pattern,
+			Name:         propName,
+			Description:  val.Description,
+			Type:         typ,
+			In:           "formData",
+			Extensions:   stripNonExtensions(val.Extensions),
+			Enum:         val.Enum,
+			ExclusiveMin: val.ExclusiveMin,
+			ExclusiveMax: val.ExclusiveMax,
+			MinLength:    val.MinLength,
+			MaxLength:    val.MaxLength,
+			Default:      val.Default,
+			Items:        val.Items,
+			MinItems:     val.MinItems,
+			MaxItems:     val.MaxItems,
+			Maximum:      val.Max,
+			Minimum:      val.Min,
+			Pattern:      val.Pattern,
 			// CollectionFormat: val.CollectionFormat,
 			// Format:          val.Format,
 			AllowEmptyValue: val.AllowEmptyValue,
@@ -943,21 +924,20 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
 	if operation == nil {
 		return nil, nil
 	}
-	stripNonCustomExtensions(operation.Extensions)
 	result := &openapi2.Operation{
-		OperationID:    operation.OperationID,
-		Summary:        operation.Summary,
-		Description:    operation.Description,
-		Deprecated:     operation.Deprecated,
-		Tags:           operation.Tags,
-		ExtensionProps: operation.ExtensionProps,
+		OperationID: operation.OperationID,
+		Summary:     operation.Summary,
+		Description: operation.Description,
+		Deprecated:  operation.Deprecated,
+		Tags:        operation.Tags,
+		Extensions:  stripNonExtensions(operation.Extensions),
 	}
 	if v := operation.Security; v != nil {
 		resultSecurity := FromV3SecurityRequirements(*v)
 		result.Security = &resultSecurity
 	}
 	for _, parameter := range operation.Parameters {
-		r, err := FromV3Parameter(parameter, &doc3.Components)
+		r, err := FromV3Parameter(parameter, doc3.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -970,7 +950,7 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
 			return nil, errors.New("could not find a name for request body")
 		}
 
-		bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, &doc3.Components)
+		bodyOrRefParameters, formDataParameters, consumes, err := fromV3RequestBodies(name, v, doc3.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -991,7 +971,7 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
 	sort.Sort(result.Parameters)
 
 	if responses := operation.Responses; responses != nil {
-		resultResponses, err := FromV3Responses(responses, &doc3.Components)
+		resultResponses, err := FromV3Responses(responses, doc3.Components)
 		if err != nil {
 			return nil, err
 		}
@@ -1003,13 +983,12 @@ func FromV3Operation(doc3 *openapi3.T, operation *openapi3.Operation) (*openapi2
 func FromV3RequestBody(name string, requestBodyRef *openapi3.RequestBodyRef, mediaType *openapi3.MediaType, components *openapi3.Components) (*openapi2.Parameter, error) {
 	requestBody := requestBodyRef.Value
 
-	stripNonCustomExtensions(requestBody.Extensions)
 	result := &openapi2.Parameter{
-		In:             "body",
-		Name:           name,
-		Description:    requestBody.Description,
-		Required:       requestBody.Required,
-		ExtensionProps: requestBody.ExtensionProps,
+		In:          "body",
+		Name:        name,
+		Description: requestBody.Description,
+		Required:    requestBody.Required,
+		Extensions:  stripNonExtensions(requestBody.Extensions),
 	}
 
 	if mediaType != nil {
@@ -1026,13 +1005,12 @@ func FromV3Parameter(ref *openapi3.ParameterRef, components *openapi3.Components
 	if parameter == nil {
 		return nil, nil
 	}
-	stripNonCustomExtensions(parameter.Extensions)
 	result := &openapi2.Parameter{
-		Description:    parameter.Description,
-		In:             parameter.In,
-		Name:           parameter.Name,
-		Required:       parameter.Required,
-		ExtensionProps: parameter.ExtensionProps,
+		Description: parameter.Description,
+		In:          parameter.In,
+		Name:        parameter.Name,
+		Required:    parameter.Required,
+		Extensions:  stripNonExtensions(parameter.Extensions),
 	}
 	if schemaRef := parameter.Schema; schemaRef != nil {
 		schemaRef, _ = FromV3SchemaRef(schemaRef, components)
@@ -1088,10 +1066,9 @@ func FromV3Response(ref *openapi3.ResponseRef, components *openapi3.Components)
 	if desc := response.Description; desc != nil {
 		description = *desc
 	}
-	stripNonCustomExtensions(response.Extensions)
 	result := &openapi2.Response{
-		Description:    description,
-		ExtensionProps: response.ExtensionProps,
+		Description: description,
+		Extensions:  stripNonExtensions(response.Extensions),
 	}
 	if content := response.Content; content != nil {
 		if ct := content["application/json"]; ct != nil {
@@ -1127,11 +1104,10 @@ func FromV3SecurityScheme(doc3 *openapi3.T, ref *openapi3.SecuritySchemeRef) (*o
 	if securityScheme == nil {
 		return nil, nil
 	}
-	stripNonCustomExtensions(securityScheme.Extensions)
 	result := &openapi2.SecurityScheme{
-		Ref:            FromV3Ref(ref.Ref),
-		Description:    securityScheme.Description,
-		ExtensionProps: securityScheme.ExtensionProps,
+		Ref:         FromV3Ref(ref.Ref),
+		Description: securityScheme.Description,
+		Extensions:  stripNonExtensions(securityScheme.Extensions),
 	}
 	switch securityScheme.Type {
 	case "http":
@@ -1195,15 +1171,17 @@ var attemptedBodyParameterNames = []string{
 	"requestBody",
 }
 
-func stripNonCustomExtensions(extensions map[string]interface{}) {
+// stripNonExtensions removes invalid extensions: those not prefixed by "x-" and returns them
+func stripNonExtensions(extensions map[string]interface{}) map[string]interface{} {
 	for extName := range extensions {
 		if !strings.HasPrefix(extName, "x-") {
 			delete(extensions, extName)
 		}
 	}
+	return extensions
 }
 
-func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.ExtensionProps) {
+func addPathExtensions(doc2 *openapi2.T, path string, extensions map[string]interface{}) {
 	if doc2.Paths == nil {
 		doc2.Paths = make(map[string]*openapi2.PathItem)
 	}
@@ -1212,5 +1190,5 @@ func addPathExtensions(doc2 *openapi2.T, path string, extensionProps openapi3.Ex
 		pathItem = &openapi2.PathItem{}
 		doc2.Paths[path] = pathItem
 	}
-	pathItem.ExtensionProps = extensionProps
+	pathItem.Extensions = extensions
 }
diff --git a/openapi3/components.go b/openapi3/components.go
index 8abc3fe..0981e8b 100644
--- a/openapi3/components.go
+++ b/openapi3/components.go
@@ -2,17 +2,16 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"regexp"
 	"sort"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Components is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#components-object
 type Components struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Schemas         Schemas         `json:"schemas,omitempty" yaml:"schemas,omitempty"`
 	Parameters      ParametersMap   `json:"parameters,omitempty" yaml:"parameters,omitempty"`
@@ -30,13 +29,60 @@ func NewComponents() Components {
 }
 
 // MarshalJSON returns the JSON encoding of Components.
-func (components *Components) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(components)
+func (components Components) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 9+len(components.Extensions))
+	for k, v := range components.Extensions {
+		m[k] = v
+	}
+	if x := components.Schemas; len(x) != 0 {
+		m["schemas"] = x
+	}
+	if x := components.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	if x := components.Headers; len(x) != 0 {
+		m["headers"] = x
+	}
+	if x := components.RequestBodies; len(x) != 0 {
+		m["requestBodies"] = x
+	}
+	if x := components.Responses; len(x) != 0 {
+		m["responses"] = x
+	}
+	if x := components.SecuritySchemes; len(x) != 0 {
+		m["securitySchemes"] = x
+	}
+	if x := components.Examples; len(x) != 0 {
+		m["examples"] = x
+	}
+	if x := components.Links; len(x) != 0 {
+		m["links"] = x
+	}
+	if x := components.Callbacks; len(x) != 0 {
+		m["callbacks"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Components to a copy of data.
 func (components *Components) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, components)
+	type ComponentsBis Components
+	var x ComponentsBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "schemas")
+	delete(x.Extensions, "parameters")
+	delete(x.Extensions, "headers")
+	delete(x.Extensions, "requestBodies")
+	delete(x.Extensions, "responses")
+	delete(x.Extensions, "securitySchemes")
+	delete(x.Extensions, "examples")
+	delete(x.Extensions, "links")
+	delete(x.Extensions, "callbacks")
+	*components = Components(x)
+	return nil
 }
 
 // Validate returns an error if Components does not comply with the OpenAPI spec.
@@ -178,7 +224,7 @@ func (components *Components) Validate(ctx context.Context, opts ...ValidationOp
 		}
 	}
 
-	return
+	return validateExtensions(ctx, components.Extensions)
 }
 
 const identifierPattern = `^[a-zA-Z0-9._-]+$`
diff --git a/openapi3/discriminator.go b/openapi3/discriminator.go
index 8ab344a..8b6b813 100644
--- a/openapi3/discriminator.go
+++ b/openapi3/discriminator.go
@@ -2,32 +2,48 @@ package openapi3
 
 import (
 	"context"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
+	"encoding/json"
 )
 
 // Discriminator is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#discriminator-object
 type Discriminator struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
-	PropertyName string            `json:"propertyName" yaml:"propertyName"`
+	PropertyName string            `json:"propertyName" yaml:"propertyName"` // required
 	Mapping      map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of Discriminator.
-func (discriminator *Discriminator) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(discriminator)
+func (discriminator Discriminator) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 2+len(discriminator.Extensions))
+	for k, v := range discriminator.Extensions {
+		m[k] = v
+	}
+	m["propertyName"] = discriminator.PropertyName
+	if x := discriminator.Mapping; len(x) != 0 {
+		m["mapping"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Discriminator to a copy of data.
 func (discriminator *Discriminator) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, discriminator)
+	type DiscriminatorBis Discriminator
+	var x DiscriminatorBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "propertyName")
+	delete(x.Extensions, "mapping")
+	*discriminator = Discriminator(x)
+	return nil
 }
 
 // Validate returns an error if Discriminator does not comply with the OpenAPI spec.
 func (discriminator *Discriminator) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
-	return nil
+	return validateExtensions(ctx, discriminator.Extensions)
 }
diff --git a/openapi3/encoding.go b/openapi3/encoding.go
index 003833e..dc2e544 100644
--- a/openapi3/encoding.go
+++ b/openapi3/encoding.go
@@ -2,16 +2,15 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"sort"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Encoding is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#encoding-object
 type Encoding struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	ContentType   string  `json:"contentType,omitempty" yaml:"contentType,omitempty"`
 	Headers       Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -41,13 +40,44 @@ func (encoding *Encoding) WithHeaderRef(name string, ref *HeaderRef) *Encoding {
 }
 
 // MarshalJSON returns the JSON encoding of Encoding.
-func (encoding *Encoding) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(encoding)
+func (encoding Encoding) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 5+len(encoding.Extensions))
+	for k, v := range encoding.Extensions {
+		m[k] = v
+	}
+	if x := encoding.ContentType; x != "" {
+		m["contentType"] = x
+	}
+	if x := encoding.Headers; len(x) != 0 {
+		m["headers"] = x
+	}
+	if x := encoding.Style; x != "" {
+		m["style"] = x
+	}
+	if x := encoding.Explode; x != nil {
+		m["explode"] = x
+	}
+	if x := encoding.AllowReserved; x {
+		m["allowReserved"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Encoding to a copy of data.
 func (encoding *Encoding) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, encoding)
+	type EncodingBis Encoding
+	var x EncodingBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "contentType")
+	delete(x.Extensions, "headers")
+	delete(x.Extensions, "style")
+	delete(x.Extensions, "explode")
+	delete(x.Extensions, "allowReserved")
+	*encoding = Encoding(x)
+	return nil
 }
 
 // SerializationMethod returns a serialization method of request body.
@@ -102,5 +132,5 @@ func (encoding *Encoding) Validate(ctx context.Context, opts ...ValidationOption
 		return fmt.Errorf("serialization method with style=%q and explode=%v is not supported by media type", sm.Style, sm.Explode)
 	}
 
-	return nil
+	return validateExtensions(ctx, encoding.Extensions)
 }
diff --git a/openapi3/example.go b/openapi3/example.go
index f4cbfc0..04338be 100644
--- a/openapi3/example.go
+++ b/openapi3/example.go
@@ -2,12 +2,11 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Examples map[string]*ExampleRef
@@ -30,7 +29,7 @@ func (e Examples) JSONLookup(token string) (interface{}, error) {
 // Example is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#example-object
 type Example struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Summary       string      `json:"summary,omitempty" yaml:"summary,omitempty"`
 	Description   string      `json:"description,omitempty" yaml:"description,omitempty"`
@@ -39,24 +38,49 @@ type Example struct {
 }
 
 func NewExample(value interface{}) *Example {
-	return &Example{
-		Value: value,
-	}
+	return &Example{Value: value}
 }
 
 // MarshalJSON returns the JSON encoding of Example.
-func (example *Example) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(example)
+func (example Example) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(example.Extensions))
+	for k, v := range example.Extensions {
+		m[k] = v
+	}
+	if x := example.Summary; x != "" {
+		m["summary"] = x
+	}
+	if x := example.Description; x != "" {
+		m["description"] = x
+	}
+	if x := example.Value; x != nil {
+		m["value"] = x
+	}
+	if x := example.ExternalValue; x != "" {
+		m["externalValue"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Example to a copy of data.
 func (example *Example) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, example)
+	type ExampleBis Example
+	var x ExampleBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "summary")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "value")
+	delete(x.Extensions, "externalValue")
+	*example = Example(x)
+	return nil
 }
 
 // Validate returns an error if Example does not comply with the OpenAPI spec.
 func (example *Example) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if example.Value != nil && example.ExternalValue != "" {
 		return errors.New("value and externalValue are mutually exclusive")
@@ -65,5 +89,5 @@ func (example *Example) Validate(ctx context.Context, opts ...ValidationOption)
 		return errors.New("no value or externalValue field")
 	}
 
-	return nil
+	return validateExtensions(ctx, example.Extensions)
 }
diff --git a/openapi3/extension.go b/openapi3/extension.go
index f6b7ef9..c299590 100644
--- a/openapi3/extension.go
+++ b/openapi3/extension.go
@@ -1,38 +1,24 @@
 package openapi3
 
 import (
-	"github.com/getkin/kin-openapi/jsoninfo"
+	"context"
+	"fmt"
+	"sort"
+	"strings"
 )
 
-// ExtensionProps provides support for OpenAPI extensions.
-// It reads/writes all properties that begin with "x-".
-type ExtensionProps struct {
-	Extensions map[string]interface{} `json:"-" yaml:"-"`
-}
-
-// Assert that the type implements the interface
-var _ jsoninfo.StrictStruct = &ExtensionProps{}
-
-// EncodeWith will be invoked by package "jsoninfo"
-func (props *ExtensionProps) EncodeWith(encoder *jsoninfo.ObjectEncoder, value interface{}) error {
-	for k, v := range props.Extensions {
-		if err := encoder.EncodeExtension(k, v); err != nil {
-			return err
+func validateExtensions(ctx context.Context, extensions map[string]interface{}) error { // FIXME: newtype + Validate(...)
+	var unknowns []string
+	for k := range extensions {
+		if !strings.HasPrefix(k, "x-") {
+			unknowns = append(unknowns, k)
 		}
 	}
-	return encoder.EncodeStructFieldsAndExtensions(value)
-}
 
-// DecodeWith will be invoked by package "jsoninfo"
-func (props *ExtensionProps) DecodeWith(decoder *jsoninfo.ObjectDecoder, value interface{}) error {
-	if err := decoder.DecodeStructFieldsAndExtensions(value); err != nil {
-		return err
-	}
-	source := decoder.DecodeExtensionMap()
-	result := make(map[string]interface{}, len(source))
-	for k, v := range source {
-		result[k] = v
+	if len(unknowns) != 0 {
+		sort.Strings(unknowns)
+		return fmt.Errorf("extra sibling fields: %+v", unknowns)
 	}
-	props.Extensions = result
+
 	return nil
 }
diff --git a/openapi3/external_docs.go b/openapi3/external_docs.go
index 65ec2e8..276a36c 100644
--- a/openapi3/external_docs.go
+++ b/openapi3/external_docs.go
@@ -2,35 +2,53 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // ExternalDocs is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#external-documentation-object
 type ExternalDocs struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Description string `json:"description,omitempty" yaml:"description,omitempty"`
 	URL         string `json:"url,omitempty" yaml:"url,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of ExternalDocs.
-func (e *ExternalDocs) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(e)
+func (e ExternalDocs) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 2+len(e.Extensions))
+	for k, v := range e.Extensions {
+		m[k] = v
+	}
+	if x := e.Description; x != "" {
+		m["description"] = x
+	}
+	if x := e.URL; x != "" {
+		m["url"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets ExternalDocs to a copy of data.
 func (e *ExternalDocs) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, e)
+	type ExternalDocsBis ExternalDocs
+	var x ExternalDocsBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "url")
+	*e = ExternalDocs(x)
+	return nil
 }
 
 // Validate returns an error if ExternalDocs does not comply with the OpenAPI spec.
 func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if e.URL == "" {
 		return errors.New("url is required")
@@ -38,5 +56,6 @@ func (e *ExternalDocs) Validate(ctx context.Context, opts ...ValidationOption) e
 	if _, err := url.Parse(e.URL); err != nil {
 		return fmt.Errorf("url is incorrect: %w", err)
 	}
-	return nil
+
+	return validateExtensions(ctx, e.Extensions)
 }
diff --git a/openapi3/header.go b/openapi3/header.go
index 454fad5..8bce69f 100644
--- a/openapi3/header.go
+++ b/openapi3/header.go
@@ -6,8 +6,6 @@ import (
 	"fmt"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Headers map[string]*HeaderRef
@@ -35,9 +33,19 @@ type Header struct {
 
 var _ jsonpointer.JSONPointable = (*Header)(nil)
 
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (header Header) JSONLookup(token string) (interface{}, error) {
+	return header.Parameter.JSONLookup(token)
+}
+
+// MarshalJSON returns the JSON encoding of Header.
+func (header Header) MarshalJSON() ([]byte, error) {
+	return header.Parameter.MarshalJSON()
+}
+
 // UnmarshalJSON sets Header to a copy of data.
 func (header *Header) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, header)
+	return header.Parameter.UnmarshalJSON(data)
 }
 
 // SerializationMethod returns a header's serialization method.
@@ -93,43 +101,3 @@ func (header *Header) Validate(ctx context.Context, opts ...ValidationOption) er
 	}
 	return nil
 }
-
-// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (header Header) JSONLookup(token string) (interface{}, error) {
-	switch token {
-	case "schema":
-		if header.Schema != nil {
-			if header.Schema.Ref != "" {
-				return &Ref{Ref: header.Schema.Ref}, nil
-			}
-			return header.Schema.Value, nil
-		}
-	case "name":
-		return header.Name, nil
-	case "in":
-		return header.In, nil
-	case "description":
-		return header.Description, nil
-	case "style":
-		return header.Style, nil
-	case "explode":
-		return header.Explode, nil
-	case "allowEmptyValue":
-		return header.AllowEmptyValue, nil
-	case "allowReserved":
-		return header.AllowReserved, nil
-	case "deprecated":
-		return header.Deprecated, nil
-	case "required":
-		return header.Required, nil
-	case "example":
-		return header.Example, nil
-	case "examples":
-		return header.Examples, nil
-	case "content":
-		return header.Content, nil
-	}
-
-	v, _, err := jsonpointer.GetForToken(header.ExtensionProps, token)
-	return v, err
-}
diff --git a/openapi3/info.go b/openapi3/info.go
index 7207609..381047f 100644
--- a/openapi3/info.go
+++ b/openapi3/info.go
@@ -2,15 +2,14 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Info is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#info-object
 type Info struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Title          string   `json:"title" yaml:"title"` // Required
 	Description    string   `json:"description,omitempty" yaml:"description,omitempty"`
@@ -21,13 +20,44 @@ type Info struct {
 }
 
 // MarshalJSON returns the JSON encoding of Info.
-func (info *Info) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(info)
+func (info Info) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 6+len(info.Extensions))
+	for k, v := range info.Extensions {
+		m[k] = v
+	}
+	m["title"] = info.Title
+	if x := info.Description; x != "" {
+		m["description"] = x
+	}
+	if x := info.TermsOfService; x != "" {
+		m["termsOfService"] = x
+	}
+	if x := info.Contact; x != nil {
+		m["contact"] = x
+	}
+	if x := info.License; x != nil {
+		m["license"] = x
+	}
+	m["version"] = info.Version
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Info to a copy of data.
 func (info *Info) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, info)
+	type InfoBis Info
+	var x InfoBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "title")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "termsOfService")
+	delete(x.Extensions, "contact")
+	delete(x.Extensions, "license")
+	delete(x.Extensions, "version")
+	*info = Info(x)
+	return nil
 }
 
 // Validate returns an error if Info does not comply with the OpenAPI spec.
@@ -54,13 +84,13 @@ func (info *Info) Validate(ctx context.Context, opts ...ValidationOption) error
 		return errors.New("value of title must be a non-empty string")
 	}
 
-	return nil
+	return validateExtensions(ctx, info.Extensions)
 }
 
 // Contact is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#contact-object
 type Contact struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Name  string `json:"name,omitempty" yaml:"name,omitempty"`
 	URL   string `json:"url,omitempty" yaml:"url,omitempty"`
@@ -68,47 +98,88 @@ type Contact struct {
 }
 
 // MarshalJSON returns the JSON encoding of Contact.
-func (contact *Contact) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(contact)
+func (contact Contact) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 3+len(contact.Extensions))
+	for k, v := range contact.Extensions {
+		m[k] = v
+	}
+	if x := contact.Name; x != "" {
+		m["name"] = x
+	}
+	if x := contact.URL; x != "" {
+		m["url"] = x
+	}
+	if x := contact.Email; x != "" {
+		m["email"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Contact to a copy of data.
 func (contact *Contact) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, contact)
+	type ContactBis Contact
+	var x ContactBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "url")
+	delete(x.Extensions, "email")
+	*contact = Contact(x)
+	return nil
 }
 
 // Validate returns an error if Contact does not comply with the OpenAPI spec.
 func (contact *Contact) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
-	return nil
+	return validateExtensions(ctx, contact.Extensions)
 }
 
 // License is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#license-object
 type License struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Name string `json:"name" yaml:"name"` // Required
 	URL  string `json:"url,omitempty" yaml:"url,omitempty"`
 }
 
 // MarshalJSON returns the JSON encoding of License.
-func (license *License) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(license)
+func (license License) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 2+len(license.Extensions))
+	for k, v := range license.Extensions {
+		m[k] = v
+	}
+	m["name"] = license.Name
+	if x := license.URL; x != "" {
+		m["url"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets License to a copy of data.
 func (license *License) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, license)
+	type LicenseBis License
+	var x LicenseBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "url")
+	*license = License(x)
+	return nil
 }
 
 // Validate returns an error if License does not comply with the OpenAPI spec.
 func (license *License) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if license.Name == "" {
 		return errors.New("value of license name must be a non-empty string")
 	}
-	return nil
+
+	return validateExtensions(ctx, license.Extensions)
 }
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index dcec43b..acb83cd 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -55,11 +55,16 @@ func (doc *T) addSchemaToSpec(s *SchemaRef, refNameResolver RefNameResolver, par
 	}
 
 	name := refNameResolver(s.Ref)
-	if _, ok := doc.Components.Schemas[name]; ok {
-		s.Ref = "#/components/schemas/" + name
-		return true
+	if doc.Components != nil {
+		if _, ok := doc.Components.Schemas[name]; ok {
+			s.Ref = "#/components/schemas/" + name
+			return true
+		}
 	}
 
+	if doc.Components == nil {
+		doc.Components = &Components{}
+	}
 	if doc.Components.Schemas == nil {
 		doc.Components.Schemas = make(Schemas)
 	}
@@ -220,7 +225,7 @@ func (doc *T) derefSchema(s *Schema, refNameResolver RefNameResolver, parentIsEx
 			doc.derefSchema(s2.Value, refNameResolver, isExternal || parentIsExternal)
 		}
 	}
-	for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties, s.Items} {
+	for _, ref := range []*SchemaRef{s.Not, s.AdditionalProperties.Schema, s.Items} {
 		isExternal := doc.addSchemaToSpec(ref, refNameResolver, parentIsExternal)
 		if ref != nil {
 			doc.derefSchema(ref.Value, refNameResolver, isExternal || parentIsExternal)
@@ -335,44 +340,45 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri
 		refNameResolver = DefaultRefNameResolver
 	}
 
-	// Handle components section
-	names := schemaNames(doc.Components.Schemas)
-	for _, name := range names {
-		schema := doc.Components.Schemas[name]
-		isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
-		if schema != nil {
-			schema.Ref = "" // always dereference the top level
-			doc.derefSchema(schema.Value, refNameResolver, isExternal)
+	if components := doc.Components; components != nil {
+		names := schemaNames(components.Schemas)
+		for _, name := range names {
+			schema := components.Schemas[name]
+			isExternal := doc.addSchemaToSpec(schema, refNameResolver, false)
+			if schema != nil {
+				schema.Ref = "" // always dereference the top level
+				doc.derefSchema(schema.Value, refNameResolver, isExternal)
+			}
 		}
-	}
-	names = parametersMapNames(doc.Components.Parameters)
-	for _, name := range names {
-		p := doc.Components.Parameters[name]
-		isExternal := doc.addParameterToSpec(p, refNameResolver, false)
-		if p != nil && p.Value != nil {
-			p.Ref = "" // always dereference the top level
-			doc.derefParameter(*p.Value, refNameResolver, isExternal)
+		names = parametersMapNames(components.Parameters)
+		for _, name := range names {
+			p := components.Parameters[name]
+			isExternal := doc.addParameterToSpec(p, refNameResolver, false)
+			if p != nil && p.Value != nil {
+				p.Ref = "" // always dereference the top level
+				doc.derefParameter(*p.Value, refNameResolver, isExternal)
+			}
 		}
-	}
-	doc.derefHeaders(doc.Components.Headers, refNameResolver, false)
-	for _, req := range doc.Components.RequestBodies {
-		isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
-		if req != nil && req.Value != nil {
-			req.Ref = "" // always dereference the top level
-			doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
+		doc.derefHeaders(components.Headers, refNameResolver, false)
+		for _, req := range components.RequestBodies {
+			isExternal := doc.addRequestBodyToSpec(req, refNameResolver, false)
+			if req != nil && req.Value != nil {
+				req.Ref = "" // always dereference the top level
+				doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
+			}
 		}
-	}
-	doc.derefResponses(doc.Components.Responses, refNameResolver, false)
-	for _, ss := range doc.Components.SecuritySchemes {
-		doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
-	}
-	doc.derefExamples(doc.Components.Examples, refNameResolver, false)
-	doc.derefLinks(doc.Components.Links, refNameResolver, false)
-	for _, cb := range doc.Components.Callbacks {
-		isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
-		if cb != nil && cb.Value != nil {
-			cb.Ref = "" // always dereference the top level
-			doc.derefPaths(*cb.Value, refNameResolver, isExternal)
+		doc.derefResponses(components.Responses, refNameResolver, false)
+		for _, ss := range components.SecuritySchemes {
+			doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
+		}
+		doc.derefExamples(components.Examples, refNameResolver, false)
+		doc.derefLinks(components.Links, refNameResolver, false)
+		for _, cb := range components.Callbacks {
+			isExternal := doc.addCallbackToSpec(cb, refNameResolver, false)
+			if cb != nil && cb.Value != nil {
+				cb.Ref = "" // always dereference the top level
+				doc.derefPaths(*cb.Value, refNameResolver, isExternal)
+			}
 		}
 	}
 
diff --git a/openapi3/link.go b/openapi3/link.go
index 137aef3..08dfa8d 100644
--- a/openapi3/link.go
+++ b/openapi3/link.go
@@ -2,12 +2,11 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type Links map[string]*LinkRef
@@ -30,7 +29,7 @@ var _ jsonpointer.JSONPointable = (*Links)(nil)
 // Link is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#link-object
 type Link struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	OperationRef string                 `json:"operationRef,omitempty" yaml:"operationRef,omitempty"`
 	OperationID  string                 `json:"operationId,omitempty" yaml:"operationId,omitempty"`
@@ -41,18 +40,55 @@ type Link struct {
 }
 
 // MarshalJSON returns the JSON encoding of Link.
-func (link *Link) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(link)
+func (link Link) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 6+len(link.Extensions))
+	for k, v := range link.Extensions {
+		m[k] = v
+	}
+
+	if x := link.OperationRef; x != "" {
+		m["operationRef"] = x
+	}
+	if x := link.OperationID; x != "" {
+		m["operationId"] = x
+	}
+	if x := link.Description; x != "" {
+		m["description"] = x
+	}
+	if x := link.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	if x := link.Server; x != nil {
+		m["server"] = x
+	}
+	if x := link.RequestBody; x != nil {
+		m["requestBody"] = x
+	}
+
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Link to a copy of data.
 func (link *Link) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, link)
+	type LinkBis Link
+	var x LinkBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "operationRef")
+	delete(x.Extensions, "operationId")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "parameters")
+	delete(x.Extensions, "server")
+	delete(x.Extensions, "requestBody")
+	*link = Link(x)
+	return nil
 }
 
 // Validate returns an error if Link does not comply with the OpenAPI spec.
 func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if link.OperationID == "" && link.OperationRef == "" {
 		return errors.New("missing operationId or operationRef on link")
@@ -60,5 +96,6 @@ func (link *Link) Validate(ctx context.Context, opts ...ValidationOption) error
 	if link.OperationID != "" && link.OperationRef != "" {
 		return fmt.Errorf("operationId %q and operationRef %q are mutually exclusive", link.OperationID, link.OperationRef)
 	}
-	return nil
+
+	return validateExtensions(ctx, link.Extensions)
 }
diff --git a/openapi3/loader.go b/openapi3/loader.go
index ecc2ef2..72ab8c4 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -175,6 +175,7 @@ func (loader *Loader) loadFromDataWithPathInternal(data []byte, location *url.UR
 func unmarshal(data []byte, v interface{}) error {
 	// See https://github.com/getkin/kin-openapi/issues/680
 	if err := json.Unmarshal(data, v); err != nil {
+		// UnmarshalStrict(data, v) TODO: investigate how ymlv3 handles duplicate map keys
 		return yaml.Unmarshal(data, v)
 	}
 	return nil
@@ -190,54 +191,54 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
 		loader.resetVisitedPathItemRefs()
 	}
 
-	// Visit all components
-	components := doc.Components
-	for _, component := range components.Headers {
-		if err = loader.resolveHeaderRef(doc, component, location); err != nil {
-			return
+	if components := doc.Components; components != nil {
+		for _, component := range components.Headers {
+			if err = loader.resolveHeaderRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
-	for _, component := range components.Parameters {
-		if err = loader.resolveParameterRef(doc, component, location); err != nil {
-			return
+		for _, component := range components.Parameters {
+			if err = loader.resolveParameterRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
-	for _, component := range components.RequestBodies {
-		if err = loader.resolveRequestBodyRef(doc, component, location); err != nil {
-			return
+		for _, component := range components.RequestBodies {
+			if err = loader.resolveRequestBodyRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
-	for _, component := range components.Responses {
-		if err = loader.resolveResponseRef(doc, component, location); err != nil {
-			return
+		for _, component := range components.Responses {
+			if err = loader.resolveResponseRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
-	for _, component := range components.Schemas {
-		if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil {
-			return
+		for _, component := range components.Schemas {
+			if err = loader.resolveSchemaRef(doc, component, location, []string{}); err != nil {
+				return
+			}
 		}
-	}
-	for _, component := range components.SecuritySchemes {
-		if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil {
-			return
+		for _, component := range components.SecuritySchemes {
+			if err = loader.resolveSecuritySchemeRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
 
-	examples := make([]string, 0, len(components.Examples))
-	for name := range components.Examples {
-		examples = append(examples, name)
-	}
-	sort.Strings(examples)
-	for _, name := range examples {
-		component := components.Examples[name]
-		if err = loader.resolveExampleRef(doc, component, location); err != nil {
-			return
+		examples := make([]string, 0, len(components.Examples))
+		for name := range components.Examples {
+			examples = append(examples, name)
+		}
+		sort.Strings(examples)
+		for _, name := range examples {
+			component := components.Examples[name]
+			if err = loader.resolveExampleRef(doc, component, location); err != nil {
+				return
+			}
 		}
-	}
 
-	for _, component := range components.Callbacks {
-		if err = loader.resolveCallbackRef(doc, component, location); err != nil {
-			return
+		for _, component := range components.Callbacks {
+			if err = loader.resolveCallbackRef(doc, component, location); err != nil {
+				return
+			}
 		}
 	}
 
@@ -361,10 +362,10 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv
 func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 	// Special case due to multijson
 	if s, ok := cursor.(*SchemaRef); ok && fieldName == "additionalProperties" {
-		if ap := s.Value.AdditionalPropertiesAllowed; ap != nil {
+		if ap := s.Value.AdditionalProperties.Has; ap != nil {
 			return *ap, nil
 		}
-		return s.Value.AdditionalProperties, nil
+		return s.Value.AdditionalProperties.Schema, nil
 	}
 
 	switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() {
@@ -390,14 +391,7 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 		hasFields := false
 		for i := 0; i < val.NumField(); i++ {
 			hasFields = true
-			field := val.Type().Field(i)
-			tagValue := field.Tag.Get("yaml")
-			yamlKey := strings.Split(tagValue, ",")[0]
-			if yamlKey == "-" {
-				tagValue := field.Tag.Get("multijson")
-				yamlKey = strings.Split(tagValue, ",")[0]
-			}
-			if yamlKey == fieldName {
+			if fieldName == strings.Split(val.Type().Field(i).Tag.Get("yaml"), ",")[0] {
 				return val.Field(i).Interface(), nil
 			}
 		}
@@ -407,14 +401,10 @@ func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 			return drillIntoField(val.FieldByName("Value").Interface(), fieldName)
 		}
 		if hasFields {
-			if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "ExtensionProps" {
-				extensions := val.Field(0).Interface().(ExtensionProps).Extensions
+			if ff := val.Type().Field(0); ff.PkgPath == "" && ff.Name == "Extensions" {
+				extensions := val.Field(0).Interface().(map[string]interface{})
 				if enc, ok := extensions[fieldName]; ok {
-					var dec interface{}
-					if err := json.Unmarshal(enc.(json.RawMessage), &dec); err != nil {
-						return nil, err
-					}
-					return dec, nil
+					return enc, nil
 				}
 			}
 		}
@@ -757,7 +747,7 @@ func (loader *Loader) resolveSchemaRef(doc *T, component *SchemaRef, documentPat
 			return err
 		}
 	}
-	if v := value.AdditionalProperties; v != nil {
+	if v := value.AdditionalProperties.Schema; v != nil {
 		if err := loader.resolveSchemaRef(doc, v, documentPath, visited); err != nil {
 			return err
 		}
@@ -923,7 +913,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 					}
 					id := unescapeRefString(rest)
 
-					if doc.Components.Callbacks == nil {
+					if doc.Components == nil || doc.Components.Callbacks == nil {
 						return failedToResolveRefFragmentPart(ref, "callbacks")
 					}
 					resolved := doc.Components.Callbacks[id]
diff --git a/openapi3/media_type.go b/openapi3/media_type.go
index 090be76..2a9b472 100644
--- a/openapi3/media_type.go
+++ b/openapi3/media_type.go
@@ -2,19 +2,18 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"sort"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // MediaType is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object
 type MediaType struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Schema   *SchemaRef           `json:"schema,omitempty" yaml:"schema,omitempty"`
 	Example  interface{}          `json:"example,omitempty" yaml:"example,omitempty"`
@@ -65,13 +64,40 @@ func (mediaType *MediaType) WithEncoding(name string, enc *Encoding) *MediaType
 }
 
 // MarshalJSON returns the JSON encoding of MediaType.
-func (mediaType *MediaType) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(mediaType)
+func (mediaType MediaType) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(mediaType.Extensions))
+	for k, v := range mediaType.Extensions {
+		m[k] = v
+	}
+	if x := mediaType.Schema; x != nil {
+		m["schema"] = x
+	}
+	if x := mediaType.Example; x != nil {
+		m["example"] = x
+	}
+	if x := mediaType.Examples; len(x) != 0 {
+		m["examples"] = x
+	}
+	if x := mediaType.Encoding; len(x) != 0 {
+		m["encoding"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets MediaType to a copy of data.
 func (mediaType *MediaType) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, mediaType)
+	type MediaTypeBis MediaType
+	var x MediaTypeBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "schema")
+	delete(x.Extensions, "example")
+	delete(x.Extensions, "examples")
+	delete(x.Extensions, "encoding")
+	*mediaType = MediaType(x)
+	return nil
 }
 
 // Validate returns an error if MediaType does not comply with the OpenAPI spec.
@@ -90,35 +116,33 @@ func (mediaType *MediaType) Validate(ctx context.Context, opts ...ValidationOpti
 			return errors.New("example and examples are mutually exclusive")
 		}
 
-		if vo := getValidationOptions(ctx); vo.examplesValidationDisabled {
-			return nil
-		}
-
-		if example := mediaType.Example; example != nil {
-			if err := validateExampleValue(ctx, example, schema.Value); err != nil {
-				return fmt.Errorf("invalid example: %w", err)
+		if vo := getValidationOptions(ctx); !vo.examplesValidationDisabled {
+			if example := mediaType.Example; example != nil {
+				if err := validateExampleValue(ctx, example, schema.Value); err != nil {
+					return fmt.Errorf("invalid example: %w", err)
+				}
 			}
-		}
 
-		if examples := mediaType.Examples; examples != nil {
-			names := make([]string, 0, len(examples))
-			for name := range examples {
-				names = append(names, name)
-			}
-			sort.Strings(names)
-			for _, k := range names {
-				v := examples[k]
-				if err := v.Validate(ctx); err != nil {
-					return fmt.Errorf("example %s: %w", k, err)
+			if examples := mediaType.Examples; examples != nil {
+				names := make([]string, 0, len(examples))
+				for name := range examples {
+					names = append(names, name)
 				}
-				if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
-					return fmt.Errorf("example %s: %w", k, err)
+				sort.Strings(names)
+				for _, k := range names {
+					v := examples[k]
+					if err := v.Validate(ctx); err != nil {
+						return fmt.Errorf("example %s: %w", k, err)
+					}
+					if err := validateExampleValue(ctx, v.Value.Value, schema.Value); err != nil {
+						return fmt.Errorf("example %s: %w", k, err)
+					}
 				}
 			}
 		}
 	}
 
-	return nil
+	return validateExtensions(ctx, mediaType.Extensions)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
@@ -138,6 +162,6 @@ func (mediaType MediaType) JSONLookup(token string) (interface{}, error) {
 	case "encoding":
 		return mediaType.Encoding, nil
 	}
-	v, _, err := jsonpointer.GetForToken(mediaType.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(mediaType.Extensions, token)
 	return v, err
 }
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 6622ef0..8b8f71b 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -2,19 +2,18 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // T is the root of an OpenAPI v3 document
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#openapi-object
 type T struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	OpenAPI      string               `json:"openapi" yaml:"openapi"` // Required
-	Components   Components           `json:"components,omitempty" yaml:"components,omitempty"`
+	Components   *Components          `json:"components,omitempty" yaml:"components,omitempty"`
 	Info         *Info                `json:"info" yaml:"info"`   // Required
 	Paths        Paths                `json:"paths" yaml:"paths"` // Required
 	Security     SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
@@ -26,13 +25,50 @@ type T struct {
 }
 
 // MarshalJSON returns the JSON encoding of T.
-func (doc *T) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(doc)
+func (doc T) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(doc.Extensions))
+	for k, v := range doc.Extensions {
+		m[k] = v
+	}
+	m["openapi"] = doc.OpenAPI
+	if x := doc.Components; x != nil {
+		m["components"] = x
+	}
+	m["info"] = doc.Info
+	m["paths"] = doc.Paths
+	if x := doc.Security; len(x) != 0 {
+		m["security"] = x
+	}
+	if x := doc.Servers; len(x) != 0 {
+		m["servers"] = x
+	}
+	if x := doc.Tags; len(x) != 0 {
+		m["tags"] = x
+	}
+	if x := doc.ExternalDocs; x != nil {
+		m["externalDocs"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets T to a copy of data.
 func (doc *T) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, doc)
+	type TBis T
+	var x TBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "openapi")
+	delete(x.Extensions, "components")
+	delete(x.Extensions, "info")
+	delete(x.Extensions, "paths")
+	delete(x.Extensions, "security")
+	delete(x.Extensions, "servers")
+	delete(x.Extensions, "tags")
+	delete(x.Extensions, "externalDocs")
+	*doc = T(x)
+	return nil
 }
 
 func (doc *T) AddOperation(path string, method string, operation *Operation) {
@@ -64,8 +100,10 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
 	// NOTE: only mention info/components/paths/... key in this func's errors.
 
 	wrap = func(e error) error { return fmt.Errorf("invalid components: %w", e) }
-	if err := doc.Components.Validate(ctx); err != nil {
-		return wrap(err)
+	if v := doc.Components; v != nil {
+		if err := v.Validate(ctx); err != nil {
+			return wrap(err)
+		}
 	}
 
 	wrap = func(e error) error { return fmt.Errorf("invalid info: %w", e) }
@@ -114,5 +152,5 @@ func (doc *T) Validate(ctx context.Context, opts ...ValidationOption) error {
 		}
 	}
 
-	return nil
+	return validateExtensions(ctx, doc.Extensions)
 }
diff --git a/openapi3/operation.go b/openapi3/operation.go
index d877049..645c080 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -2,19 +2,18 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"strconv"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Operation represents "operation" specified by" OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#operation-object
 type Operation struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	// Optional tags for documentation.
 	Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
@@ -58,13 +57,70 @@ func NewOperation() *Operation {
 }
 
 // MarshalJSON returns the JSON encoding of Operation.
-func (operation *Operation) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(operation)
+func (operation Operation) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 12+len(operation.Extensions))
+	for k, v := range operation.Extensions {
+		m[k] = v
+	}
+	if x := operation.Tags; len(x) != 0 {
+		m["tags"] = x
+	}
+	if x := operation.Summary; x != "" {
+		m["summary"] = x
+	}
+	if x := operation.Description; x != "" {
+		m["description"] = x
+	}
+	if x := operation.OperationID; x != "" {
+		m["operationId"] = x
+	}
+	if x := operation.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	if x := operation.RequestBody; x != nil {
+		m["requestBody"] = x
+	}
+	m["responses"] = operation.Responses
+	if x := operation.Callbacks; len(x) != 0 {
+		m["callbacks"] = x
+	}
+	if x := operation.Deprecated; x {
+		m["deprecated"] = x
+	}
+	if x := operation.Security; x != nil {
+		m["security"] = x
+	}
+	if x := operation.Servers; x != nil {
+		m["servers"] = x
+	}
+	if x := operation.ExternalDocs; x != nil {
+		m["externalDocs"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Operation to a copy of data.
 func (operation *Operation) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, operation)
+	type OperationBis Operation
+	var x OperationBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "tags")
+	delete(x.Extensions, "summary")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "operationId")
+	delete(x.Extensions, "parameters")
+	delete(x.Extensions, "requestBody")
+	delete(x.Extensions, "responses")
+	delete(x.Extensions, "callbacks")
+	delete(x.Extensions, "deprecated")
+	delete(x.Extensions, "security")
+	delete(x.Extensions, "servers")
+	delete(x.Extensions, "externalDocs")
+	*operation = Operation(x)
+	return nil
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
@@ -101,7 +157,7 @@ func (operation Operation) JSONLookup(token string) (interface{}, error) {
 		return operation.ExternalDocs, nil
 	}
 
-	v, _, err := jsonpointer.GetForToken(operation.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(operation.Extensions, token)
 	return v, err
 }
 
@@ -156,5 +212,5 @@ func (operation *Operation) Validate(ctx context.Context, opts ...ValidationOpti
 		}
 	}
 
-	return nil
+	return validateExtensions(ctx, operation.Extensions)
 }
diff --git a/openapi3/parameter.go b/openapi3/parameter.go
index 04e13b2..ec1893e 100644
--- a/openapi3/parameter.go
+++ b/openapi3/parameter.go
@@ -2,14 +2,13 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"sort"
 	"strconv"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type ParametersMap map[string]*ParameterRef
@@ -92,7 +91,7 @@ func (parameters Parameters) Validate(ctx context.Context, opts ...ValidationOpt
 // Parameter is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#parameter-object
 type Parameter struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Name            string      `json:"name,omitempty" yaml:"name,omitempty"`
 	In              string      `json:"in,omitempty" yaml:"in,omitempty"`
@@ -169,13 +168,80 @@ func (parameter *Parameter) WithSchema(value *Schema) *Parameter {
 }
 
 // MarshalJSON returns the JSON encoding of Parameter.
-func (parameter *Parameter) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(parameter)
+func (parameter Parameter) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 13+len(parameter.Extensions))
+	for k, v := range parameter.Extensions {
+		m[k] = v
+	}
+
+	if x := parameter.Name; x != "" {
+		m["name"] = x
+	}
+	if x := parameter.In; x != "" {
+		m["in"] = x
+	}
+	if x := parameter.Description; x != "" {
+		m["description"] = x
+	}
+	if x := parameter.Style; x != "" {
+		m["style"] = x
+	}
+	if x := parameter.Explode; x != nil {
+		m["explode"] = x
+	}
+	if x := parameter.AllowEmptyValue; x {
+		m["allowEmptyValue"] = x
+	}
+	if x := parameter.AllowReserved; x {
+		m["allowReserved"] = x
+	}
+	if x := parameter.Deprecated; x {
+		m["deprecated"] = x
+	}
+	if x := parameter.Required; x {
+		m["required"] = x
+	}
+	if x := parameter.Schema; x != nil {
+		m["schema"] = x
+	}
+	if x := parameter.Example; x != nil {
+		m["example"] = x
+	}
+	if x := parameter.Examples; len(x) != 0 {
+		m["examples"] = x
+	}
+	if x := parameter.Content; len(x) != 0 {
+		m["content"] = x
+	}
+
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Parameter to a copy of data.
 func (parameter *Parameter) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, parameter)
+	type ParameterBis Parameter
+	var x ParameterBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "in")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "style")
+	delete(x.Extensions, "explode")
+	delete(x.Extensions, "allowEmptyValue")
+	delete(x.Extensions, "allowReserved")
+	delete(x.Extensions, "deprecated")
+	delete(x.Extensions, "required")
+	delete(x.Extensions, "schema")
+	delete(x.Extensions, "example")
+	delete(x.Extensions, "examples")
+	delete(x.Extensions, "content")
+
+	*parameter = Parameter(x)
+	return nil
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
@@ -214,7 +280,7 @@ func (parameter Parameter) JSONLookup(token string) (interface{}, error) {
 		return parameter.Content, nil
 	}
 
-	v, _, err := jsonpointer.GetForToken(parameter.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(parameter.Extensions, token)
 	return v, err
 }
 
@@ -348,5 +414,5 @@ func (parameter *Parameter) Validate(ctx context.Context, opts ...ValidationOpti
 		}
 	}
 
-	return nil
+	return validateExtensions(ctx, parameter.Extensions)
 }
diff --git a/openapi3/path_item.go b/openapi3/path_item.go
index 5323dc1..fab75d9 100644
--- a/openapi3/path_item.go
+++ b/openapi3/path_item.go
@@ -2,17 +2,16 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"sort"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // PathItem is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#path-item-object
 type PathItem struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Ref         string     `json:"$ref,omitempty" yaml:"$ref,omitempty"`
 	Summary     string     `json:"summary,omitempty" yaml:"summary,omitempty"`
@@ -31,13 +30,81 @@ type PathItem struct {
 }
 
 // MarshalJSON returns the JSON encoding of PathItem.
-func (pathItem *PathItem) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(pathItem)
+func (pathItem PathItem) MarshalJSON() ([]byte, error) {
+	if ref := pathItem.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+
+	m := make(map[string]interface{}, 13+len(pathItem.Extensions))
+	for k, v := range pathItem.Extensions {
+		m[k] = v
+	}
+	if x := pathItem.Summary; x != "" {
+		m["summary"] = x
+	}
+	if x := pathItem.Description; x != "" {
+		m["description"] = x
+	}
+	if x := pathItem.Connect; x != nil {
+		m["connect"] = x
+	}
+	if x := pathItem.Delete; x != nil {
+		m["delete"] = x
+	}
+	if x := pathItem.Get; x != nil {
+		m["get"] = x
+	}
+	if x := pathItem.Head; x != nil {
+		m["head"] = x
+	}
+	if x := pathItem.Options; x != nil {
+		m["options"] = x
+	}
+	if x := pathItem.Patch; x != nil {
+		m["patch"] = x
+	}
+	if x := pathItem.Post; x != nil {
+		m["post"] = x
+	}
+	if x := pathItem.Put; x != nil {
+		m["put"] = x
+	}
+	if x := pathItem.Trace; x != nil {
+		m["trace"] = x
+	}
+	if x := pathItem.Servers; len(x) != 0 {
+		m["servers"] = x
+	}
+	if x := pathItem.Parameters; len(x) != 0 {
+		m["parameters"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets PathItem to a copy of data.
 func (pathItem *PathItem) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, pathItem)
+	type PathItemBis PathItem
+	var x PathItemBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "$ref")
+	delete(x.Extensions, "summary")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "connect")
+	delete(x.Extensions, "delete")
+	delete(x.Extensions, "get")
+	delete(x.Extensions, "head")
+	delete(x.Extensions, "options")
+	delete(x.Extensions, "patch")
+	delete(x.Extensions, "post")
+	delete(x.Extensions, "put")
+	delete(x.Extensions, "trace")
+	delete(x.Extensions, "servers")
+	delete(x.Extensions, "parameters")
+	*pathItem = PathItem(x)
+	return nil
 }
 
 func (pathItem *PathItem) Operations() map[string]*Operation {
@@ -139,5 +206,6 @@ func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption
 			return fmt.Errorf("invalid operation %s: %v", method, err)
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, pathItem.Extensions)
 }
diff --git a/openapi3/ref.go b/openapi3/ref.go
new file mode 100644
index 0000000..a937de4
--- /dev/null
+++ b/openapi3/ref.go
@@ -0,0 +1,7 @@
+package openapi3
+
+// Ref is specified by OpenAPI/Swagger 3.0 standard.
+// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
+type Ref struct {
+	Ref string `json:"$ref" yaml:"$ref"`
+}
diff --git a/openapi3/refs.go b/openapi3/refs.go
index d36d562..cc9b41a 100644
--- a/openapi3/refs.go
+++ b/openapi3/refs.go
@@ -2,58 +2,88 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
+	"fmt"
+	"sort"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
+	"github.com/perimeterx/marshmallow"
 )
 
-// Ref is specified by OpenAPI/Swagger 3.0 standard.
-// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#reference-object
-type Ref struct {
-	Ref string `json:"$ref" yaml:"$ref"`
-}
-
 // CallbackRef represents either a Callback or a $ref to a Callback.
 // When serializing and both fields are set, Ref is preferred over Value.
 type CallbackRef struct {
 	Ref   string
 	Value *Callback
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*CallbackRef)(nil)
 
 // MarshalYAML returns the YAML encoding of CallbackRef.
-func (value *CallbackRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x CallbackRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of CallbackRef.
-func (value *CallbackRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x CallbackRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return json.Marshal(x.Value)
 }
 
 // UnmarshalJSON sets CallbackRef to a copy of data.
-func (value *CallbackRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *CallbackRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if CallbackRef does not comply with the OpenAPI spec.
-func (value *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *CallbackRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value CallbackRef) JSONLookup(token string) (interface{}, error) {
+func (x *CallbackRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
@@ -62,41 +92,75 @@ func (value CallbackRef) JSONLookup(token string) (interface{}, error) {
 type ExampleRef struct {
 	Ref   string
 	Value *Example
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*ExampleRef)(nil)
 
 // MarshalYAML returns the YAML encoding of ExampleRef.
-func (value *ExampleRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x ExampleRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of ExampleRef.
-func (value *ExampleRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x ExampleRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets ExampleRef to a copy of data.
-func (value *ExampleRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *ExampleRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if ExampleRef does not comply with the OpenAPI spec.
-func (value *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *ExampleRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value ExampleRef) JSONLookup(token string) (interface{}, error) {
+func (x *ExampleRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
@@ -105,41 +169,75 @@ func (value ExampleRef) JSONLookup(token string) (interface{}, error) {
 type HeaderRef struct {
 	Ref   string
 	Value *Header
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*HeaderRef)(nil)
 
 // MarshalYAML returns the YAML encoding of HeaderRef.
-func (value *HeaderRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x HeaderRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of HeaderRef.
-func (value *HeaderRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x HeaderRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets HeaderRef to a copy of data.
-func (value *HeaderRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *HeaderRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if HeaderRef does not comply with the OpenAPI spec.
-func (value *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *HeaderRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value HeaderRef) JSONLookup(token string) (interface{}, error) {
+func (x *HeaderRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
@@ -148,30 +246,76 @@ func (value HeaderRef) JSONLookup(token string) (interface{}, error) {
 type LinkRef struct {
 	Ref   string
 	Value *Link
+	extra []string
 }
 
+var _ jsonpointer.JSONPointable = (*LinkRef)(nil)
+
 // MarshalYAML returns the YAML encoding of LinkRef.
-func (value *LinkRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x LinkRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of LinkRef.
-func (value *LinkRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x LinkRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets LinkRef to a copy of data.
-func (value *LinkRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *LinkRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if LinkRef does not comply with the OpenAPI spec.
-func (value *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
+}
+
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (x *LinkRef) JSONLookup(token string) (interface{}, error) {
+	if token == "$ref" {
+		return x.Ref, nil
+	}
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
+	return ptr, err
 }
 
 // ParameterRef represents either a Parameter or a $ref to a Parameter.
@@ -179,127 +323,229 @@ func (value *LinkRef) Validate(ctx context.Context, opts ...ValidationOption) er
 type ParameterRef struct {
 	Ref   string
 	Value *Parameter
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*ParameterRef)(nil)
 
 // MarshalYAML returns the YAML encoding of ParameterRef.
-func (value *ParameterRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x ParameterRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of ParameterRef.
-func (value *ParameterRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x ParameterRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets ParameterRef to a copy of data.
-func (value *ParameterRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *ParameterRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if ParameterRef does not comply with the OpenAPI spec.
-func (value *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *ParameterRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value ParameterRef) JSONLookup(token string) (interface{}, error) {
+func (x *ParameterRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
-// ResponseRef represents either a Response or a $ref to a Response.
+// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
 // When serializing and both fields are set, Ref is preferred over Value.
-type ResponseRef struct {
+type RequestBodyRef struct {
 	Ref   string
-	Value *Response
+	Value *RequestBody
+	extra []string
 }
 
-var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
+var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
 
-// MarshalYAML returns the YAML encoding of ResponseRef.
-func (value *ResponseRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+// MarshalYAML returns the YAML encoding of RequestBodyRef.
+func (x RequestBodyRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
-// MarshalJSON returns the JSON encoding of ResponseRef.
-func (value *ResponseRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+// MarshalJSON returns the JSON encoding of RequestBodyRef.
+func (x RequestBodyRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
-// UnmarshalJSON sets ResponseRef to a copy of data.
-func (value *ResponseRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+// UnmarshalJSON sets RequestBodyRef to a copy of data.
+func (x *RequestBodyRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
-// Validate returns an error if ResponseRef does not comply with the OpenAPI spec.
-func (value *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec.
+func (x *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value ResponseRef) JSONLookup(token string) (interface{}, error) {
+func (x *RequestBodyRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
-// RequestBodyRef represents either a RequestBody or a $ref to a RequestBody.
+// ResponseRef represents either a Response or a $ref to a Response.
 // When serializing and both fields are set, Ref is preferred over Value.
-type RequestBodyRef struct {
+type ResponseRef struct {
 	Ref   string
-	Value *RequestBody
+	Value *Response
+	extra []string
 }
 
-var _ jsonpointer.JSONPointable = (*RequestBodyRef)(nil)
+var _ jsonpointer.JSONPointable = (*ResponseRef)(nil)
 
-// MarshalYAML returns the YAML encoding of RequestBodyRef.
-func (value *RequestBodyRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+// MarshalYAML returns the YAML encoding of ResponseRef.
+func (x ResponseRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
-// MarshalJSON returns the JSON encoding of RequestBodyRef.
-func (value *RequestBodyRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+// MarshalJSON returns the JSON encoding of ResponseRef.
+func (x ResponseRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
-// UnmarshalJSON sets RequestBodyRef to a copy of data.
-func (value *RequestBodyRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+// UnmarshalJSON sets ResponseRef to a copy of data.
+func (x *ResponseRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
-// Validate returns an error if RequestBodyRef does not comply with the OpenAPI spec.
-func (value *RequestBodyRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+// Validate returns an error if ResponseRef does not comply with the OpenAPI spec.
+func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) {
+func (x *ResponseRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
@@ -308,48 +554,75 @@ func (value RequestBodyRef) JSONLookup(token string) (interface{}, error) {
 type SchemaRef struct {
 	Ref   string
 	Value *Schema
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*SchemaRef)(nil)
 
-func NewSchemaRef(ref string, value *Schema) *SchemaRef {
-	return &SchemaRef{
-		Ref:   ref,
-		Value: value,
-	}
-}
-
 // MarshalYAML returns the YAML encoding of SchemaRef.
-func (value *SchemaRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x SchemaRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of SchemaRef.
-func (value *SchemaRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x SchemaRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets SchemaRef to a copy of data.
-func (value *SchemaRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if SchemaRef does not comply with the OpenAPI spec.
-func (value *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *SchemaRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value SchemaRef) JSONLookup(token string) (interface{}, error) {
+func (x *SchemaRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
 
@@ -358,48 +631,74 @@ func (value SchemaRef) JSONLookup(token string) (interface{}, error) {
 type SecuritySchemeRef struct {
 	Ref   string
 	Value *SecurityScheme
+	extra []string
 }
 
 var _ jsonpointer.JSONPointable = (*SecuritySchemeRef)(nil)
 
 // MarshalYAML returns the YAML encoding of SecuritySchemeRef.
-func (value *SecuritySchemeRef) MarshalYAML() (interface{}, error) {
-	return marshalRefYAML(value.Ref, value.Value)
+func (x SecuritySchemeRef) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
 }
 
 // MarshalJSON returns the JSON encoding of SecuritySchemeRef.
-func (value *SecuritySchemeRef) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalRef(value.Ref, value.Value)
+func (x SecuritySchemeRef) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+	return x.Value.MarshalJSON()
 }
 
 // UnmarshalJSON sets SecuritySchemeRef to a copy of data.
-func (value *SecuritySchemeRef) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalRef(data, &value.Ref, &value.Value)
+func (x *SecuritySchemeRef) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
 }
 
 // Validate returns an error if SecuritySchemeRef does not comply with the OpenAPI spec.
-func (value *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (x *SecuritySchemeRef) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
-	if v := value.Value; v != nil {
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
 		return v.Validate(ctx)
 	}
-	return foundUnresolvedRef(value.Ref)
+	return foundUnresolvedRef(x.Ref)
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
-func (value SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
+func (x *SecuritySchemeRef) JSONLookup(token string) (interface{}, error) {
 	if token == "$ref" {
-		return value.Ref, nil
+		return x.Ref, nil
 	}
-
-	ptr, _, err := jsonpointer.GetForToken(value.Value, token)
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
 	return ptr, err
 }
-
-// marshalRefYAML returns the YAML encoding of ref values.
-func marshalRefYAML(value string, otherwise interface{}) (interface{}, error) {
-	if value != "" {
-		return &Ref{Ref: value}, nil
-	}
-	return otherwise, nil
-}
diff --git a/openapi3/request_body.go b/openapi3/request_body.go
index f0d9e1e..de8919f 100644
--- a/openapi3/request_body.go
+++ b/openapi3/request_body.go
@@ -2,12 +2,11 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type RequestBodies map[string]*RequestBodyRef
@@ -30,7 +29,7 @@ func (r RequestBodies) JSONLookup(token string) (interface{}, error) {
 // RequestBody is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#request-body-object
 type RequestBody struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Description string  `json:"description,omitempty" yaml:"description,omitempty"`
 	Required    bool    `json:"required,omitempty" yaml:"required,omitempty"`
@@ -95,13 +94,36 @@ func (requestBody *RequestBody) GetMediaType(mediaType string) *MediaType {
 }
 
 // MarshalJSON returns the JSON encoding of RequestBody.
-func (requestBody *RequestBody) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(requestBody)
+func (requestBody RequestBody) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 3+len(requestBody.Extensions))
+	for k, v := range requestBody.Extensions {
+		m[k] = v
+	}
+	if x := requestBody.Description; x != "" {
+		m["description"] = requestBody.Description
+	}
+	if x := requestBody.Required; x {
+		m["required"] = x
+	}
+	if x := requestBody.Content; true {
+		m["content"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets RequestBody to a copy of data.
 func (requestBody *RequestBody) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, requestBody)
+	type RequestBodyBis RequestBody
+	var x RequestBodyBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "required")
+	delete(x.Extensions, "content")
+	*requestBody = RequestBody(x)
+	return nil
 }
 
 // Validate returns an error if RequestBody does not comply with the OpenAPI spec.
@@ -116,5 +138,9 @@ func (requestBody *RequestBody) Validate(ctx context.Context, opts ...Validation
 		vo.examplesValidationAsReq, vo.examplesValidationAsRes = true, false
 	}
 
-	return requestBody.Content.Validate(ctx)
+	if err := requestBody.Content.Validate(ctx); err != nil {
+		return err
+	}
+
+	return validateExtensions(ctx, requestBody.Extensions)
 }
diff --git a/openapi3/response.go b/openapi3/response.go
index 324f77d..b85c914 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -2,14 +2,13 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"sort"
 	"strconv"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Responses is specified by OpenAPI/Swagger 3.0 standard.
@@ -70,7 +69,7 @@ func (responses Responses) JSONLookup(token string) (interface{}, error) {
 // Response is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object
 type Response struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Description *string `json:"description,omitempty" yaml:"description,omitempty"`
 	Headers     Headers `json:"headers,omitempty" yaml:"headers,omitempty"`
@@ -103,13 +102,40 @@ func (response *Response) WithJSONSchemaRef(schema *SchemaRef) *Response {
 }
 
 // MarshalJSON returns the JSON encoding of Response.
-func (response *Response) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(response)
+func (response Response) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(response.Extensions))
+	for k, v := range response.Extensions {
+		m[k] = v
+	}
+	if x := response.Description; x != nil {
+		m["description"] = x
+	}
+	if x := response.Headers; len(x) != 0 {
+		m["headers"] = x
+	}
+	if x := response.Content; len(x) != 0 {
+		m["content"] = x
+	}
+	if x := response.Links; len(x) != 0 {
+		m["links"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Response to a copy of data.
 func (response *Response) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, response)
+	type ResponseBis Response
+	var x ResponseBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "headers")
+	delete(x.Extensions, "content")
+	delete(x.Extensions, "links")
+	*response = Response(x)
+	return nil
 }
 
 // Validate returns an error if Response does not comply with the OpenAPI spec.
@@ -152,5 +178,6 @@ func (response *Response) Validate(ctx context.Context, opts ...ValidationOption
 			return err
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, response.Extensions)
 }
diff --git a/openapi3/schema.go b/openapi3/schema.go
index d795fb5..9d21c00 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -17,8 +17,6 @@ import (
 
 	"github.com/go-openapi/jsonpointer"
 	"github.com/mohae/deepcopy"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 const (
@@ -71,6 +69,14 @@ func Uint64Ptr(value uint64) *uint64 {
 	return &value
 }
 
+// NewSchemaRef simply builds a SchemaRef
+func NewSchemaRef(ref string, value *Schema) *SchemaRef {
+	return &SchemaRef{
+		Ref:   ref,
+		Value: value,
+	}
+}
+
 type Schemas map[string]*SchemaRef
 
 var _ jsonpointer.JSONPointable = (*Schemas)(nil)
@@ -114,7 +120,7 @@ func (s SchemaRefs) JSONLookup(token string) (interface{}, error) {
 // Schema is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
 type Schema struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	OneOf        SchemaRefs    `json:"oneOf,omitempty" yaml:"oneOf,omitempty"`
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
@@ -159,13 +165,57 @@ type Schema struct {
 	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"`
-	AdditionalPropertiesAllowed *bool          `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // In this order...
-	AdditionalProperties        *SchemaRef     `multijson:"additionalProperties,omitempty" json:"-" yaml:"-"` // ...for multijson
-	Discriminator               *Discriminator `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
+	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 AdditionalProperties `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
+	Discriminator        *Discriminator       `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
+}
+
+type AdditionalProperties struct {
+	Has    *bool
+	Schema *SchemaRef
+}
+
+// MarshalJSON returns the JSON encoding of AdditionalProperties.
+func (addProps AdditionalProperties) MarshalJSON() ([]byte, error) {
+	if x := addProps.Has; x != nil {
+		if *x {
+			return []byte("true"), nil
+		}
+		return []byte("false"), nil
+	}
+	if x := addProps.Schema; x != nil {
+		return json.Marshal(x)
+	}
+	return nil, nil
+}
+
+// UnmarshalJSON sets AdditionalProperties to a copy of data.
+func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error {
+	var x interface{}
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	switch y := x.(type) {
+	case nil:
+	case bool:
+		addProps.Has = &y
+	case map[string]interface{}:
+		if len(y) == 0 {
+			addProps.Schema = &SchemaRef{Value: &Schema{}}
+		} else {
+			buf := new(bytes.Buffer)
+			json.NewEncoder(buf).Encode(y)
+			if err := json.NewDecoder(buf).Decode(&addProps.Schema); err != nil {
+				return err
+			}
+		}
+	default:
+		return errors.New("cannot unmarshal additionalProperties: value must be either a schema object or a boolean")
+	}
+	return nil
 }
 
 var _ jsonpointer.JSONPointable = (*Schema)(nil)
@@ -175,31 +225,217 @@ func NewSchema() *Schema {
 }
 
 // MarshalJSON returns the JSON encoding of Schema.
-func (schema *Schema) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(schema)
+func (schema Schema) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 36+len(schema.Extensions))
+	for k, v := range schema.Extensions {
+		m[k] = v
+	}
+
+	if x := schema.OneOf; len(x) != 0 {
+		m["oneOf"] = x
+	}
+	if x := schema.AnyOf; len(x) != 0 {
+		m["anyOf"] = x
+	}
+	if x := schema.AllOf; len(x) != 0 {
+		m["allOf"] = x
+	}
+	if x := schema.Not; x != nil {
+		m["not"] = x
+	}
+	if x := schema.Type; len(x) != 0 {
+		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
+	}
+	// Properties
+	if x := schema.Nullable; x {
+		m["nullable"] = 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 != nil {
+		m["discriminator"] = x
+	}
+
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Schema to a copy of data.
 func (schema *Schema) UnmarshalJSON(data []byte) error {
-	err := jsoninfo.UnmarshalStrictStruct(data, schema)
+	type SchemaBis Schema
+	var x SchemaBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return 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")
+
+	*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 err
+	return nil
 }
 
 // JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
 func (schema Schema) JSONLookup(token string) (interface{}, error) {
 	switch token {
 	case "additionalProperties":
-		if schema.AdditionalProperties != nil {
-			if schema.AdditionalProperties.Ref != "" {
-				return &Ref{Ref: schema.AdditionalProperties.Ref}, nil
+		if addProps := schema.AdditionalProperties.Has; addProps != nil {
+			return *addProps, nil
+		}
+		if addProps := schema.AdditionalProperties.Schema; addProps != nil {
+			if addProps.Ref != "" {
+				return &Ref{Ref: addProps.Ref}, nil
 			}
-			return schema.AdditionalProperties.Value, nil
+			return addProps.Value, nil
 		}
 	case "not":
 		if schema.Not != nil {
@@ -237,8 +473,6 @@ func (schema Schema) JSONLookup(token string) (interface{}, error) {
 		return schema.Example, nil
 	case "externalDocs":
 		return schema.ExternalDocs, nil
-	case "additionalPropertiesAllowed":
-		return schema.AdditionalPropertiesAllowed, nil
 	case "uniqueItems":
 		return schema.UniqueItems, nil
 	case "exclusiveMin":
@@ -285,7 +519,7 @@ func (schema Schema) JSONLookup(token string) (interface{}, error) {
 		return schema.Discriminator, nil
 	}
 
-	v, _, err := jsonpointer.GetForToken(schema.ExtensionProps, token)
+	v, _, err := jsonpointer.GetForToken(schema.Extensions, token)
 	return v, err
 }
 
@@ -546,23 +780,19 @@ func (schema *Schema) WithMaxProperties(i int64) *Schema {
 }
 
 func (schema *Schema) WithAnyAdditionalProperties() *Schema {
-	schema.AdditionalProperties = nil
-	t := true
-	schema.AdditionalPropertiesAllowed = &t
+	schema.AdditionalProperties = AdditionalProperties{Has: BoolPtr(true)}
 	return schema
 }
 
 func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema {
-	if v == nil {
-		schema.AdditionalProperties = nil
-	} else {
-		schema.AdditionalProperties = &SchemaRef{
-			Value: v,
-		}
+	schema.AdditionalProperties = AdditionalProperties{}
+	if v != nil {
+		schema.AdditionalProperties.Schema = &SchemaRef{Value: v}
 	}
 	return schema
 }
 
+// IsEmpty tells whether schema is equivalent to the empty schema `{}`.
 func (schema *Schema) IsEmpty() bool {
 	if schema.Type != "" || schema.Format != "" || len(schema.Enum) != 0 ||
 		schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax ||
@@ -577,10 +807,10 @@ func (schema *Schema) IsEmpty() bool {
 	if n := schema.Not; n != nil && !n.Value.IsEmpty() {
 		return false
 	}
-	if ap := schema.AdditionalProperties; ap != nil && !ap.Value.IsEmpty() {
+	if ap := schema.AdditionalProperties.Schema; ap != nil && !ap.Value.IsEmpty() {
 		return false
 	}
-	if apa := schema.AdditionalPropertiesAllowed; apa != nil && !*apa {
+	if apa := schema.AdditionalProperties.Has; apa != nil && *apa {
 		return false
 	}
 	if items := schema.Items; items != nil && !items.Value.IsEmpty() {
@@ -615,12 +845,12 @@ func (schema *Schema) Validate(ctx context.Context, opts ...ValidationOption) er
 	return schema.validate(ctx, []*Schema{})
 }
 
-func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error) {
+func (schema *Schema) validate(ctx context.Context, stack []*Schema) error {
 	validationOpts := getValidationOptions(ctx)
 
 	for _, existing := range stack {
 		if existing == schema {
-			return
+			return nil
 		}
 	}
 	stack = append(stack, schema)
@@ -634,8 +864,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(item.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
@@ -644,8 +874,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(item.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
@@ -654,8 +884,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(item.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
@@ -664,8 +894,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(ref.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
@@ -716,7 +946,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 			}
 		}
 		if schema.Pattern != "" && !validationOpts.schemaPatternValidationDisabled {
-			if err = schema.compilePattern(); err != nil {
+			if err := schema.compilePattern(); err != nil {
 				return err
 			}
 		}
@@ -734,8 +964,8 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(ref.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
@@ -750,23 +980,26 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		if v == nil {
 			return foundUnresolvedRef(ref.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
-	if ref := schema.AdditionalProperties; ref != nil {
+	if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {
+		return errors.New("additionalProperties are set to both boolean and schema")
+	}
+	if ref := schema.AdditionalProperties.Schema; ref != nil {
 		v := ref.Value
 		if v == nil {
 			return foundUnresolvedRef(ref.Ref)
 		}
-		if err = v.validate(ctx, stack); err != nil {
-			return
+		if err := v.validate(ctx, stack); err != nil {
+			return err
 		}
 	}
 
 	if v := schema.ExternalDocs; v != nil {
-		if err = v.Validate(ctx); err != nil {
+		if err := v.Validate(ctx); err != nil {
 			return fmt.Errorf("invalid external docs: %w", err)
 		}
 	}
@@ -783,7 +1016,7 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) (err error)
 		}
 	}
 
-	return
+	return validateExtensions(ctx, schema.Extensions)
 }
 
 func (schema *Schema) IsMatching(value interface{}) bool {
@@ -1572,7 +1805,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 
 	// "additionalProperties"
 	var additionalProperties *Schema
-	if ref := schema.AdditionalProperties; ref != nil {
+	if ref := schema.AdditionalProperties.Schema; ref != nil {
 		additionalProperties = ref.Value
 	}
 	keys := make([]string, 0, len(value))
@@ -1606,8 +1839,7 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 				continue
 			}
 		}
-		allowed := schema.AdditionalPropertiesAllowed
-		if additionalProperties != nil || allowed == nil || *allowed {
+		if allowed := schema.AdditionalProperties.Has; allowed == nil || *allowed {
 			if additionalProperties != nil {
 				if err := additionalProperties.visitJSON(settings, v); err != nil {
 					if settings.failfast {
diff --git a/openapi3/security_requirements.go b/openapi3/security_requirements.go
index 3f5bd95..87891c9 100644
--- a/openapi3/security_requirements.go
+++ b/openapi3/security_requirements.go
@@ -45,7 +45,7 @@ func (security SecurityRequirement) Authenticate(provider string, scopes ...stri
 
 // Validate returns an error if SecurityRequirement does not comply with the OpenAPI spec.
 func (security *SecurityRequirement) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	return nil
 }
diff --git a/openapi3/security_scheme.go b/openapi3/security_scheme.go
index 83330f2..f9a0838 100644
--- a/openapi3/security_scheme.go
+++ b/openapi3/security_scheme.go
@@ -2,13 +2,12 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/url"
 
 	"github.com/go-openapi/jsonpointer"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 type SecuritySchemes map[string]*SecuritySchemeRef
@@ -31,7 +30,7 @@ var _ jsonpointer.JSONPointable = (*SecuritySchemes)(nil)
 // SecurityScheme is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object
 type SecurityScheme struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Type             string      `json:"type,omitempty" yaml:"type,omitempty"`
 	Description      string      `json:"description,omitempty" yaml:"description,omitempty"`
@@ -71,13 +70,56 @@ func NewJWTSecurityScheme() *SecurityScheme {
 }
 
 // MarshalJSON returns the JSON encoding of SecurityScheme.
-func (ss *SecurityScheme) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(ss)
+func (ss SecurityScheme) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 8+len(ss.Extensions))
+	for k, v := range ss.Extensions {
+		m[k] = v
+	}
+	if x := ss.Type; x != "" {
+		m["type"] = x
+	}
+	if x := ss.Description; x != "" {
+		m["description"] = x
+	}
+	if x := ss.Name; x != "" {
+		m["name"] = x
+	}
+	if x := ss.In; x != "" {
+		m["in"] = x
+	}
+	if x := ss.Scheme; x != "" {
+		m["scheme"] = x
+	}
+	if x := ss.BearerFormat; x != "" {
+		m["bearerFormat"] = x
+	}
+	if x := ss.Flows; x != nil {
+		m["flows"] = x
+	}
+	if x := ss.OpenIdConnectUrl; x != "" {
+		m["openIdConnectUrl"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets SecurityScheme to a copy of data.
 func (ss *SecurityScheme) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, ss)
+	type SecuritySchemeBis SecurityScheme
+	var x SecuritySchemeBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "type")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "in")
+	delete(x.Extensions, "scheme")
+	delete(x.Extensions, "bearerFormat")
+	delete(x.Extensions, "flows")
+	delete(x.Extensions, "openIdConnectUrl")
+	*ss = SecurityScheme(x)
+	return nil
 }
 
 func (ss *SecurityScheme) WithType(value string) *SecurityScheme {
@@ -173,13 +215,14 @@ func (ss *SecurityScheme) Validate(ctx context.Context, opts ...ValidationOption
 	} else if ss.Flows != nil {
 		return fmt.Errorf("security scheme of type %q can't have 'flows'", ss.Type)
 	}
-	return nil
+
+	return validateExtensions(ctx, ss.Extensions)
 }
 
 // OAuthFlows is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object
 type OAuthFlows struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Implicit          *OAuthFlow `json:"implicit,omitempty" yaml:"implicit,omitempty"`
 	Password          *OAuthFlow `json:"password,omitempty" yaml:"password,omitempty"`
@@ -197,13 +240,40 @@ const (
 )
 
 // MarshalJSON returns the JSON encoding of OAuthFlows.
-func (flows *OAuthFlows) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(flows)
+func (flows OAuthFlows) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(flows.Extensions))
+	for k, v := range flows.Extensions {
+		m[k] = v
+	}
+	if x := flows.Implicit; x != nil {
+		m["implicit"] = x
+	}
+	if x := flows.Password; x != nil {
+		m["password"] = x
+	}
+	if x := flows.ClientCredentials; x != nil {
+		m["clientCredentials"] = x
+	}
+	if x := flows.AuthorizationCode; x != nil {
+		m["authorizationCode"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets OAuthFlows to a copy of data.
 func (flows *OAuthFlows) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, flows)
+	type OAuthFlowsBis OAuthFlows
+	var x OAuthFlowsBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "implicit")
+	delete(x.Extensions, "password")
+	delete(x.Extensions, "clientCredentials")
+	delete(x.Extensions, "authorizationCode")
+	*flows = OAuthFlows(x)
+	return nil
 }
 
 // Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
@@ -215,48 +285,77 @@ func (flows *OAuthFlows) Validate(ctx context.Context, opts ...ValidationOption)
 			return fmt.Errorf("the OAuth flow 'implicit' is invalid: %w", err)
 		}
 	}
+
 	if v := flows.Password; v != nil {
 		if err := v.validate(ctx, oAuthFlowTypePassword, opts...); err != nil {
 			return fmt.Errorf("the OAuth flow 'password' is invalid: %w", err)
 		}
 	}
+
 	if v := flows.ClientCredentials; v != nil {
 		if err := v.validate(ctx, oAuthFlowTypeClientCredentials, opts...); err != nil {
 			return fmt.Errorf("the OAuth flow 'clientCredentials' is invalid: %w", err)
 		}
 	}
+
 	if v := flows.AuthorizationCode; v != nil {
 		if err := v.validate(ctx, oAuthFlowAuthorizationCode, opts...); err != nil {
 			return fmt.Errorf("the OAuth flow 'authorizationCode' is invalid: %w", err)
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, flows.Extensions)
 }
 
 // OAuthFlow is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flow-object
 type OAuthFlow struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	AuthorizationURL string            `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
 	TokenURL         string            `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
 	RefreshURL       string            `json:"refreshUrl,omitempty" yaml:"refreshUrl,omitempty"`
-	Scopes           map[string]string `json:"scopes" yaml:"scopes"`
+	Scopes           map[string]string `json:"scopes" yaml:"scopes"` // required
 }
 
 // MarshalJSON returns the JSON encoding of OAuthFlow.
-func (flow *OAuthFlow) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(flow)
+func (flow OAuthFlow) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(flow.Extensions))
+	for k, v := range flow.Extensions {
+		m[k] = v
+	}
+	if x := flow.AuthorizationURL; x != "" {
+		m["authorizationUrl"] = x
+	}
+	if x := flow.TokenURL; x != "" {
+		m["tokenUrl"] = x
+	}
+	if x := flow.RefreshURL; x != "" {
+		m["refreshUrl"] = x
+	}
+	m["scopes"] = flow.Scopes
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets OAuthFlow to a copy of data.
 func (flow *OAuthFlow) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, flow)
+	type OAuthFlowBis OAuthFlow
+	var x OAuthFlowBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "authorizationUrl")
+	delete(x.Extensions, "tokenUrl")
+	delete(x.Extensions, "refreshUrl")
+	delete(x.Extensions, "scopes")
+	*flow = OAuthFlow(x)
+	return nil
 }
 
 // Validate returns an error if OAuthFlows does not comply with the OpenAPI spec.
 func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if v := flow.RefreshURL; v != "" {
 		if _, err := url.Parse(v); err != nil {
@@ -268,7 +367,7 @@ func (flow *OAuthFlow) Validate(ctx context.Context, opts ...ValidationOption) e
 		return errors.New("field 'scopes' is empty or missing")
 	}
 
-	return nil
+	return validateExtensions(ctx, flow.Extensions)
 }
 
 func (flow *OAuthFlow) validate(ctx context.Context, typ oAuthFlowType, opts ...ValidationOption) error {
diff --git a/openapi3/server.go b/openapi3/server.go
index 587e8e0..9fc99f9 100644
--- a/openapi3/server.go
+++ b/openapi3/server.go
@@ -2,14 +2,13 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"math"
 	"net/url"
 	"sort"
 	"strings"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Servers is specified by OpenAPI/Swagger standard version 3.
@@ -52,9 +51,9 @@ func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string)
 // Server is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
 type Server struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
-	URL         string                     `json:"url" yaml:"url"`
+	URL         string                     `json:"url" yaml:"url"` // Required
 	Description string                     `json:"description,omitempty" yaml:"description,omitempty"`
 	Variables   map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
 }
@@ -84,13 +83,34 @@ func (server *Server) BasePath() (string, error) {
 }
 
 // MarshalJSON returns the JSON encoding of Server.
-func (server *Server) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(server)
+func (server Server) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 3+len(server.Extensions))
+	for k, v := range server.Extensions {
+		m[k] = v
+	}
+	m["url"] = server.URL
+	if x := server.Description; x != "" {
+		m["description"] = x
+	}
+	if x := server.Variables; len(x) != 0 {
+		m["variables"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Server to a copy of data.
 func (server *Server) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, server)
+	type ServerBis Server
+	var x ServerBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "url")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "variables")
+	*server = Server(x)
+	return nil
 }
 
 func (server Server) ParameterNames() ([]string, error) {
@@ -195,13 +215,14 @@ func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (e
 			return
 		}
 	}
-	return
+
+	return validateExtensions(ctx, server.Extensions)
 }
 
 // ServerVariable is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
 type ServerVariable struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Enum        []string `json:"enum,omitempty" yaml:"enum,omitempty"`
 	Default     string   `json:"default,omitempty" yaml:"default,omitempty"`
@@ -209,18 +230,41 @@ type ServerVariable struct {
 }
 
 // MarshalJSON returns the JSON encoding of ServerVariable.
-func (serverVariable *ServerVariable) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(serverVariable)
+func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 4+len(serverVariable.Extensions))
+	for k, v := range serverVariable.Extensions {
+		m[k] = v
+	}
+	if x := serverVariable.Enum; len(x) != 0 {
+		m["enum"] = x
+	}
+	if x := serverVariable.Default; x != "" {
+		m["default"] = x
+	}
+	if x := serverVariable.Description; x != "" {
+		m["description"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets ServerVariable to a copy of data.
 func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, serverVariable)
+	type ServerVariableBis ServerVariable
+	var x ServerVariableBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "enum")
+	delete(x.Extensions, "default")
+	delete(x.Extensions, "description")
+	*serverVariable = ServerVariable(x)
+	return nil
 }
 
 // Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
 func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
 	if serverVariable.Default == "" {
 		data, err := serverVariable.MarshalJSON()
@@ -229,5 +273,6 @@ func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...Vali
 		}
 		return fmt.Errorf("field default is required in %s", data)
 	}
-	return nil
+
+	return validateExtensions(ctx, serverVariable.Extensions)
 }
diff --git a/openapi3/tag.go b/openapi3/tag.go
index b5cb7f8..93009a1 100644
--- a/openapi3/tag.go
+++ b/openapi3/tag.go
@@ -2,9 +2,8 @@ package openapi3
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
 )
 
 // Tags is specified by OpenAPI/Swagger 3.0 standard.
@@ -34,7 +33,7 @@ func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error {
 // Tag is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#tag-object
 type Tag struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Name         string        `json:"name,omitempty" yaml:"name,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
@@ -42,13 +41,36 @@ type Tag struct {
 }
 
 // MarshalJSON returns the JSON encoding of Tag.
-func (t *Tag) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(t)
+func (t Tag) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 3+len(t.Extensions))
+	for k, v := range t.Extensions {
+		m[k] = v
+	}
+	if x := t.Name; x != "" {
+		m["name"] = x
+	}
+	if x := t.Description; x != "" {
+		m["description"] = x
+	}
+	if x := t.ExternalDocs; x != nil {
+		m["externalDocs"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets Tag to a copy of data.
 func (t *Tag) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, t)
+	type TagBis Tag
+	var x TagBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "description")
+	delete(x.Extensions, "externalDocs")
+	*t = Tag(x)
+	return nil
 }
 
 // Validate returns an error if Tag does not comply with the OpenAPI spec.
@@ -60,5 +82,6 @@ func (t *Tag) Validate(ctx context.Context, opts ...ValidationOption) error {
 			return fmt.Errorf("invalid external docs: %w", err)
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, t.Extensions)
 }
diff --git a/openapi3/validation_options.go b/openapi3/validation_options.go
index 343b683..0ca12e5 100644
--- a/openapi3/validation_options.go
+++ b/openapi3/validation_options.go
@@ -12,10 +12,23 @@ type ValidationOptions struct {
 	schemaDefaultsValidationDisabled                 bool
 	schemaFormatValidationEnabled                    bool
 	schemaPatternValidationDisabled                  bool
+	extraSiblingFieldsAllowed                        map[string]struct{}
 }
 
 type validationOptionsKey struct{}
 
+// AllowExtraSiblingFields called as AllowExtraSiblingFields("description") makes Validate not return an error when said field appears next to a $ref.
+func AllowExtraSiblingFields(fields ...string) ValidationOption {
+	return func(options *ValidationOptions) {
+		for _, field := range fields {
+			if options.extraSiblingFieldsAllowed == nil {
+				options.extraSiblingFieldsAllowed = make(map[string]struct{}, len(fields))
+			}
+			options.extraSiblingFieldsAllowed[field] = struct{}{}
+		}
+	}
+}
+
 // EnableSchemaFormatValidation makes Validate not return an error when validating documents that mention schema formats that are not defined by the OpenAPIv3 specification.
 // By default, schema format validation is disabled.
 func EnableSchemaFormatValidation() ValidationOption {
diff --git a/openapi3/xml.go b/openapi3/xml.go
index a55ff41..34ed3be 100644
--- a/openapi3/xml.go
+++ b/openapi3/xml.go
@@ -2,14 +2,13 @@ package openapi3
 
 import (
 	"context"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
+	"encoding/json"
 )
 
 // XML is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#xml-object
 type XML struct {
-	ExtensionProps `json:"-" yaml:"-"`
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
 	Name      string `json:"name,omitempty" yaml:"name,omitempty"`
 	Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
@@ -19,18 +18,49 @@ type XML struct {
 }
 
 // MarshalJSON returns the JSON encoding of XML.
-func (xml *XML) MarshalJSON() ([]byte, error) {
-	return jsoninfo.MarshalStrictStruct(xml)
+func (xml XML) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, 5+len(xml.Extensions))
+	for k, v := range xml.Extensions {
+		m[k] = v
+	}
+	if x := xml.Name; x != "" {
+		m["name"] = x
+	}
+	if x := xml.Namespace; x != "" {
+		m["namespace"] = x
+	}
+	if x := xml.Prefix; x != "" {
+		m["prefix"] = x
+	}
+	if x := xml.Attribute; x {
+		m["attribute"] = x
+	}
+	if x := xml.Wrapped; x {
+		m["wrapped"] = x
+	}
+	return json.Marshal(m)
 }
 
 // UnmarshalJSON sets XML to a copy of data.
 func (xml *XML) UnmarshalJSON(data []byte) error {
-	return jsoninfo.UnmarshalStrictStruct(data, xml)
+	type XMLBis XML
+	var x XMLBis
+	if err := json.Unmarshal(data, &x); err != nil {
+		return err
+	}
+	_ = json.Unmarshal(data, &x.Extensions)
+	delete(x.Extensions, "name")
+	delete(x.Extensions, "namespace")
+	delete(x.Extensions, "prefix")
+	delete(x.Extensions, "attribute")
+	delete(x.Extensions, "wrapped")
+	*xml = XML(x)
+	return nil
 }
 
 // Validate returns an error if XML does not comply with the OpenAPI spec.
 func (xml *XML) Validate(ctx context.Context, opts ...ValidationOption) error {
-	// ctx = WithValidationOptions(ctx, opts...)
+	ctx = WithValidationOptions(ctx, opts...)
 
-	return nil // TODO
+	return validateExtensions(ctx, xml.Extensions)
 }
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 2cd700c..2e374d4 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -1,6 +1,8 @@
 package openapi3filter
 
 import (
+	"archive/zip"
+	"bytes"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -1004,15 +1006,16 @@ func decodeBody(body io.Reader, header http.Header, schema *openapi3.SchemaRef,
 }
 
 func init() {
-	RegisterBodyDecoder("text/plain", plainBodyDecoder)
 	RegisterBodyDecoder("application/json", jsonBodyDecoder)
 	RegisterBodyDecoder("application/json-patch+json", jsonBodyDecoder)
-	RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
-	RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
+	RegisterBodyDecoder("application/octet-stream", FileBodyDecoder)
 	RegisterBodyDecoder("application/problem+json", jsonBodyDecoder)
 	RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder)
+	RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
+	RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
+	RegisterBodyDecoder("application/zip", ZipFileBodyDecoder)
 	RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder)
-	RegisterBodyDecoder("application/octet-stream", FileBodyDecoder)
+	RegisterBodyDecoder("text/plain", plainBodyDecoder)
 }
 
 func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
@@ -1125,8 +1128,7 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 		if len(schema.Value.AllOf) > 0 {
 			var exists bool
 			for _, sr := range schema.Value.AllOf {
-				valueSchema, exists = sr.Value.Properties[name]
-				if exists {
+				if valueSchema, exists = sr.Value.Properties[name]; exists {
 					break
 				}
 			}
@@ -1137,10 +1139,8 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 			// If the property's schema has type "array" it is means that the form contains a few parts with the same name.
 			// Every such part has a type that is defined by an items schema in the property's schema.
 			var exists bool
-			valueSchema, exists = schema.Value.Properties[name]
-			if !exists {
-				anyProperties := schema.Value.AdditionalPropertiesAllowed
-				if anyProperties != nil {
+			if valueSchema, exists = schema.Value.Properties[name]; !exists {
+				if anyProperties := schema.Value.AdditionalProperties.Has; anyProperties != nil {
 					switch *anyProperties {
 					case true:
 						//additionalProperties: true
@@ -1150,11 +1150,10 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 						return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
 					}
 				}
-				if schema.Value.AdditionalProperties == nil {
+				if schema.Value.AdditionalProperties.Schema == nil {
 					return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
 				}
-				valueSchema, exists = schema.Value.AdditionalProperties.Value.Properties[name]
-				if !exists {
+				if valueSchema, exists = schema.Value.AdditionalProperties.Schema.Value.Properties[name]; !exists {
 					return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
 				}
 			}
@@ -1179,8 +1178,8 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 			for k, v := range sr.Value.Properties {
 				allTheProperties[k] = v
 			}
-			if sr.Value.AdditionalProperties != nil {
-				for k, v := range sr.Value.AdditionalProperties.Value.Properties {
+			if addProps := sr.Value.AdditionalProperties.Schema; addProps != nil {
+				for k, v := range addProps.Value.Properties {
 					allTheProperties[k] = v
 				}
 			}
@@ -1189,8 +1188,8 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 		for k, v := range schema.Value.Properties {
 			allTheProperties[k] = v
 		}
-		if schema.Value.AdditionalProperties != nil {
-			for k, v := range schema.Value.AdditionalProperties.Value.Properties {
+		if addProps := schema.Value.AdditionalProperties.Schema; addProps != nil {
+			for k, v := range addProps.Value.Properties {
 				allTheProperties[k] = v
 			}
 		}
@@ -1221,3 +1220,54 @@ func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
 	}
 	return string(data), nil
 }
+
+// ZipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
+func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
+	buff := bytes.NewBuffer([]byte{})
+	size, err := io.Copy(buff, body)
+	if err != nil {
+		return nil, err
+	}
+
+	zr, err := zip.NewReader(bytes.NewReader(buff.Bytes()), size)
+	if err != nil {
+		return nil, err
+	}
+
+	const bufferSize = 256
+	content := make([]byte, 0, bufferSize*len(zr.File))
+	buffer := make([]byte, bufferSize)
+
+	for _, f := range zr.File {
+		err := func() error {
+			rc, err := f.Open()
+			if err != nil {
+				return err
+			}
+			defer func() {
+				_ = rc.Close()
+			}()
+
+			for {
+				n, err := rc.Read(buffer)
+				if 0 < n {
+					content = append(content, buffer...)
+				}
+				if err == io.EOF {
+					break
+				}
+				if err != nil {
+					return err
+				}
+			}
+
+			return nil
+		}()
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return string(content), nil
+}
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index a61c57a..7245cbe 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -346,10 +346,6 @@ func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationI
 
 // validateSecurityRequirement validates a single OpenAPI 3 security requirement
 func validateSecurityRequirement(ctx context.Context, input *RequestValidationInput, securityRequirement openapi3.SecurityRequirement) error {
-	doc := input.Route.Spec
-	securitySchemes := doc.Components.SecuritySchemes
-
-	// Ensure deterministic order
 	names := make([]string, 0, len(securityRequirement))
 	for name := range securityRequirement {
 		names = append(names, name)
@@ -366,6 +362,11 @@ func validateSecurityRequirement(ctx context.Context, input *RequestValidationIn
 		return ErrAuthenticationServiceMissing
 	}
 
+	var securitySchemes openapi3.SecuritySchemes
+	if components := input.Route.Spec.Components; components != nil {
+		securitySchemes = components.SecuritySchemes
+	}
+
 	// For each scheme for the requirement
 	for _, name := range names {
 		var securityScheme *openapi3.SecurityScheme
diff --git a/jsoninfo/field_info.go b/openapi3gen/field_info.go
similarity index 78%
rename from jsoninfo/field_info.go
rename to openapi3gen/field_info.go
index 6b45f8c..13f5ba0 100644
--- a/jsoninfo/field_info.go
+++ b/openapi3gen/field_info.go
@@ -1,4 +1,4 @@
-package jsoninfo
+package openapi3gen
 
 import (
 	"reflect"
@@ -7,9 +7,8 @@ import (
 	"unicode/utf8"
 )
 
-// FieldInfo contains information about JSON serialization of a field.
-type FieldInfo struct {
-	MultipleFields     bool // Whether multiple Go fields share this JSON name
+// theFieldInfo contains information about JSON serialization of a field.
+type theFieldInfo struct {
 	HasJSONTag         bool
 	TypeIsMarshaller   bool
 	TypeIsUnmarshaller bool
@@ -20,7 +19,7 @@ type FieldInfo struct {
 	JSONName           string
 }
 
-func AppendFields(fields []FieldInfo, parentIndex []int, t reflect.Type) []FieldInfo {
+func appendFields(fields []theFieldInfo, parentIndex []int, t reflect.Type) []theFieldInfo {
 	if t.Kind() == reflect.Ptr {
 		t = t.Elem()
 	}
@@ -40,7 +39,7 @@ iteration:
 				continue
 			}
 			if jsonTag == "" {
-				fields = AppendFields(fields, index, f.Type)
+				fields = appendFields(fields, index, f.Type)
 				continue iteration
 			}
 		}
@@ -58,7 +57,7 @@ iteration:
 		}
 
 		// Declare a field
-		field := FieldInfo{
+		field := theFieldInfo{
 			Index:    index,
 			Type:     f.Type,
 			JSONName: f.Name,
@@ -67,13 +66,6 @@ iteration:
 		// Read "json" tag
 		jsonTag := f.Tag.Get("json")
 
-		// Read our custom "multijson" tag that
-		// allows multiple fields with the same name.
-		if v := f.Tag.Get("multijson"); v != "" {
-			field.MultipleFields = true
-			jsonTag = v
-		}
-
 		// Handle "-"
 		if jsonTag == "-" {
 			continue
@@ -108,7 +100,7 @@ iteration:
 	return fields
 }
 
-type sortableFieldInfos []FieldInfo
+type sortableFieldInfos []theFieldInfo
 
 func (list sortableFieldInfos) Len() int {
 	return len(list)
diff --git a/openapi3gen/openapi3gen.go b/openapi3gen/openapi3gen.go
index 4387727..eccabd8 100644
--- a/openapi3gen/openapi3gen.go
+++ b/openapi3gen/openapi3gen.go
@@ -9,7 +9,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/getkin/kin-openapi/jsoninfo"
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
@@ -119,7 +118,7 @@ func (g *Generator) NewSchemaRefForValue(value interface{}, schemas openapi3.Sch
 	return ref, nil
 }
 
-func (g *Generator) generateSchemaRefFor(parents []*jsoninfo.TypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
+func (g *Generator) generateSchemaRefFor(parents []*theTypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
 	if ref := g.Types[t]; ref != nil && g.opts.schemaCustomizer == nil {
 		g.SchemaRefs[ref]++
 		return ref, nil
@@ -139,7 +138,7 @@ func (g *Generator) generateSchemaRefFor(parents []*jsoninfo.TypeInfo, t reflect
 	return ref, nil
 }
 
-func getStructField(t reflect.Type, fieldInfo jsoninfo.FieldInfo) reflect.StructField {
+func getStructField(t reflect.Type, fieldInfo theFieldInfo) reflect.StructField {
 	var ff reflect.StructField
 	// fieldInfo.Index is an array of indexes starting from the root of the type
 	for i := 0; i < len(fieldInfo.Index); i++ {
@@ -152,8 +151,8 @@ func getStructField(t reflect.Type, fieldInfo jsoninfo.FieldInfo) reflect.Struct
 	return ff
 }
 
-func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
-	typeInfo := jsoninfo.GetTypeInfo(t)
+func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type, name string, tag reflect.StructTag) (*openapi3.SchemaRef, error) {
+	typeInfo := getTypeInfo(t)
 	for _, parent := range parents {
 		if parent == typeInfo {
 			return nil, &CycleError{}
@@ -161,7 +160,7 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
 	}
 
 	if cap(parents) == 0 {
-		parents = make([]*jsoninfo.TypeInfo, 0, 4)
+		parents = make([]*theTypeInfo, 0, 4)
 	}
 	parents = append(parents, typeInfo)
 
@@ -284,7 +283,7 @@ func (g *Generator) generateWithoutSaving(parents []*jsoninfo.TypeInfo, t reflec
 		}
 		if additionalProperties != nil {
 			g.SchemaRefs[additionalProperties]++
-			schema.AdditionalProperties = additionalProperties
+			schema.AdditionalProperties = openapi3.AdditionalProperties{Schema: additionalProperties}
 		}
 
 	case reflect.Struct:
@@ -374,7 +373,7 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
 		ref := g.generateCycleSchemaRef(t.Elem(), schema)
 		mapSchema := openapi3.NewSchema()
 		mapSchema.Type = "object"
-		mapSchema.AdditionalProperties = ref
+		mapSchema.AdditionalProperties = openapi3.AdditionalProperties{Schema: ref}
 		return openapi3.NewSchemaRef("", mapSchema)
 	default:
 		typeName = t.Name()
diff --git a/openapi3gen/type_info.go b/openapi3gen/type_info.go
new file mode 100644
index 0000000..062882b
--- /dev/null
+++ b/openapi3gen/type_info.go
@@ -0,0 +1,54 @@
+package openapi3gen
+
+import (
+	"reflect"
+	"sort"
+	"sync"
+)
+
+var (
+	typeInfos      = map[reflect.Type]*theTypeInfo{}
+	typeInfosMutex sync.RWMutex
+)
+
+// theTypeInfo contains information about JSON serialization of a type
+type theTypeInfo struct {
+	Type   reflect.Type
+	Fields []theFieldInfo
+}
+
+// getTypeInfo returns theTypeInfo for the given type.
+func getTypeInfo(t reflect.Type) *theTypeInfo {
+	for t.Kind() == reflect.Ptr {
+		t = t.Elem()
+	}
+	typeInfosMutex.RLock()
+	typeInfo, exists := typeInfos[t]
+	typeInfosMutex.RUnlock()
+	if exists {
+		return typeInfo
+	}
+	if t.Kind() != reflect.Struct {
+		typeInfo = &theTypeInfo{
+			Type: t,
+		}
+	} else {
+		// Allocate
+		typeInfo = &theTypeInfo{
+			Type:   t,
+			Fields: make([]theFieldInfo, 0, 16),
+		}
+
+		// Add fields
+		typeInfo.Fields = appendFields(nil, nil, t)
+
+		// Sort fields
+		sort.Sort(sortableFieldInfos(typeInfo.Fields))
+	}
+
+	// Publish
+	typeInfosMutex.Lock()
+	typeInfos[t] = typeInfo
+	typeInfosMutex.Unlock()
+	return typeInfo
+}
diff --git a/refs.sh b/refs.sh
new file mode 100755
index 0000000..bbcbe54
--- /dev/null
+++ b/refs.sh
@@ -0,0 +1,125 @@
+#!/bin/bash -eux
+set -o pipefail
+
+types=()
+types+=("Callback")
+types+=("Example")
+types+=("Header")
+types+=("Link")
+types+=("Parameter")
+types+=("RequestBody")
+types+=("Response")
+types+=("Schema")
+types+=("SecurityScheme")
+
+cat <<EOF
+package openapi3
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"sort"
+
+	"github.com/go-openapi/jsonpointer"
+	"github.com/perimeterx/marshmallow"
+)
+EOF
+
+for type in "${types[@]}"; do
+	cat <<EOF
+
+// ${type}Ref represents either a ${type} or a \$ref to a ${type}.
+// When serializing and both fields are set, Ref is preferred over Value.
+type ${type}Ref struct {
+	Ref   string
+	Value *${type}
+	extra []string
+}
+
+var _ jsonpointer.JSONPointable = (*${type}Ref)(nil)
+
+// MarshalYAML returns the YAML encoding of ${type}Ref.
+func (x ${type}Ref) MarshalYAML() (interface{}, error) {
+	if ref := x.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	}
+	return x.Value, nil
+}
+
+// MarshalJSON returns the JSON encoding of ${type}Ref.
+func (x ${type}Ref) MarshalJSON() ([]byte, error) {
+	if ref := x.Ref; ref != "" {
+		return json.Marshal(Ref{Ref: ref})
+	}
+EOF
+
+	case $type in
+		# Callback) echo '	return x.Value.MarshalJSON()' ;; TODO: when https://github.com/getkin/kin-openapi/issues/687
+		Example) echo '	return x.Value.MarshalJSON()' ;;
+		Header) echo '	return x.Value.MarshalJSON()' ;;
+		Link) echo '	return x.Value.MarshalJSON()' ;;
+		Parameter) echo '	return x.Value.MarshalJSON()' ;;
+		RequestBody) echo '	return x.Value.MarshalJSON()' ;;
+		Response) echo '	return x.Value.MarshalJSON()' ;;
+		Schema) echo '	return x.Value.MarshalJSON()' ;;
+		SecurityScheme) echo '	return x.Value.MarshalJSON()' ;;
+		*) echo '	return json.Marshal(x.Value)'
+	esac
+
+	cat <<EOF
+}
+
+// UnmarshalJSON sets ${type}Ref to a copy of data.
+func (x *${type}Ref) 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)
+			}
+		}
+		return nil
+	}
+	return json.Unmarshal(data, &x.Value)
+}
+
+// Validate returns an error if ${type}Ref does not comply with the OpenAPI spec.
+func (x *${type}Ref) Validate(ctx context.Context, opts ...ValidationOption) error {
+	ctx = WithValidationOptions(ctx, opts...)
+	if extra := x.extra; len(extra) != 0 {
+		sort.Strings(extra)
+
+		extras := make([]string, 0, len(extra))
+		allowed := getValidationOptions(ctx).extraSiblingFieldsAllowed
+		if allowed == nil {
+			allowed = make(map[string]struct{}, 0)
+		}
+		for _, ex := range extra {
+			if _, ok := allowed[ex]; !ok {
+				extras = append(extras, ex)
+			}
+		}
+		if len(extras) != 0 {
+			return fmt.Errorf("extra sibling fields: %+v", extras)
+		}
+	}
+	if v := x.Value; v != nil {
+		return v.Validate(ctx)
+	}
+	return foundUnresolvedRef(x.Ref)
+}
+
+// JSONLookup implements github.com/go-openapi/jsonpointer#JSONPointable
+func (x *${type}Ref) JSONLookup(token string) (interface{}, error) {
+	if token == "\$ref" {
+		return x.Ref, nil
+	}
+	ptr, _, err := jsonpointer.GetForToken(x.Value, token)
+	return ptr, err
+}
+EOF
+
+done


Test patch:
diff --git a/jsoninfo/marshal_test.go b/jsoninfo/marshal_test.go
deleted file mode 100644
index 1055154..0000000
--- a/jsoninfo/marshal_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-package jsoninfo_test
-
-import (
-	"encoding/json"
-	"testing"
-	"time"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
-	"github.com/getkin/kin-openapi/openapi3"
-)
-
-type Simple struct {
-	openapi3.ExtensionProps
-	Bool    bool      `json:"bool"`
-	Int     int       `json:"int"`
-	Int64   int64     `json:"int64"`
-	Float64 float64   `json:"float64"`
-	Time    time.Time `json:"time"`
-	String  string    `json:"string"`
-	Bytes   []byte    `json:"bytes"`
-}
-
-type SimpleOmitEmpty struct {
-	openapi3.ExtensionProps
-	Bool    bool      `json:"bool,omitempty"`
-	Int     int       `json:"int,omitempty"`
-	Int64   int64     `json:"int64,omitempty"`
-	Float64 float64   `json:"float64,omitempty"`
-	Time    time.Time `json:"time,omitempty"`
-	String  string    `json:"string,omitempty"`
-	Bytes   []byte    `json:"bytes,omitempty"`
-}
-
-type SimplePtrOmitEmpty struct {
-	openapi3.ExtensionProps
-	Bool    *bool      `json:"bool,omitempty"`
-	Int     *int       `json:"int,omitempty"`
-	Int64   *int64     `json:"int64,omitempty"`
-	Float64 *float64   `json:"float64,omitempty"`
-	Time    *time.Time `json:"time,omitempty"`
-	String  *string    `json:"string,omitempty"`
-	Bytes   *[]byte    `json:"bytes,omitempty"`
-}
-
-type OriginalNameType struct {
-	openapi3.ExtensionProps
-	Field string `json:",omitempty"`
-}
-
-type RootType struct {
-	openapi3.ExtensionProps
-	EmbeddedType0
-	EmbeddedType1
-}
-
-type EmbeddedType0 struct {
-	openapi3.ExtensionProps
-	Field0 string `json:"embedded0,omitempty"`
-}
-
-type EmbeddedType1 struct {
-	openapi3.ExtensionProps
-	Field1 string `json:"embedded1,omitempty"`
-}
-
-// Example describes expected outcome of:
-//
-//	1.Marshal JSON
-//	2.Unmarshal value
-//	3.Marshal value
-type Example struct {
-	NoMarshal   bool
-	NoUnmarshal bool
-	Value       jsoninfo.StrictStruct
-	JSON        interface{}
-}
-
-var Examples = []Example{
-	// Primitives
-	{
-		Value: &SimpleOmitEmpty{},
-		JSON: Object{
-			"time": time.Unix(0, 0),
-		},
-	},
-	{
-		Value: &SimpleOmitEmpty{},
-		JSON: Object{
-			"bool":    true,
-			"int":     42,
-			"int64":   42,
-			"float64": 3.14,
-			"string":  "abc",
-			"bytes":   []byte{1, 2, 3},
-			"time":    time.Unix(1, 0),
-		},
-	},
-
-	// Pointers
-	{
-		Value: &SimplePtrOmitEmpty{},
-		JSON:  Object{},
-	},
-	{
-		Value: &SimplePtrOmitEmpty{},
-		JSON: Object{
-			"bool":    true,
-			"int":     42,
-			"int64":   42,
-			"float64": 3.14,
-			"string":  "abc",
-			"bytes":   []byte{1, 2, 3},
-			"time":    time.Unix(1, 0),
-		},
-	},
-
-	// JSON tag "fieldName"
-	{
-		Value: &Simple{},
-		JSON: Object{
-			"bool":    false,
-			"int":     0,
-			"int64":   0,
-			"float64": 0,
-			"string":  "",
-			"bytes":   []byte{},
-			"time":    time.Unix(0, 0),
-		},
-	},
-
-	// JSON tag ",omitempty"
-	{
-		Value: &OriginalNameType{},
-		JSON: Object{
-			"Field": "abc",
-		},
-	},
-
-	// Embedding
-	{
-		Value: &RootType{},
-		JSON:  Object{},
-	},
-	{
-		Value: &RootType{},
-		JSON: Object{
-			"embedded0": "0",
-			"embedded1": "1",
-			"x-other":   "abc",
-		},
-	},
-}
-
-type Object map[string]interface{}
-
-func TestExtensions(t *testing.T) {
-	for _, example := range Examples {
-		// Define JSON that will be unmarshalled
-		expectedData, err := json.Marshal(example.JSON)
-		if err != nil {
-			panic(err)
-		}
-		expected := string(expectedData)
-
-		// Define value that will marshalled
-		x := example.Value
-
-		// Unmarshal
-		if !example.NoUnmarshal {
-			t.Logf("Unmarshalling %T", x)
-			if err := jsoninfo.UnmarshalStrictStruct(expectedData, x); err != nil {
-				t.Fatalf("Error unmarshalling %T: %v", x, err)
-			}
-			t.Logf("Marshalling %T", x)
-		}
-
-		// Marshal
-		if !example.NoMarshal {
-			data, err := jsoninfo.MarshalStrictStruct(x)
-			if err != nil {
-				t.Fatalf("Error marshalling: %v", err)
-			}
-			actually := string(data)
-
-			if actually != expected {
-				t.Fatalf("Error!\nExpected: %s\nActually: %s", expected, actually)
-			}
-		}
-	}
-}
diff --git a/jsoninfo/unmarshal_test.go b/jsoninfo/unmarshal_test.go
deleted file mode 100644
index dd25f04..0000000
--- a/jsoninfo/unmarshal_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package jsoninfo
-
-import (
-	"errors"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestNewObjectDecoder(t *testing.T) {
-	data := []byte(`
-	{
-		"field1": 1,
-		"field2": 2
-	}
-`)
-	t.Run("test new object decoder", func(t *testing.T) {
-		decoder, err := NewObjectDecoder(data)
-		require.NoError(t, err)
-		require.NotNil(t, decoder)
-		require.Equal(t, data, decoder.Data)
-		require.Equal(t, 2, len(decoder.DecodeExtensionMap()))
-	})
-}
-
-type mockStrictStruct struct {
-	EncodeWithFn func(encoder *ObjectEncoder, value interface{}) error
-	DecodeWithFn func(decoder *ObjectDecoder, value interface{}) error
-}
-
-func (m *mockStrictStruct) EncodeWith(encoder *ObjectEncoder, value interface{}) error {
-	return m.EncodeWithFn(encoder, value)
-}
-
-func (m *mockStrictStruct) DecodeWith(decoder *ObjectDecoder, value interface{}) error {
-	return m.DecodeWithFn(decoder, value)
-}
-
-func TestUnmarshalStrictStruct(t *testing.T) {
-	data := []byte(`
-			{
-				"field1": 1,
-				"field2": 2
-			}
-		`)
-
-	t.Run("test unmarshal with StrictStruct without err", func(t *testing.T) {
-		decodeWithFnCalled := 0
-		mockStruct := &mockStrictStruct{
-			EncodeWithFn: func(encoder *ObjectEncoder, value interface{}) error {
-				return nil
-			},
-			DecodeWithFn: func(decoder *ObjectDecoder, value interface{}) error {
-				decodeWithFnCalled++
-				return nil
-			},
-		}
-		err := UnmarshalStrictStruct(data, mockStruct)
-		require.NoError(t, err)
-		require.Equal(t, 1, decodeWithFnCalled)
-	})
-
-	t.Run("test unmarshal with StrictStruct with err", func(t *testing.T) {
-		decodeWithFnCalled := 0
-		mockStruct := &mockStrictStruct{
-			EncodeWithFn: func(encoder *ObjectEncoder, value interface{}) error {
-				return nil
-			},
-			DecodeWithFn: func(decoder *ObjectDecoder, value interface{}) error {
-				decodeWithFnCalled++
-				return errors.New("unable to decode the value")
-			},
-		}
-		err := UnmarshalStrictStruct(data, mockStruct)
-		require.Error(t, err)
-		require.Equal(t, 1, decodeWithFnCalled)
-	})
-}
-
-func TestDecodeStructFieldsAndExtensions(t *testing.T) {
-	data := []byte(`
-	{
-		"field1": "field1",
-		"field2": "field2"
-	}
-`)
-	decoder, err := NewObjectDecoder(data)
-	require.NoError(t, err)
-	require.NotNil(t, decoder)
-
-	t.Run("value is not pointer", func(t *testing.T) {
-		var value interface{}
-		require.Panics(t, func() {
-			_ = decoder.DecodeStructFieldsAndExtensions(value)
-		}, "value is not a pointer")
-	})
-
-	t.Run("value is nil", func(t *testing.T) {
-		var value *string = nil
-		require.Panics(t, func() {
-			_ = decoder.DecodeStructFieldsAndExtensions(value)
-		}, "value is nil")
-	})
-
-	t.Run("value is not struct", func(t *testing.T) {
-		var value = "simple string"
-		require.Panics(t, func() {
-			_ = decoder.DecodeStructFieldsAndExtensions(&value)
-		}, "value is not struct")
-	})
-
-	t.Run("successfully decoded with all fields", func(t *testing.T) {
-		d, err := NewObjectDecoder(data)
-		require.NoError(t, err)
-		require.NotNil(t, d)
-
-		var value = struct {
-			Field1 string `json:"field1"`
-			Field2 string `json:"field2"`
-		}{}
-		err = d.DecodeStructFieldsAndExtensions(&value)
-		require.NoError(t, err)
-		require.Equal(t, "field1", value.Field1)
-		require.Equal(t, "field2", value.Field2)
-		require.Equal(t, 0, len(d.DecodeExtensionMap()))
-	})
-
-	t.Run("successfully decoded with renaming field", func(t *testing.T) {
-		d, err := NewObjectDecoder(data)
-		require.NoError(t, err)
-		require.NotNil(t, d)
-
-		var value = struct {
-			Field1 string `json:"field1"`
-		}{}
-		err = d.DecodeStructFieldsAndExtensions(&value)
-		require.NoError(t, err)
-		require.Equal(t, "field1", value.Field1)
-		require.Equal(t, 1, len(d.DecodeExtensionMap()))
-	})
-
-	t.Run("un-successfully decoded due to data mismatch", func(t *testing.T) {
-		d, err := NewObjectDecoder(data)
-		require.NoError(t, err)
-		require.NotNil(t, d)
-
-		var value = struct {
-			Field1 int `json:"field1"`
-		}{}
-		err = d.DecodeStructFieldsAndExtensions(&value)
-		require.Error(t, err)
-		require.EqualError(t, err, `failed to unmarshal property "field1" (*int): json: cannot unmarshal string into Go value of type int`)
-		require.Equal(t, 0, value.Field1)
-		require.Equal(t, 2, len(d.DecodeExtensionMap()))
-	})
-}
diff --git a/openapi3/example_validation_test.go b/openapi3/example_validation_test.go
index 6ce7c0a..de89548 100644
--- a/openapi3/example_validation_test.go
+++ b/openapi3/example_validation_test.go
@@ -241,6 +241,7 @@ paths:
 					spec.WriteString(tc.parametersExample)
 					spec.WriteString(`
       requestBody:
+        required: true
         content:
           application/json:
             schema:
@@ -249,7 +250,6 @@ paths:
 					spec.WriteString(tc.mediaTypeRequestExample)
 					spec.WriteString(`
         description: Created user object
-        required: true
       responses:
         '204':
           description: "success"
@@ -262,11 +262,12 @@ paths:
   /readWriteOnly:
     post:
       requestBody:
+        required: true
         content:
           application/json:
             schema:
               $ref: "#/components/schemas/ReadWriteOnlyData"
-            required: true`)
+`)
 					spec.WriteString(tc.readWriteOnlyMediaTypeRequestExample)
 					spec.WriteString(`
       responses:
diff --git a/openapi3/extension_test.go b/openapi3/extension_test.go
deleted file mode 100644
index a995378..0000000
--- a/openapi3/extension_test.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package openapi3
-
-import (
-	"encoding/json"
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-
-	"github.com/getkin/kin-openapi/jsoninfo"
-)
-
-func ExampleExtensionProps_DecodeWith() {
-	loader := NewLoader()
-	loader.IsExternalRefsAllowed = true
-	spec, err := loader.LoadFromFile("testdata/testref.openapi.json")
-	if err != nil {
-		panic(err)
-	}
-
-	dec, err := jsoninfo.NewObjectDecoder(spec.Info.Extensions["x-my-extension"].(json.RawMessage))
-	if err != nil {
-		panic(err)
-	}
-	var value struct {
-		Key int `json:"k"`
-	}
-	if err = spec.Info.DecodeWith(dec, &value); err != nil {
-		panic(err)
-	}
-	fmt.Println(value.Key)
-	// Output: 42
-}
-
-func TestExtensionProps_EncodeWith(t *testing.T) {
-	t.Run("successfully encoded", func(t *testing.T) {
-		encoder := jsoninfo.NewObjectEncoder()
-		var extensionProps = ExtensionProps{
-			Extensions: map[string]interface{}{
-				"field1": "value1",
-			},
-		}
-
-		var value = struct {
-			Field1 string `json:"field1"`
-			Field2 string `json:"field2"`
-		}{}
-
-		err := extensionProps.EncodeWith(encoder, &value)
-		require.NoError(t, err)
-	})
-}
-
-func TestExtensionProps_DecodeWith(t *testing.T) {
-	data := []byte(`
-	{
-		"field1": "value1",
-		"field2": "value2"
-	}
-`)
-	t.Run("successfully decode all the fields", func(t *testing.T) {
-		decoder, err := jsoninfo.NewObjectDecoder(data)
-		require.NoError(t, err)
-		var extensionProps = &ExtensionProps{
-			Extensions: map[string]interface{}{
-				"field1": "value1",
-				"field2": "value1",
-			},
-		}
-
-		var value = struct {
-			Field1 string `json:"field1"`
-			Field2 string `json:"field2"`
-		}{}
-
-		err = extensionProps.DecodeWith(decoder, &value)
-		require.NoError(t, err)
-		require.Equal(t, 0, len(extensionProps.Extensions))
-		require.Equal(t, "value1", value.Field1)
-		require.Equal(t, "value2", value.Field2)
-	})
-
-	t.Run("successfully decode some of the fields", func(t *testing.T) {
-		decoder, err := jsoninfo.NewObjectDecoder(data)
-		require.NoError(t, err)
-		var extensionProps = &ExtensionProps{
-			Extensions: map[string]interface{}{
-				"field1": "value1",
-				"field2": "value2",
-			},
-		}
-
-		var value = &struct {
-			Field1 string `json:"field1"`
-		}{}
-
-		err = extensionProps.DecodeWith(decoder, value)
-		require.NoError(t, err)
-		require.Equal(t, 1, len(extensionProps.Extensions))
-		require.Equal(t, "value1", value.Field1)
-	})
-
-	t.Run("successfully decode none of the fields", func(t *testing.T) {
-		decoder, err := jsoninfo.NewObjectDecoder(data)
-		require.NoError(t, err)
-
-		var extensionProps = &ExtensionProps{
-			Extensions: map[string]interface{}{
-				"field1": "value1",
-				"field2": "value2",
-			},
-		}
-
-		var value = struct {
-			Field3 string `json:"field3"`
-			Field4 string `json:"field4"`
-		}{}
-
-		err = extensionProps.DecodeWith(decoder, &value)
-		require.NoError(t, err)
-		require.Equal(t, 2, len(extensionProps.Extensions))
-		require.Empty(t, value.Field3)
-		require.Empty(t, value.Field4)
-	})
-}
diff --git a/openapi3/issue341_test.go b/openapi3/issue341_test.go
index 93364d0..15ea9d4 100644
--- a/openapi3/issue341_test.go
+++ b/openapi3/issue341_test.go
@@ -20,7 +20,7 @@ func TestIssue341(t *testing.T) {
 
 	bs, err := doc.MarshalJSON()
 	require.NoError(t, err)
-	require.Equal(t, []byte(`{"components":{},"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"get":{"responses":{"200":{"$ref":"#/components/responses/testpath_200_response"}}}}}}`), bs)
+	require.JSONEq(t, `{"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"get":{"responses":{"200":{"$ref":"#/components/responses/testpath_200_response"}}}}}}`, string(bs))
 
 	require.Equal(t, "string", doc.Paths["/testpath"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
 }
diff --git a/openapi3/issue376_test.go b/openapi3/issue376_test.go
index 22aa7fb..825f1d1 100644
--- a/openapi3/issue376_test.go
+++ b/openapi3/issue376_test.go
@@ -1,6 +1,7 @@
 package openapi3
 
 import (
+	"context"
 	"fmt"
 	"testing"
 
@@ -42,15 +43,42 @@ info:
 	require.Equal(t, "string", doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
 }
 
+func TestExclusiveValuesOfValuesAdditionalProperties(t *testing.T) {
+	schema := &Schema{
+		AdditionalProperties: AdditionalProperties{
+			Has:    BoolPtr(false),
+			Schema: NewSchemaRef("", &Schema{}),
+		},
+	}
+	err := schema.Validate(context.Background())
+	require.ErrorContains(t, err, ` to both `)
+
+	schema = &Schema{
+		AdditionalProperties: AdditionalProperties{
+			Has: BoolPtr(false),
+		},
+	}
+	err = schema.Validate(context.Background())
+	require.NoError(t, err)
+
+	schema = &Schema{
+		AdditionalProperties: AdditionalProperties{
+			Schema: NewSchemaRef("", &Schema{}),
+		},
+	}
+	err = schema.Validate(context.Background())
+	require.NoError(t, err)
+}
+
 func TestMultijsonTagSerialization(t *testing.T) {
-	spec := []byte(`
+	specYAML := []byte(`
 openapi: 3.0.0
 components:
   schemas:
     unset:
       type: number
-    #empty-object:
-    # TODO additionalProperties: {}
+    empty-object:
+      additionalProperties: {}
     object:
       additionalProperties: {type: string}
     boolean:
@@ -61,42 +89,77 @@ info:
   version: 1.2.3.4
 `)
 
-	loader := NewLoader()
-
-	doc, err := loader.LoadFromData(spec)
-	require.NoError(t, err)
-
-	err = doc.Validate(loader.Context)
-	require.NoError(t, err)
-
-	for propName, propSchema := range doc.Components.Schemas {
-		ap := propSchema.Value.AdditionalProperties
-		apa := propSchema.Value.AdditionalPropertiesAllowed
-
-		encoded, err := propSchema.MarshalJSON()
-		require.NoError(t, err)
-		require.Equal(t, string(encoded), map[string]string{
-			"unset": `{"type":"number"}`,
-			// TODO: "empty-object":`{"additionalProperties":{}}`,
-			"object":  `{"additionalProperties":{"type":"string"}}`,
-			"boolean": `{"additionalProperties":false}`,
-		}[propName])
-
-		if propName == "unset" {
-			require.True(t, ap == nil && apa == nil)
-			continue
-		}
-
-		apStr := ""
-		if ap != nil {
-			apStr = fmt.Sprintf("{Ref:%s Value.Type:%v}", (*ap).Ref, (*ap).Value.Type)
-		}
-		apaStr := ""
-		if apa != nil {
-			apaStr = fmt.Sprintf("%v", *apa)
-		}
-
-		require.Truef(t, (ap != nil && apa == nil) || (ap == nil && apa != nil),
-			"%s: isnil(%s) xor isnil(%s)", propName, apaStr, apStr)
+	specJSON := []byte(`{
+  "openapi": "3.0.0",
+  "components": {
+    "schemas": {
+      "unset": {
+        "type": "number"
+      },
+      "empty-object": {
+        "additionalProperties": {
+        }
+      },
+      "object": {
+        "additionalProperties": {
+          "type": "string"
+        }
+      },
+      "boolean": {
+        "additionalProperties": false
+      }
+    }
+  },
+  "paths": {
+  },
+  "info": {
+    "title": "An API",
+    "version": "1.2.3.4"
+  }
+}`)
+
+	for i, spec := range [][]byte{specJSON, specYAML} {
+		t.Run(fmt.Sprintf("spec%02d", i), func(t *testing.T) {
+			loader := NewLoader()
+
+			doc, err := loader.LoadFromData(spec)
+			require.NoError(t, err)
+
+			err = doc.Validate(loader.Context)
+			require.NoError(t, err)
+
+			for propName, propSchema := range doc.Components.Schemas {
+				t.Run(propName, func(t *testing.T) {
+					ap := propSchema.Value.AdditionalProperties.Schema
+					apa := propSchema.Value.AdditionalProperties.Has
+
+					apStr := ""
+					if ap != nil {
+						apStr = fmt.Sprintf("{Ref:%s Value.Type:%v}", (*ap).Ref, (*ap).Value.Type)
+					}
+					apaStr := ""
+					if apa != nil {
+						apaStr = fmt.Sprintf("%v", *apa)
+					}
+
+					encoded, err := propSchema.MarshalJSON()
+					require.NoError(t, err)
+					require.Equal(t, map[string]string{
+						"unset":        `{"type":"number"}`,
+						"empty-object": `{"additionalProperties":{}}`,
+						"object":       `{"additionalProperties":{"type":"string"}}`,
+						"boolean":      `{"additionalProperties":false}`,
+					}[propName], string(encoded))
+
+					if propName == "unset" {
+						require.True(t, ap == nil && apa == nil)
+						return
+					}
+
+					require.Truef(t, (ap != nil && apa == nil) || (ap == nil && apa != nil),
+						"%s: isnil(%s) xor isnil(%s)", propName, apaStr, apStr)
+				})
+			}
+		})
 	}
 }
diff --git a/openapi3/issue513_test.go b/openapi3/issue513_test.go
new file mode 100644
index 0000000..332b922
--- /dev/null
+++ b/openapi3/issue513_test.go
@@ -0,0 +1,173 @@
+package openapi3
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue513OKWithExtension(t *testing.T) {
+	spec := `
+openapi: "3.0.3"
+info:
+  title: 'My app'
+  version: 1.0.0
+  description: 'An API'
+
+paths:
+  /v1/operation:
+    delete:
+      summary: Delete something
+      responses:
+        200:
+          description: Success
+        default:
+          description: '* **400** - Bad Request'
+          x-my-extension: {val: ue}
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+components:
+  schemas:
+    Error:
+      type: object
+      description: An error response body.
+      properties:
+        message:
+          description: A detailed message describing the error.
+          type: string
+`[1:]
+	sl := NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+	data, err := json.Marshal(doc)
+	require.NoError(t, err)
+	require.Contains(t, string(data), `x-my-extension`)
+}
+
+func TestIssue513KOHasExtraFieldSchema(t *testing.T) {
+	spec := `
+openapi: "3.0.3"
+info:
+  title: 'My app'
+  version: 1.0.0
+  description: 'An API'
+
+paths:
+  /v1/operation:
+    delete:
+      summary: Delete something
+      responses:
+        200:
+          description: Success
+        default:
+          description: '* **400** - Bad Request'
+          x-my-extension: {val: ue}
+          # Notice here schema is invalid. It should instead be:
+          # content:
+          #   application/json:
+          #     schema:
+          #       $ref: '#/components/schemas/Error'
+          schema:
+            $ref: '#/components/schemas/Error'
+components:
+  schemas:
+    Error:
+      type: object
+      description: An error response body.
+      properties:
+        message:
+          description: A detailed message describing the error.
+          type: string
+`[1:]
+	sl := NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	require.Contains(t, doc.Paths["/v1/operation"].Delete.Responses["default"].Value.Extensions, `x-my-extension`)
+	err = doc.Validate(sl.Context)
+	require.ErrorContains(t, err, `extra sibling fields: [schema]`)
+}
+
+func TestIssue513KOMixesRefAlongWithOtherFields(t *testing.T) {
+	spec := `
+openapi: "3.0.3"
+info:
+  title: 'My app'
+  version: 1.0.0
+  description: 'An API'
+
+paths:
+  /v1/operation:
+    delete:
+      summary: Delete something
+      responses:
+        200:
+          description: A sibling field that the spec says is ignored
+          $ref: '#/components/responses/SomeResponseBody'
+components:
+  responses:
+    SomeResponseBody:
+      description: Success
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/Error'
+  schemas:
+    Error:
+      type: object
+      description: An error response body.
+      properties:
+        message:
+          description: A detailed message describing the error.
+          type: string
+`[1:]
+	sl := NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	err = doc.Validate(sl.Context)
+	require.ErrorContains(t, err, `extra sibling fields: [description]`)
+}
+
+func TestIssue513KOMixesRefAlongWithOtherFieldsAllowed(t *testing.T) {
+	spec := `
+openapi: "3.0.3"
+info:
+  title: 'My app'
+  version: 1.0.0
+  description: 'An API'
+
+paths:
+  /v1/operation:
+    delete:
+      summary: Delete something
+      responses:
+        200:
+          description: A sibling field that the spec says is ignored
+          $ref: '#/components/responses/SomeResponseBody'
+components:
+  responses:
+    SomeResponseBody:
+      description: Success
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/Error'
+  schemas:
+    Error:
+      type: object
+      description: An error response body.
+      properties:
+        message:
+          description: A detailed message describing the error.
+          type: string
+`[1:]
+	sl := NewLoader()
+	doc, err := sl.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+	err = doc.Validate(sl.Context, AllowExtraSiblingFields("description"))
+	require.NoError(t, err)
+}
diff --git a/openapi3/load_cicular_ref_with_external_file_test.go b/openapi3/load_cicular_ref_with_external_file_test.go
index 978ef8f..9bcaaf7 100644
--- a/openapi3/load_cicular_ref_with_external_file_test.go
+++ b/openapi3/load_cicular_ref_with_external_file_test.go
@@ -53,8 +53,8 @@ func TestLoadCircularRefFromFile(t *testing.T) {
 			Title:   "Recursive cyclic refs example",
 			Version: "1.0",
 		},
-		Components: openapi3.Components{
-			Schemas: map[string]*openapi3.SchemaRef{
+		Components: &openapi3.Components{
+			Schemas: openapi3.Schemas{
 				"Foo": foo,
 				"Bar": bar,
 			},
diff --git a/openapi3/loader_paths_test.go b/openapi3/loader_paths_test.go
index 584f00e..f7edc73 100644
--- a/openapi3/loader_paths_test.go
+++ b/openapi3/loader_paths_test.go
@@ -13,7 +13,6 @@ openapi: "3.0"
 info:
   version: "1.0"
   title: sample
-basePath: /adc/v1
 paths:
   PATH:
     get:
diff --git a/openapi3/loader_test.go b/openapi3/loader_test.go
index e792767..3515586 100644
--- a/openapi3/loader_test.go
+++ b/openapi3/loader_test.go
@@ -78,7 +78,7 @@ func ExampleLoader() {
 }
 
 func TestResolveSchemaRef(t *testing.T) {
-	source := []byte(`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1",description":"An API"},"paths":{},"components":{"schemas":{"B":{"type":"string"},"A":{"allOf":[{"$ref":"#/components/schemas/B"}]}}}}`)
+	source := []byte(`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{},"components":{"schemas":{"B":{"type":"string"},"A":{"allOf":[{"$ref":"#/components/schemas/B"}]}}}}`)
 	loader := NewLoader()
 	doc, err := loader.LoadFromData(source)
 	require.NoError(t, err)
@@ -90,15 +90,6 @@ func TestResolveSchemaRef(t *testing.T) {
 	require.NotNil(t, refAVisited.Value)
 }
 
-func TestResolveSchemaRefWithNullSchemaRef(t *testing.T) {
-	source := []byte(`{"openapi":"3.0.0","info":{"title":"MyAPI","version":"0.1","description":"An API"},"paths":{"/foo":{"post":{"requestBody":{"content":{"application/json":{"schema":null}}}}}}}`)
-	loader := NewLoader()
-	doc, err := loader.LoadFromData(source)
-	require.NoError(t, err)
-	err = doc.Validate(loader.Context)
-	require.EqualError(t, err, `invalid paths: invalid path /foo: invalid operation POST: found unresolved ref: ""`)
-}
-
 func TestResolveResponseExampleRef(t *testing.T) {
 	source := []byte(`
 openapi: 3.0.1
diff --git a/openapi3/openapi3_test.go b/openapi3/openapi3_test.go
index 7736310..e01af82 100644
--- a/openapi3/openapi3_test.go
+++ b/openapi3/openapi3_test.go
@@ -299,28 +299,28 @@ func spec() *T {
 				},
 			},
 		},
-		Components: Components{
-			Parameters: map[string]*ParameterRef{
+		Components: &Components{
+			Parameters: ParametersMap{
 				"someParameter": {
 					Value: parameter,
 				},
 			},
-			RequestBodies: map[string]*RequestBodyRef{
+			RequestBodies: RequestBodies{
 				"someRequestBody": {
 					Value: requestBody,
 				},
 			},
-			Responses: map[string]*ResponseRef{
+			Responses: Responses{
 				"someResponse": {
 					Value: response,
 				},
 			},
-			Schemas: map[string]*SchemaRef{
+			Schemas: Schemas{
 				"someSchema": {
 					Value: schema,
 				},
 			},
-			Headers: map[string]*HeaderRef{
+			Headers: Headers{
 				"someHeader": {
 					Ref: "#/components/headers/otherHeader",
 				},
@@ -328,7 +328,7 @@ func spec() *T {
 					Value: &Header{Parameter{Schema: &SchemaRef{Value: NewStringSchema()}}},
 				},
 			},
-			Examples: map[string]*ExampleRef{
+			Examples: Examples{
 				"someExample": {
 					Ref: "#/components/examples/otherExample",
 				},
@@ -336,7 +336,7 @@ func spec() *T {
 					Value: NewExample(example),
 				},
 			},
-			SecuritySchemes: map[string]*SecuritySchemeRef{
+			SecuritySchemes: SecuritySchemes{
 				"someSecurityScheme": {
 					Ref: "#/components/securitySchemes/otherSecurityScheme",
 				},
diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go
index 89971cf..2bd9848 100644
--- a/openapi3/schema_test.go
+++ b/openapi3/schema_test.go
@@ -801,11 +801,11 @@ var schemaExamples = []schemaExample{
 	{
 		Schema: &Schema{
 			Type: "object",
-			AdditionalProperties: &SchemaRef{
+			AdditionalProperties: AdditionalProperties{Schema: &SchemaRef{
 				Value: &Schema{
 					Type: "number",
 				},
-			},
+			}},
 		},
 		Serialization: map[string]interface{}{
 			"type": "object",
@@ -828,8 +828,8 @@ var schemaExamples = []schemaExample{
 	},
 	{
 		Schema: &Schema{
-			Type:                        "object",
-			AdditionalPropertiesAllowed: BoolPtr(true),
+			Type:                 "object",
+			AdditionalProperties: AdditionalProperties{Has: BoolPtr(true)},
 		},
 		Serialization: map[string]interface{}{
 			"type":                 "object",
diff --git a/openapi3filter/issue707_test.go b/openapi3filter/issue707_test.go
index c0dbe64..a7cbc39 100644
--- a/openapi3filter/issue707_test.go
+++ b/openapi3filter/issue707_test.go
@@ -15,27 +15,26 @@ func TestIssue707(t *testing.T) {
 	loader := openapi3.NewLoader()
 	ctx := loader.Context
 	spec := `
-    openapi: 3.0.0
-    info:
-     version: 1.0.0
-     title: Sample API
-    paths:
-     /items:
-      get:
-       description: Returns a list of stuff
-       parameters:
-        - description: parameter with a default value
-          explode: true
-          in: query
-          name: param-with-default
-          schema:
-           default: 124
-           type: integer
-           style: form
-          required: false
-       responses:
+openapi: 3.0.0
+info:
+  version: 1.0.0
+  title: Sample API
+paths:
+  /items:
+    get:
+      description: Returns a list of stuff
+      parameters:
+      - description: parameter with a default value
+        explode: true
+        in: query
+        name: param-with-default
+        schema:
+          default: 124
+          type: integer
+        required: false
+      responses:
         '200':
-           description: Successful response
+          description: Successful response
 `[1:]
 
 	doc, err := loader.LoadFromData([]byte(spec))
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index cdbeb12..d3a1b45 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -541,7 +541,7 @@ func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *test
 				securitySchemes[1].Name: {},
 			},
 		},
-		Components: openapi3.Components{
+		Components: &openapi3.Components{
 			SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
 		},
 	}
@@ -670,7 +670,7 @@ func TestAnySecurityRequirementMet(t *testing.T) {
 			Version: "0.1",
 		},
 		Paths: map[string]*openapi3.PathItem{},
-		Components: openapi3.Components{
+		Components: &openapi3.Components{
 			SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
 		},
 	}
@@ -767,7 +767,7 @@ func TestAllSchemesMet(t *testing.T) {
 			Version: "0.1",
 		},
 		Paths: map[string]*openapi3.PathItem{},
-		Components: openapi3.Components{
+		Components: &openapi3.Components{
 			SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
 		},
 	}
diff --git a/openapi3filter/zip_file_upload_test.go b/openapi3filter/zip_file_upload_test.go
new file mode 100644
index 0000000..69c6419
--- /dev/null
+++ b/openapi3filter/zip_file_upload_test.go
@@ -0,0 +1,116 @@
+package openapi3filter_test
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"net/textproto"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/getkin/kin-openapi/openapi3filter"
+	"github.com/getkin/kin-openapi/routers/gorillamux"
+)
+
+func TestValidateZipFileUpload(t *testing.T) {
+	const spec = `
+openapi: 3.0.0
+info:
+  title: 'Validator'
+  version: 0.0.1
+paths:
+  /test:
+    post:
+      requestBody:
+        required: true
+        content:
+          multipart/form-data:
+            schema:
+              type: object
+              required:
+                - file
+              properties:
+                file:
+                  type: string
+                  format: binary
+      responses:
+        '200':
+          description: Created
+`
+
+	loader := openapi3.NewLoader()
+	doc, err := loader.LoadFromData([]byte(spec))
+	require.NoError(t, err)
+
+	err = doc.Validate(loader.Context)
+	require.NoError(t, err)
+
+	router, err := gorillamux.NewRouter(doc)
+	require.NoError(t, err)
+
+	tests := []struct {
+		zipData []byte
+		wantErr bool
+	}{
+		{
+			[]byte{
+				0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7d, 0x23, 0x56, 0xcd, 0xfd, 0x67, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x1c, 0x00, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x09, 0x00, 0x03, 0xac, 0xce, 0xb3, 0x63, 0xaf, 0xce, 0xb3, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xf7, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x0a, 0x50, 0x4b, 0x01, 0x02, 0x1e, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7d, 0x23, 0x56, 0xcd, 0xfd, 0x67, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa4, 0x81, 0x00, 0x00, 0x00, 0x00, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x05, 0x00, 0x03, 0xac, 0xce, 0xb3, 0x63, 0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xf7, 0x01, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00,
+			},
+			false,
+		},
+		{
+			[]byte{
+				0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			}, // No entry
+			true,
+		},
+	}
+	for _, tt := range tests {
+		body := &bytes.Buffer{}
+		writer := multipart.NewWriter(body)
+
+		{ // Add file data
+			h := make(textproto.MIMEHeader)
+			h.Set("Content-Disposition", `form-data; name="file"; filename="hello.zip"`)
+			h.Set("Content-Type", "application/zip")
+
+			fw, err := writer.CreatePart(h)
+			require.NoError(t, err)
+			_, err = io.Copy(fw, bytes.NewReader(tt.zipData))
+
+			require.NoError(t, err)
+		}
+
+		writer.Close()
+
+		req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewReader(body.Bytes()))
+		require.NoError(t, err)
+
+		req.Header.Set("Content-Type", writer.FormDataContentType())
+
+		route, pathParams, err := router.FindRoute(req)
+		require.NoError(t, err)
+
+		if err = openapi3filter.ValidateRequestBody(
+			context.Background(),
+			&openapi3filter.RequestValidationInput{
+				Request:    req,
+				PathParams: pathParams,
+				Route:      route,
+			},
+			route.Operation.RequestBody.Value,
+		); err != nil {
+			if !tt.wantErr {
+				t.Errorf("got %v", err)
+			}
+			continue
+		}
+		if tt.wantErr {
+			t.Errorf("want err")
+		}
+	}
+}
diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go
index bfa3120..9a143e4 100644
--- a/openapi3gen/openapi3gen_test.go
+++ b/openapi3gen/openapi3gen_test.go
@@ -379,7 +379,7 @@ func TestCyclicReferences(t *testing.T) {
 
 	require.NotNil(t, schemaRef.Value.Properties["MapCycle"])
 	require.Equal(t, "object", schemaRef.Value.Properties["MapCycle"].Value.Type)
-	require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["MapCycle"].Value.AdditionalProperties.Ref)
+	require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["MapCycle"].Value.AdditionalProperties.Schema.Ref)
 }
 
 func ExampleSchemaCustomizer() {
diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go
index 3e74400..5fb9be2 100644
--- a/routers/gorillamux/router_test.go
+++ b/routers/gorillamux/router_test.go
@@ -494,18 +494,16 @@ func newServerWithVariables(url string, variables map[string]string) *openapi3.S
 	}
 
 	return &openapi3.Server{
-		ExtensionProps: openapi3.ExtensionProps{},
-		URL:            url,
-		Description:    "",
-		Variables:      serverVariables,
+		URL:         url,
+		Description: "",
+		Variables:   serverVariables,
 	}
 }
 
 func newServerVariable(defaultValue string) *openapi3.ServerVariable {
 	return &openapi3.ServerVariable{
-		ExtensionProps: openapi3.ExtensionProps{},
-		Enum:           nil,
-		Default:        defaultValue,
-		Description:    "",
+		Enum:        nil,
+		Default:     defaultValue,
+		Description: "",
 	}
 }