Repository: getkin/kin-openapi
Pull request #763 (2023-02-06 12:47:53)

openapi3: add support for extensions on the few types left
Signed-off-by: Pierre Fenoll <pierrefenoll@gmail.com>


Patch:
diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt
index 02d22a1..3afa393 100644
--- a/.github/docs/openapi3.txt
+++ b/.github/docs/openapi3.txt
@@ -136,13 +136,46 @@ func (addProps AdditionalProperties) MarshalJSON() ([]byte, error)
 func (addProps *AdditionalProperties) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets AdditionalProperties to a copy of data.
 
-type Callback map[string]*PathItem
+type Callback struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	// Has unexported fields.
+}
     Callback is specified by OpenAPI/Swagger standard version 3. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
 
-func (callback Callback) Validate(ctx context.Context, opts ...ValidationOption) error
+func NewCallback(opts ...NewCallbackOption) *Callback
+    NewCallback builds a Callback object with path items in insertion order.
+
+func NewCallbackWithCapacity(cap int) *Callback
+    NewCallbackWithCapacity builds a Callback object of the given capacity.
+
+func (callback Callback) JSONLookup(token string) (interface{}, error)
+    JSONLookup implements
+    https://github.com/go-openapi/jsonpointer#JSONPointable
+
+func (callback *Callback) Len() int
+    Len returns the amount of keys in callback excluding callback.Extensions.
+
+func (callback *Callback) Map() map[string]*PathItem
+    Map returns callback as a 'map'. Note: iteration on Go maps is not ordered.
+
+func (callback Callback) MarshalJSON() ([]byte, error)
+    MarshalJSON returns the JSON encoding of Callback.
+
+func (callback *Callback) Set(key string, value *PathItem)
+    Set adds or replaces key 'key' of 'callback' with 'value'. Note: 'callback'
+    MUST be non-nil
+
+func (callback *Callback) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Callback to a copy of data.
+
+func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if Callback does not comply with the OpenAPI spec.
 
+func (callback *Callback) Value(key string) *PathItem
+    Value returns the callback for key or nil
+
 type CallbackRef struct {
 	Ref   string
 	Value *Callback
@@ -609,6 +642,27 @@ func (me MultiError) Is(target error) bool
     `errors.Is()` It will also return true if any of the contained errors match
     target
 
+type NewCallbackOption func(*Callback)
+    NewCallbackOption describes options to NewCallback func
+
+func WithCallback(cb string, pathItem *PathItem) NewCallbackOption
+    WithCallback adds Callback as an option to NewCallback
+
+type NewPathsOption func(*Paths)
+    NewPathsOption describes options to NewPaths func
+
+func WithPath(path string, pathItem *PathItem) NewPathsOption
+    WithPath adds a named path item
+
+type NewResponsesOption func(*Responses)
+    NewResponsesOption describes options to NewResponses func
+
+func WithName(name string, response *Response) NewResponsesOption
+    WithName adds a name-keyed Response
+
+func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption
+    WithStatus adds a status code keyed ResponseRef
+
 type OAuthFlow struct {
 	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
@@ -673,7 +727,7 @@ type Operation struct {
 	RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
 
 	// Responses.
-	Responses Responses `json:"responses" yaml:"responses"` // Required
+	Responses *Responses `json:"responses" yaml:"responses"` // Required
 
 	// Optional callbacks
 	Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
@@ -847,11 +901,21 @@ func (pathItem *PathItem) UnmarshalJSON(data []byte) error
 func (pathItem *PathItem) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if PathItem does not comply with the OpenAPI spec.
 
-type Paths map[string]*PathItem
+type Paths struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	// Has unexported fields.
+}
     Paths is specified by OpenAPI/Swagger standard version 3. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
 
-func (paths Paths) Find(key string) *PathItem
+func NewPaths(opts ...NewPathsOption) *Paths
+    NewPaths builds a paths object with path items in insertion order.
+
+func NewPathsWithCapacity(cap int) *Paths
+    NewPathsWithCapacity builds a paths object of the given capacity.
+
+func (paths *Paths) Find(key string) *PathItem
     Find returns a path that matches the key.
 
     The method ignores differences in template variable names (except possible
@@ -866,16 +930,39 @@ func (paths Paths) Find(key string) *PathItem
 
     would return the correct path item.
 
-func (paths Paths) InMatchingOrder() []string
+func (paths *Paths) InMatchingOrder() []string
     InMatchingOrder returns paths in the
     order they are matched against URLs. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
     When matching URLs, concrete (non-templated) paths would be matched before
     their templated counterparts.
 
-func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
+func (paths Paths) JSONLookup(token string) (interface{}, error)
+    JSONLookup implements
+    https://github.com/go-openapi/jsonpointer#JSONPointable
+
+func (paths *Paths) Len() int
+    Len returns the amount of keys in paths excluding paths.Extensions.
+
+func (paths *Paths) Map() map[string]*PathItem
+    Map returns paths as a 'map'. Note: iteration on Go maps is not ordered.
+
+func (paths Paths) MarshalJSON() ([]byte, error)
+    MarshalJSON returns the JSON encoding of Paths.
+
+func (paths *Paths) Set(key string, value *PathItem)
+    Set adds or replaces key 'key' of 'paths' with 'value'. Note: 'paths' MUST
+    be non-nil
+
+func (paths *Paths) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Paths to a copy of data.
+
+func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if Paths does not comply with the OpenAPI spec.
 
+func (paths *Paths) Value(key string) *PathItem
+    Value returns the paths for key or nil
+
 type ReadFromURIFunc func(loader *Loader, url *url.URL) ([]byte, error)
     ReadFromURIFunc defines a function which reads the contents of a resource
     located at a URI.
@@ -1039,28 +1126,58 @@ func (x *ResponseRef) Validate(ctx context.Context, opts ...ValidationOption) er
     Validate returns an error if ResponseRef does not comply with the OpenAPI
     spec.
 
-type Responses map[string]*ResponseRef
+type Responses struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	// Has unexported fields.
+}
     Responses is specified by OpenAPI/Swagger 3.0 standard. See
     https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
 
-func NewResponses() Responses
+func NewResponses(opts ...NewResponsesOption) *Responses
+    NewResponses builds a responses object with response objects in insertion
+    order. Given no arguments, NewResponses returns a valid responses object
+    containing a default match-all reponse.
 
-func (responses Responses) Default() *ResponseRef
+func NewResponsesWithCapacity(cap int) *Responses
+    NewResponsesWithCapacity builds a responses object of the given capacity.
 
-func (responses Responses) Get(status int) *ResponseRef
-    Get returns a ResponseRef for the given status If an exact
+func (responses *Responses) Default() *ResponseRef
+    Default returns the default response
+
+func (responses Responses) JSONLookup(token string) (interface{}, error)
+    JSONLookup implements
+    https://github.com/go-openapi/jsonpointer#JSONPointable
+
+func (responses *Responses) Len() int
+    Len returns the amount of keys in responses excluding responses.Extensions.
+
+func (responses *Responses) Map() map[string]*ResponseRef
+    Map returns responses as a 'map'. Note: iteration on Go maps is not ordered.
+
+func (responses Responses) MarshalJSON() ([]byte, error)
+    MarshalJSON returns the JSON encoding of Responses.
+
+func (responses *Responses) Set(key string, value *ResponseRef)
+    Set adds or replaces key 'key' of 'responses' with 'value'. Note:
+    'responses' MUST be non-nil
+
+func (responses *Responses) Status(status int) *ResponseRef
+    Status returns a ResponseRef for the given status If an exact
     match isn't initially found a patterned field is checked using
     the first digit to determine the range (eg: 201 to 2XX) See
     https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
 
-func (responses Responses) JSONLookup(token string) (interface{}, error)
-    JSONLookup implements
-    https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
+func (responses *Responses) UnmarshalJSON(data []byte) (err error)
+    UnmarshalJSON sets Responses to a copy of data.
 
-func (responses Responses) Validate(ctx context.Context, opts ...ValidationOption) error
+func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if Responses does not comply with the OpenAPI
     spec.
 
+func (responses *Responses) Value(key string) *ResponseRef
+    Value returns the responses for key or nil
+
 type Schema struct {
 	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
@@ -1519,7 +1636,7 @@ type T struct {
 	OpenAPI      string               `json:"openapi" yaml:"openapi"` // Required
 	Components   *Components          `json:"components,omitempty" yaml:"components,omitempty"`
 	Info         *Info                `json:"info" yaml:"info"`   // Required
-	Paths        Paths                `json:"paths" yaml:"paths"` // Required
+	Paths        *Paths               `json:"paths" yaml:"paths"` // Required
 	Security     SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
 	Servers      Servers              `json:"servers,omitempty" yaml:"servers,omitempty"`
 	Tags         Tags                 `json:"tags,omitempty" yaml:"tags,omitempty"`
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 2df5de0..70272cd 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -52,13 +52,13 @@ jobs:
 
     - uses: actions/checkout@v2
 
-    - name: Check codegen
-      run: |
-        ./refs.sh | tee openapi3/refs.go
-        git --no-pager diff --exit-code
+    - run: ./refs.sh | tee openapi3/refs.go
+    - run: git --no-pager diff --exit-code
+
+    - run: ./maps.sh
+    - run: git --no-pager diff --exit-code
 
-    - name: Check docsgen
-      run: ./docs.sh
+    - run: ./docs.sh
 
     - run: go mod download && go mod tidy && go mod verify
     - run: git --no-pager diff --exit-code
@@ -119,7 +119,7 @@ jobs:
     - if: runner.os == 'Linux'
       name: Ensure non-pointer MarshalJSON
       run: |
-        ! git grep -InE 'func.+[*].+[)].MarshalJSON[(][)]'
+        ! git grep -InE 'func[^{}]+[*][^{}]+[)].MarshalJSON[(][)]'
 
     - if: runner.os == 'Linux'
       name: Use `loader := NewLoader(); loader.Load ...`
diff --git a/README.md b/README.md
index 1e40514..95360bb 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ go run github.com/getkin/kin-openapi/cmd/validate@latest [--circular] [--default
 Use `openapi3.Loader`, which resolves all references:
 ```go
 loader := openapi3.NewLoader()
-doc, err := loader.LoadFromFile("swagger.json")
+doc, err := loader.LoadFromFile("my-openapi-spec.json")
 ```
 
 ## Getting OpenAPI operation that matches request
@@ -275,7 +275,15 @@ func safeErrorMessage(err *openapi3.SchemaError) string {
 
 This will change the schema validation errors to return only the `Reason` field, which is guaranteed to not include the original value.
 
-## CHANGELOG: Sub-v0 breaking API changes
+## CHANGELOG: Sub-v1 breaking API changes
+
+### v0.122.0
+* `Paths` field of `openapi3.T` is now a pointer
+* `Responses` field of `openapi3.Operation` is now a pointer
+* `openapi3.Paths` went from `map[string]*PathItem` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
+* `openapi3.Callback` went from `map[string]*PathItem` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
+* `openapi3.Responses` went from `map[string]*ResponseRef` to a struct with an `Extensions` field and methods: `Set`, `Value`, `Len`, `Map`, and `New*`.
+* `(openapi3.Responses).Get(int)` renamed to `(*openapi3.Responses).Status(int)`
 
 ### v0.121.0
 * Introduce `openapi3.RequestBodies` (an alias on `map[string]*openapi3.ResponseRef`) and use it in place of `openapi3.Responses` for field `openapi3.Components.Responses`.
diff --git a/maps.sh b/maps.sh
new file mode 100755
index 0000000..4f2d920
--- /dev/null
+++ b/maps.sh
@@ -0,0 +1,195 @@
+#!/bin/bash -eux
+set -o pipefail
+
+maplike=./openapi3/maplike.go
+maplike_test=./openapi3/maplike_test.go
+
+types=()
+types+=('*Responses')
+types+=('*Callback')
+types+=('*Paths')
+
+value_types=()
+value_types+=('*ResponseRef')
+value_types+=('*PathItem')
+value_types+=('*PathItem')
+
+deref_vs=()
+deref_vs+=('*Response = v.Value')
+deref_vs+=('*PathItem = v')
+deref_vs+=('*PathItem = v')
+
+names=()
+names+=('responses')
+names+=('callback')
+names+=('paths')
+
+[[ "${#types[@]}" = "${#value_types[@]}" ]]
+[[ "${#types[@]}" = "${#deref_vs[@]}" ]]
+[[ "${#types[@]}" = "${#names[@]}" ]]
+[[ "${#types[@]}" = "$(git grep -InF ' m map[string]*' -- openapi3/loader.go | wc -l)" ]]
+
+cat <<EOF >"$maplike"
+package openapi3
+
+import (
+	"encoding/json"
+	"sort"
+	"strings"
+
+	"github.com/go-openapi/jsonpointer"
+)
+EOF
+
+
+cat <<EOF >"$maplike_test"
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestMaplikeMethods(t *testing.T) {
+	t.Parallel()
+EOF
+
+for i in "${!types[@]}"; do
+	type=${types[$i]}
+	value_type=${value_types[$i]}
+	deref_v=${deref_vs[$i]}
+	name=${names[$i]}
+
+	cat <<EOF >>"$maplike"
+
+// Value returns the ${name} for key or nil
+func (${name} ${type}) Value(key string) ${value_type} {
+	if ${name}.Len() == 0 {
+		return nil
+	}
+	return ${name}.m[key]
+}
+
+// Set adds or replaces key 'key' of '${name}' with 'value'.
+// Note: '${name}' MUST be non-nil
+func (${name} ${type}) Set(key string, value ${value_type}) {
+	if ${name}.m == nil {
+		${name}.m = make(map[string]${value_type})
+	}
+	${name}.m[key] = value
+}
+
+// Len returns the amount of keys in ${name} excluding ${name}.Extensions.
+func (${name} ${type}) Len() int {
+	if ${name} == nil {
+		return 0
+	}
+	return len(${name}.m)
+}
+
+// Map returns ${name} as a 'map'.
+// Note: iteration on Go maps is not ordered.
+func (${name} ${type}) Map() map[string]${value_type} {
+	if ${name}.Len() == 0 {
+		return nil
+	}
+	return ${name}.m
+}
+
+var _ jsonpointer.JSONPointable = (${type})(nil)
+
+// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
+func (${name} ${type#'*'}) JSONLookup(token string) (interface{}, error) {
+	if v := ${name}.Value(token); v == nil {
+		vv, _, err := jsonpointer.GetForToken(${name}.Extensions, token)
+		return vv, err
+	} else if ref := v.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	} else {
+		var vv ${deref_v}
+		return vv, nil
+	}
+}
+
+// MarshalJSON returns the JSON encoding of ${type#'*'}.
+func (${name} ${type#'*'}) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, ${name}.Len()+len(${name}.Extensions))
+	for k, v := range ${name}.Extensions {
+		m[k] = v
+	}
+	for k, v := range ${name}.Map() {
+		m[k] = v
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets ${type#'*'} to a copy of data.
+func (${name} ${type}) UnmarshalJSON(data []byte) (err error) {
+	var m map[string]interface{}
+	if err = json.Unmarshal(data, &m); err != nil {
+		return
+	}
+
+	ks := make([]string, 0, len(m))
+	for k := range m {
+		ks = append(ks, k)
+	}
+	sort.Strings(ks)
+
+	x := ${type#'*'}{
+		Extensions: make(map[string]interface{}),
+		m:          make(map[string]${value_type}, len(m)),
+	}
+
+	for _, k := range ks {
+		v := m[k]
+		if strings.HasPrefix(k, "x-") {
+			x.Extensions[k] = v
+			continue
+		}
+
+		var data []byte
+		if data, err = json.Marshal(v); err != nil {
+			return
+		}
+		var vv ${value_type#'*'}
+		if err = vv.UnmarshalJSON(data); err != nil {
+			return
+		}
+		x.m[k] = &vv
+	}
+	*${name} = x
+	return
+}
+EOF
+
+	cat <<EOF >>"$maplike_test"
+
+	t.Run("${type}", func(t *testing.T) {
+		t.Parallel()
+		t.Run("nil", func(t *testing.T) {
+			x := (${type})(nil)
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]${value_type})(nil), x.Map())
+			require.Equal(t, (${value_type})(nil), x.Value("key"))
+			require.Panics(t, func() { x.Set("key", &${value_type#'*'}{}) })
+		})
+		t.Run("nonnil", func(t *testing.T) {
+			x := &${type#'*'}{}
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]${value_type})(nil), x.Map())
+			require.Equal(t, (${value_type})(nil), x.Value("key"))
+			x.Set("key", &${value_type#'*'}{})
+			require.Equal(t, 1, x.Len())
+			require.Equal(t, map[string]${value_type}{"key": {}}, x.Map())
+			require.Equal(t, &${value_type#'*'}{}, x.Value("key"))
+		})
+	})
+EOF
+
+done
+
+	cat <<EOF >>"$maplike_test"
+}
+EOF
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index ebd272a..2404e90 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -71,19 +71,18 @@ func ToV3WithLoader(doc2 *openapi2.T, loader *openapi3.Loader, location *url.URL
 	}
 
 	if paths := doc2.Paths; len(paths) != 0 {
-		doc3Paths := make(map[string]*openapi3.PathItem, len(paths))
+		doc3.Paths = openapi3.NewPathsWithCapacity(len(paths))
 		for path, pathItem := range paths {
 			r, err := ToV3PathItem(doc2, doc3.Components, pathItem, doc2.Consumes)
 			if err != nil {
 				return nil, err
 			}
-			doc3Paths[path] = r
+			doc3.Paths.Set(path, r)
 		}
-		doc3.Paths = doc3Paths
 	}
 
 	if responses := doc2.Responses; len(responses) != 0 {
-		doc3.Components.Responses = make(map[string]*openapi3.ResponseRef, len(responses))
+		doc3.Components.Responses = make(openapi3.ResponseBodies, len(responses))
 		for k, response := range responses {
 			r, err := ToV3Response(response, doc2.Produces)
 			if err != nil {
@@ -189,15 +188,14 @@ func ToV3Operation(doc2 *openapi2.T, components *openapi3.Components, pathItem *
 	}
 
 	if responses := operation.Responses; responses != nil {
-		doc3Responses := make(openapi3.Responses, len(responses))
+		doc3.Responses = openapi3.NewResponsesWithCapacity(len(responses))
 		for k, response := range responses {
-			doc3, err := ToV3Response(response, operation.Produces)
+			responseRef3, err := ToV3Response(response, operation.Produces)
 			if err != nil {
 				return nil, err
 			}
-			doc3Responses[k] = doc3
+			doc3.Responses.Set(k, responseRef3)
 		}
-		doc3.Responses = doc3Responses
 	}
 	return doc3, nil
 }
@@ -614,13 +612,15 @@ func FromV3(doc3 *openapi3.T) (*openapi2.T, error) {
 			}
 		}
 	}
+
 	if isHTTPS {
 		doc2.Schemes = append(doc2.Schemes, "https")
 	}
 	if isHTTP {
 		doc2.Schemes = append(doc2.Schemes, "http")
 	}
-	for path, pathItem := range doc3.Paths {
+
+	for path, pathItem := range doc3.Paths.Map() {
 		if pathItem == nil {
 			continue
 		}
@@ -983,7 +983,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.Map(), doc3.Components)
 		if err != nil {
 			return nil, err
 		}
diff --git a/openapi3/callback.go b/openapi3/callback.go
index bf5dc83..10e084e 100644
--- a/openapi3/callback.go
+++ b/openapi3/callback.go
@@ -7,22 +7,53 @@ import (
 
 // Callback is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#callback-object
-type Callback map[string]*PathItem
+type Callback struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	m map[string]*PathItem
+}
+
+// NewCallbackWithCapacity builds a Callback object of the given capacity.
+func NewCallbackWithCapacity(cap int) *Callback {
+	return &Callback{m: make(map[string]*PathItem, cap)}
+}
+
+// NewCallback builds a Callback object with path items in insertion order.
+func NewCallback(opts ...NewCallbackOption) *Callback {
+	Callback := NewCallbackWithCapacity(len(opts))
+	for _, opt := range opts {
+		opt(Callback)
+	}
+	return Callback
+}
+
+// NewCallbackOption describes options to NewCallback func
+type NewCallbackOption func(*Callback)
+
+// WithCallback adds Callback as an option to NewCallback
+func WithCallback(cb string, pathItem *PathItem) NewCallbackOption {
+	return func(callback *Callback) {
+		if p := pathItem; p != nil && cb != "" {
+			callback.Set(cb, p)
+		}
+	}
+}
 
 // Validate returns an error if Callback does not comply with the OpenAPI spec.
-func (callback Callback) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (callback *Callback) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
 
-	keys := make([]string, 0, len(callback))
-	for key := range callback {
+	keys := make([]string, 0, callback.Len())
+	for key := range callback.Map() {
 		keys = append(keys, key)
 	}
 	sort.Strings(keys)
 	for _, key := range keys {
-		v := callback[key]
+		v := callback.Value(key)
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, callback.Extensions)
 }
diff --git a/openapi3/internalize_refs.go b/openapi3/internalize_refs.go
index 98c0e7e..b54a406 100644
--- a/openapi3/internalize_refs.go
+++ b/openapi3/internalize_refs.go
@@ -313,14 +313,22 @@ func (doc *T) derefLinks(ls Links, refNameResolver RefNameResolver, parentIsExte
 	}
 }
 
-func (doc *T) derefResponses(es map[string]*ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) {
+func (doc *T) derefResponse(r *ResponseRef, refNameResolver RefNameResolver, parentIsExternal bool) {
+	isExternal := doc.addResponseToSpec(r, refNameResolver, parentIsExternal)
+	if v := r.Value; v != nil {
+		doc.derefHeaders(v.Headers, refNameResolver, isExternal || parentIsExternal)
+		doc.derefContent(v.Content, refNameResolver, isExternal || parentIsExternal)
+		doc.derefLinks(v.Links, refNameResolver, isExternal || parentIsExternal)
+	}
+}
+
+func (doc *T) derefResponses(rs *Responses, refNameResolver RefNameResolver, parentIsExternal bool) {
+	doc.derefResponseBodies(rs.Map(), refNameResolver, parentIsExternal)
+}
+
+func (doc *T) derefResponseBodies(es ResponseBodies, refNameResolver RefNameResolver, parentIsExternal bool) {
 	for _, e := range es {
-		isExternal := doc.addResponseToSpec(e, refNameResolver, parentIsExternal)
-		if e.Value != nil {
-			doc.derefHeaders(e.Value.Headers, refNameResolver, isExternal || parentIsExternal)
-			doc.derefContent(e.Value.Content, refNameResolver, isExternal || parentIsExternal)
-			doc.derefLinks(e.Value.Links, refNameResolver, isExternal || parentIsExternal)
-		}
+		doc.derefResponse(e, refNameResolver, parentIsExternal)
 	}
 }
 
@@ -354,7 +362,8 @@ func (doc *T) derefPaths(paths map[string]*PathItem, refNameResolver RefNameReso
 			for _, cb := range op.Callbacks {
 				isExternal := doc.addCallbackToSpec(cb, refNameResolver, pathIsExternal)
 				if cb.Value != nil {
-					doc.derefPaths(*cb.Value, refNameResolver, pathIsExternal || isExternal)
+					cbValue := (*cb.Value).Map()
+					doc.derefPaths(cbValue, refNameResolver, pathIsExternal || isExternal)
 				}
 			}
 			doc.derefResponses(op.Responses, refNameResolver, pathIsExternal)
@@ -413,7 +422,7 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri
 				doc.derefRequestBody(*req.Value, refNameResolver, isExternal)
 			}
 		}
-		doc.derefResponses(components.Responses, refNameResolver, false)
+		doc.derefResponseBodies(components.Responses, refNameResolver, false)
 		for _, ss := range components.SecuritySchemes {
 			doc.addSecuritySchemeToSpec(ss, refNameResolver, false)
 		}
@@ -423,10 +432,11 @@ func (doc *T) InternalizeRefs(ctx context.Context, refNameResolver func(ref stri
 			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)
+				cbValue := (*cb.Value).Map()
+				doc.derefPaths(cbValue, refNameResolver, isExternal)
 			}
 		}
 	}
 
-	doc.derefPaths(doc.Paths, refNameResolver, false)
+	doc.derefPaths(doc.Paths.Map(), refNameResolver, false)
 }
diff --git a/openapi3/loader.go b/openapi3/loader.go
index dd5a105..1128aaa 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -238,7 +238,7 @@ func (loader *Loader) ResolveRefsIn(doc *T, location *url.URL) (err error) {
 	}
 
 	// Visit all operations
-	for _, pathItem := range doc.Paths {
+	for _, pathItem := range doc.Paths.Map() {
 		if pathItem == nil {
 			continue
 		}
@@ -311,21 +311,32 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv
 			pathPart = unescapeRefString(pathPart)
 			attempted := false
 
+			switch c := cursor.(type) {
 			// Special case of T
 			// See issue856: a ref to doc => we assume that doc is a T => things live in T.Extensions
-			if t, ok := cursor.(*T); ok && pathPart == "" {
-				cursor = t.Extensions
-				attempted = true
-			}
+			case *T:
+				if pathPart == "" {
+					cursor = c.Extensions
+					attempted = true
+				}
 
 			// Special case due to multijson
-			if s, ok := cursor.(*SchemaRef); ok && pathPart == "additionalProperties" {
-				if ap := s.Value.AdditionalProperties.Has; ap != nil {
-					cursor = *ap
-				} else {
-					cursor = s.Value.AdditionalProperties.Schema
+			case *SchemaRef:
+				if pathPart == "additionalProperties" {
+					if ap := c.Value.AdditionalProperties.Has; ap != nil {
+						cursor = *ap
+					} else {
+						cursor = c.Value.AdditionalProperties.Schema
+					}
+					attempted = true
 				}
-				attempted = true
+
+			case *Responses:
+				cursor = c.m // m map[string]*ResponseRef
+			case *Callback:
+				cursor = c.m // m map[string]*PathItem
+			case *Paths:
+				cursor = c.m // m map[string]*PathItem
 			}
 
 			if !attempted {
@@ -417,6 +428,7 @@ func readableType(x interface{}) string {
 
 func drillIntoField(cursor interface{}, fieldName string) (interface{}, error) {
 	switch val := reflect.Indirect(reflect.ValueOf(cursor)); val.Kind() {
+
 	case reflect.Map:
 		elementValue := val.MapIndex(reflect.ValueOf(fieldName))
 		if !elementValue.IsValid() {
@@ -972,7 +984,7 @@ func (loader *Loader) resolveCallbackRef(doc *T, component *CallbackRef, documen
 		return nil
 	}
 
-	for _, pathItem := range *value {
+	for _, pathItem := range value.Map() {
 		if err = loader.resolvePathItemRef(doc, pathItem, documentPath); err != nil {
 			return err
 		}
@@ -1065,7 +1077,7 @@ func (loader *Loader) resolvePathItemRef(doc *T, pathItem *PathItem, documentPat
 				return
 			}
 		}
-		for _, response := range operation.Responses {
+		for _, response := range operation.Responses.Map() {
 			if err = loader.resolveResponseRef(doc, response, documentPath); err != nil {
 				return
 			}
diff --git a/openapi3/maplike.go b/openapi3/maplike.go
new file mode 100644
index 0000000..a35272d
--- /dev/null
+++ b/openapi3/maplike.go
@@ -0,0 +1,309 @@
+package openapi3
+
+import (
+	"encoding/json"
+	"sort"
+	"strings"
+
+	"github.com/go-openapi/jsonpointer"
+)
+
+// Value returns the responses for key or nil
+func (responses *Responses) Value(key string) *ResponseRef {
+	if responses.Len() == 0 {
+		return nil
+	}
+	return responses.m[key]
+}
+
+// Set adds or replaces key 'key' of 'responses' with 'value'.
+// Note: 'responses' MUST be non-nil
+func (responses *Responses) Set(key string, value *ResponseRef) {
+	if responses.m == nil {
+		responses.m = make(map[string]*ResponseRef)
+	}
+	responses.m[key] = value
+}
+
+// Len returns the amount of keys in responses excluding responses.Extensions.
+func (responses *Responses) Len() int {
+	if responses == nil {
+		return 0
+	}
+	return len(responses.m)
+}
+
+// Map returns responses as a 'map'.
+// Note: iteration on Go maps is not ordered.
+func (responses *Responses) Map() map[string]*ResponseRef {
+	if responses.Len() == 0 {
+		return nil
+	}
+	return responses.m
+}
+
+var _ jsonpointer.JSONPointable = (*Responses)(nil)
+
+// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
+func (responses Responses) JSONLookup(token string) (interface{}, error) {
+	if v := responses.Value(token); v == nil {
+		vv, _, err := jsonpointer.GetForToken(responses.Extensions, token)
+		return vv, err
+	} else if ref := v.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	} else {
+		var vv *Response = v.Value
+		return vv, nil
+	}
+}
+
+// MarshalJSON returns the JSON encoding of Responses.
+func (responses Responses) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, responses.Len()+len(responses.Extensions))
+	for k, v := range responses.Extensions {
+		m[k] = v
+	}
+	for k, v := range responses.Map() {
+		m[k] = v
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Responses to a copy of data.
+func (responses *Responses) UnmarshalJSON(data []byte) (err error) {
+	var m map[string]interface{}
+	if err = json.Unmarshal(data, &m); err != nil {
+		return
+	}
+
+	ks := make([]string, 0, len(m))
+	for k := range m {
+		ks = append(ks, k)
+	}
+	sort.Strings(ks)
+
+	x := Responses{
+		Extensions: make(map[string]interface{}),
+		m:          make(map[string]*ResponseRef, len(m)),
+	}
+
+	for _, k := range ks {
+		v := m[k]
+		if strings.HasPrefix(k, "x-") {
+			x.Extensions[k] = v
+			continue
+		}
+
+		var data []byte
+		if data, err = json.Marshal(v); err != nil {
+			return
+		}
+		var vv ResponseRef
+		if err = vv.UnmarshalJSON(data); err != nil {
+			return
+		}
+		x.m[k] = &vv
+	}
+	*responses = x
+	return
+}
+
+// Value returns the callback for key or nil
+func (callback *Callback) Value(key string) *PathItem {
+	if callback.Len() == 0 {
+		return nil
+	}
+	return callback.m[key]
+}
+
+// Set adds or replaces key 'key' of 'callback' with 'value'.
+// Note: 'callback' MUST be non-nil
+func (callback *Callback) Set(key string, value *PathItem) {
+	if callback.m == nil {
+		callback.m = make(map[string]*PathItem)
+	}
+	callback.m[key] = value
+}
+
+// Len returns the amount of keys in callback excluding callback.Extensions.
+func (callback *Callback) Len() int {
+	if callback == nil {
+		return 0
+	}
+	return len(callback.m)
+}
+
+// Map returns callback as a 'map'.
+// Note: iteration on Go maps is not ordered.
+func (callback *Callback) Map() map[string]*PathItem {
+	if callback.Len() == 0 {
+		return nil
+	}
+	return callback.m
+}
+
+var _ jsonpointer.JSONPointable = (*Callback)(nil)
+
+// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
+func (callback Callback) JSONLookup(token string) (interface{}, error) {
+	if v := callback.Value(token); v == nil {
+		vv, _, err := jsonpointer.GetForToken(callback.Extensions, token)
+		return vv, err
+	} else if ref := v.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	} else {
+		var vv *PathItem = v
+		return vv, nil
+	}
+}
+
+// MarshalJSON returns the JSON encoding of Callback.
+func (callback Callback) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, callback.Len()+len(callback.Extensions))
+	for k, v := range callback.Extensions {
+		m[k] = v
+	}
+	for k, v := range callback.Map() {
+		m[k] = v
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Callback to a copy of data.
+func (callback *Callback) UnmarshalJSON(data []byte) (err error) {
+	var m map[string]interface{}
+	if err = json.Unmarshal(data, &m); err != nil {
+		return
+	}
+
+	ks := make([]string, 0, len(m))
+	for k := range m {
+		ks = append(ks, k)
+	}
+	sort.Strings(ks)
+
+	x := Callback{
+		Extensions: make(map[string]interface{}),
+		m:          make(map[string]*PathItem, len(m)),
+	}
+
+	for _, k := range ks {
+		v := m[k]
+		if strings.HasPrefix(k, "x-") {
+			x.Extensions[k] = v
+			continue
+		}
+
+		var data []byte
+		if data, err = json.Marshal(v); err != nil {
+			return
+		}
+		var vv PathItem
+		if err = vv.UnmarshalJSON(data); err != nil {
+			return
+		}
+		x.m[k] = &vv
+	}
+	*callback = x
+	return
+}
+
+// Value returns the paths for key or nil
+func (paths *Paths) Value(key string) *PathItem {
+	if paths.Len() == 0 {
+		return nil
+	}
+	return paths.m[key]
+}
+
+// Set adds or replaces key 'key' of 'paths' with 'value'.
+// Note: 'paths' MUST be non-nil
+func (paths *Paths) Set(key string, value *PathItem) {
+	if paths.m == nil {
+		paths.m = make(map[string]*PathItem)
+	}
+	paths.m[key] = value
+}
+
+// Len returns the amount of keys in paths excluding paths.Extensions.
+func (paths *Paths) Len() int {
+	if paths == nil {
+		return 0
+	}
+	return len(paths.m)
+}
+
+// Map returns paths as a 'map'.
+// Note: iteration on Go maps is not ordered.
+func (paths *Paths) Map() map[string]*PathItem {
+	if paths.Len() == 0 {
+		return nil
+	}
+	return paths.m
+}
+
+var _ jsonpointer.JSONPointable = (*Paths)(nil)
+
+// JSONLookup implements https://github.com/go-openapi/jsonpointer#JSONPointable
+func (paths Paths) JSONLookup(token string) (interface{}, error) {
+	if v := paths.Value(token); v == nil {
+		vv, _, err := jsonpointer.GetForToken(paths.Extensions, token)
+		return vv, err
+	} else if ref := v.Ref; ref != "" {
+		return &Ref{Ref: ref}, nil
+	} else {
+		var vv *PathItem = v
+		return vv, nil
+	}
+}
+
+// MarshalJSON returns the JSON encoding of Paths.
+func (paths Paths) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{}, paths.Len()+len(paths.Extensions))
+	for k, v := range paths.Extensions {
+		m[k] = v
+	}
+	for k, v := range paths.Map() {
+		m[k] = v
+	}
+	return json.Marshal(m)
+}
+
+// UnmarshalJSON sets Paths to a copy of data.
+func (paths *Paths) UnmarshalJSON(data []byte) (err error) {
+	var m map[string]interface{}
+	if err = json.Unmarshal(data, &m); err != nil {
+		return
+	}
+
+	ks := make([]string, 0, len(m))
+	for k := range m {
+		ks = append(ks, k)
+	}
+	sort.Strings(ks)
+
+	x := Paths{
+		Extensions: make(map[string]interface{}),
+		m:          make(map[string]*PathItem, len(m)),
+	}
+
+	for _, k := range ks {
+		v := m[k]
+		if strings.HasPrefix(k, "x-") {
+			x.Extensions[k] = v
+			continue
+		}
+
+		var data []byte
+		if data, err = json.Marshal(v); err != nil {
+			return
+		}
+		var vv PathItem
+		if err = vv.UnmarshalJSON(data); err != nil {
+			return
+		}
+		x.m[k] = &vv
+	}
+	*paths = x
+	return
+}
diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go
index 865837e..88d66d4 100644
--- a/openapi3/openapi3.go
+++ b/openapi3/openapi3.go
@@ -17,7 +17,7 @@ type T struct {
 	OpenAPI      string               `json:"openapi" yaml:"openapi"` // Required
 	Components   *Components          `json:"components,omitempty" yaml:"components,omitempty"`
 	Info         *Info                `json:"info" yaml:"info"`   // Required
-	Paths        Paths                `json:"paths" yaml:"paths"` // Required
+	Paths        *Paths               `json:"paths" yaml:"paths"` // Required
 	Security     SecurityRequirements `json:"security,omitempty" yaml:"security,omitempty"`
 	Servers      Servers              `json:"servers,omitempty" yaml:"servers,omitempty"`
 	Tags         Tags                 `json:"tags,omitempty" yaml:"tags,omitempty"`
@@ -104,13 +104,13 @@ func (doc *T) UnmarshalJSON(data []byte) error {
 }
 
 func (doc *T) AddOperation(path string, method string, operation *Operation) {
-	if doc.Paths == nil {
-		doc.Paths = make(Paths)
-	}
-	pathItem := doc.Paths[path]
+	pathItem := doc.Paths.Value(path)
 	if pathItem == nil {
 		pathItem = &PathItem{}
-		doc.Paths[path] = pathItem
+		if doc.Paths == nil {
+			doc.Paths = NewPaths()
+		}
+		doc.Paths.Set(path, pathItem)
 	}
 	pathItem.SetOperation(method, operation)
 }
diff --git a/openapi3/operation.go b/openapi3/operation.go
index 02df2d2..d859a43 100644
--- a/openapi3/operation.go
+++ b/openapi3/operation.go
@@ -34,7 +34,7 @@ type Operation struct {
 	RequestBody *RequestBodyRef `json:"requestBody,omitempty" yaml:"requestBody,omitempty"`
 
 	// Responses.
-	Responses Responses `json:"responses" yaml:"responses"` // Required
+	Responses *Responses `json:"responses" yaml:"responses"` // Required
 
 	// Optional callbacks
 	Callbacks Callbacks `json:"callbacks,omitempty" yaml:"callbacks,omitempty"`
@@ -169,18 +169,14 @@ func (operation *Operation) AddParameter(p *Parameter) {
 }
 
 func (operation *Operation) AddResponse(status int, response *Response) {
-	responses := operation.Responses
-	if responses == nil {
-		responses = NewResponses()
-		operation.Responses = responses
-	}
 	code := "default"
-	if status != 0 {
+	if 0 < status && status < 1000 {
 		code = strconv.FormatInt(int64(status), 10)
 	}
-	responses[code] = &ResponseRef{
-		Value: response,
+	if operation.Responses == nil {
+		operation.Responses = NewResponses()
 	}
+	operation.Responses.Set(code, &ResponseRef{Value: response})
 }
 
 // Validate returns an error if Operation does not comply with the OpenAPI spec.
diff --git a/openapi3/paths.go b/openapi3/paths.go
index 0986b05..2c252a5 100644
--- a/openapi3/paths.go
+++ b/openapi3/paths.go
@@ -9,28 +9,58 @@ import (
 
 // Paths is specified by OpenAPI/Swagger standard version 3.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
-type Paths map[string]*PathItem
+type Paths struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
+
+	m map[string]*PathItem
+}
+
+// NewPathsWithCapacity builds a paths object of the given capacity.
+func NewPathsWithCapacity(cap int) *Paths {
+	return &Paths{m: make(map[string]*PathItem, cap)}
+}
+
+// NewPaths builds a paths object with path items in insertion order.
+func NewPaths(opts ...NewPathsOption) *Paths {
+	paths := NewPathsWithCapacity(len(opts))
+	for _, opt := range opts {
+		opt(paths)
+	}
+	return paths
+}
+
+// NewPathsOption describes options to NewPaths func
+type NewPathsOption func(*Paths)
+
+// WithPath adds a named path item
+func WithPath(path string, pathItem *PathItem) NewPathsOption {
+	return func(paths *Paths) {
+		if p := pathItem; p != nil && path != "" {
+			paths.Set(path, p)
+		}
+	}
+}
 
 // Validate returns an error if Paths does not comply with the OpenAPI spec.
-func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (paths *Paths) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
 
-	normalizedPaths := make(map[string]string, len(paths))
+	normalizedPaths := make(map[string]string, paths.Len())
 
-	keys := make([]string, 0, len(paths))
-	for key := range paths {
+	keys := make([]string, 0, paths.Len())
+	for key := range paths.Map() {
 		keys = append(keys, key)
 	}
 	sort.Strings(keys)
 	for _, path := range keys {
-		pathItem := paths[path]
+		pathItem := paths.Value(path)
 		if path == "" || path[0] != '/' {
 			return fmt.Errorf("path %q does not start with a forward slash (/)", path)
 		}
 
 		if pathItem == nil {
 			pathItem = &PathItem{}
-			paths[path] = pathItem
+			paths.Set(path, pathItem)
 		}
 
 		normalizedPath, _, varsInPath := normalizeTemplatedPath(path)
@@ -106,23 +136,23 @@ func (paths Paths) Validate(ctx context.Context, opts ...ValidationOption) error
 		return err
 	}
 
-	return nil
+	return validateExtensions(ctx, paths.Extensions)
 }
 
 // InMatchingOrder returns paths in the order they are matched against URLs.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#paths-object
 // When matching URLs, concrete (non-templated) paths would be matched
 // before their templated counterparts.
-func (paths Paths) InMatchingOrder() []string {
+func (paths *Paths) InMatchingOrder() []string {
 	// NOTE: sorting by number of variables ASC then by descending lexicographical
 	// order seems to be a good heuristic.
-	if paths == nil {
+	if paths.Len() == 0 {
 		return nil
 	}
 
 	vars := make(map[int][]string)
 	max := 0
-	for path := range paths {
+	for path := range paths.Map() {
 		count := strings.Count(path, "}")
 		vars[count] = append(vars[count], path)
 		if count > max {
@@ -130,7 +160,7 @@ func (paths Paths) InMatchingOrder() []string {
 		}
 	}
 
-	ordered := make([]string, 0, len(paths))
+	ordered := make([]string, 0, paths.Len())
 	for c := 0; c <= max; c++ {
 		if ps, ok := vars[c]; ok {
 			sort.Sort(sort.Reverse(sort.StringSlice(ps)))
@@ -152,15 +182,15 @@ func (paths Paths) InMatchingOrder() []string {
 //	pathItem := path.Find("/person/{name}")
 //
 // would return the correct path item.
-func (paths Paths) Find(key string) *PathItem {
+func (paths *Paths) Find(key string) *PathItem {
 	// Try directly access the map
-	pathItem := paths[key]
+	pathItem := paths.Value(key)
 	if pathItem != nil {
 		return pathItem
 	}
 
 	normalizedPath, expected, _ := normalizeTemplatedPath(key)
-	for path, pathItem := range paths {
+	for path, pathItem := range paths.Map() {
 		pathNormalized, got, _ := normalizeTemplatedPath(path)
 		if got == expected && pathNormalized == normalizedPath {
 			return pathItem
@@ -169,9 +199,9 @@ func (paths Paths) Find(key string) *PathItem {
 	return nil
 }
 
-func (paths Paths) validateUniqueOperationIDs() error {
+func (paths *Paths) validateUniqueOperationIDs() error {
 	operationIDs := make(map[string]string)
-	for urlPath, pathItem := range paths {
+	for urlPath, pathItem := range paths.Map() {
 		if pathItem == nil {
 			continue
 		}
diff --git a/openapi3/response.go b/openapi3/response.go
index 24b3a55..6cc26a5 100644
--- a/openapi3/response.go
+++ b/openapi3/response.go
@@ -4,88 +4,107 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
-	"fmt"
 	"sort"
 	"strconv"
-
-	"github.com/go-openapi/jsonpointer"
 )
 
 // Responses is specified by OpenAPI/Swagger 3.0 standard.
 // See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#responses-object
-type Responses map[string]*ResponseRef
+type Responses struct {
+	Extensions map[string]interface{} `json:"-" yaml:"-"`
 
-var _ jsonpointer.JSONPointable = (*Responses)(nil)
+	m map[string]*ResponseRef
+}
 
-// JSONLookup implements https://pkg.go.dev/github.com/go-openapi/jsonpointer#JSONPointable
-func (responses Responses) JSONLookup(token string) (interface{}, error) {
-	ref, ok := responses[token]
-	if !ok {
-		return nil, fmt.Errorf("invalid token reference: %q", token)
-	}
+// NewResponsesWithCapacity builds a responses object of the given capacity.
+func NewResponsesWithCapacity(cap int) *Responses {
+	return &Responses{m: make(map[string]*ResponseRef, cap)}
+}
+
+// NewResponses builds a responses object with response objects in insertion order.
+// Given no arguments, NewResponses returns a valid responses object containing a default match-all reponse.
+func NewResponses(opts ...NewResponsesOption) *Responses {
+	var responses *Responses
+	if n := len(opts); n != 0 {
+		responses = NewResponsesWithCapacity(n)
+	} else {
+		responses = &Responses{m: map[string]*ResponseRef{
+			"default": {Value: NewResponse().WithDescription("")},
+		}}
+	}
+	for _, opt := range opts {
+		opt(responses)
+	}
+	return responses
+}
+
+// NewResponsesOption describes options to NewResponses func
+type NewResponsesOption func(*Responses)
 
-	if ref != nil && ref.Ref != "" {
-		return &Ref{Ref: ref.Ref}, nil
+// WithStatus adds a status code keyed ResponseRef
+func WithStatus(status int, responseRef *ResponseRef) NewResponsesOption {
+	return func(responses *Responses) {
+		if r := responseRef; r != nil {
+			code := strconv.FormatInt(int64(status), 10)
+			responses.Set(code, r)
+		}
 	}
-	return ref.Value, nil
 }
 
-func NewResponses() Responses {
-	r := make(Responses)
-	r["default"] = &ResponseRef{Value: NewResponse().WithDescription("")}
-	return r
+// WithName adds a name-keyed Response
+func WithName(name string, response *Response) NewResponsesOption {
+	return func(responses *Responses) {
+		if r := response; r != nil && name != "" {
+			responses.Set(name, &ResponseRef{Value: r})
+		}
+	}
 }
 
-func (responses Responses) Default() *ResponseRef {
-	return responses["default"]
+// Default returns the default response
+func (responses *Responses) Default() *ResponseRef {
+	return responses.Value("default")
 }
 
-// Get returns a ResponseRef for the given status
+// Status returns a ResponseRef for the given status
 // If an exact match isn't initially found a patterned field is checked using
 // the first digit to determine the range (eg: 201 to 2XX)
 // See https://spec.openapis.org/oas/v3.0.3#patterned-fields-0
-func (responses Responses) Get(status int) *ResponseRef {
+func (responses *Responses) Status(status int) *ResponseRef {
 	st := strconv.FormatInt(int64(status), 10)
-	if rref, ok := responses[st]; ok {
+	if rref := responses.Value(st); rref != nil {
 		return rref
 	}
-	st = string(st[0]) + "XX"
-	switch st {
-	case "1XX":
-		return responses["1XX"]
-	case "2XX":
-		return responses["2XX"]
-	case "3XX":
-		return responses["3XX"]
-	case "4XX":
-		return responses["4XX"]
-	case "5XX":
-		return responses["5XX"]
-	default:
-		return nil
+	if 99 < status && status < 600 {
+		st = string(st[0]) + "XX"
+		switch st {
+		case "1XX", "2XX", "3XX", "4XX", "5XX":
+			return responses.Value(st)
+		}
 	}
+	return nil
 }
 
 // Validate returns an error if Responses does not comply with the OpenAPI spec.
-func (responses Responses) Validate(ctx context.Context, opts ...ValidationOption) error {
+func (responses *Responses) Validate(ctx context.Context, opts ...ValidationOption) error {
 	ctx = WithValidationOptions(ctx, opts...)
 
-	if len(responses) == 0 {
+	if responses.Len() == 0 {
 		return errors.New("the responses object MUST contain at least one response code")
 	}
 
-	keys := make([]string, 0, len(responses))
-	for key := range responses {
+	keys := make([]string, 0, responses.Len())
+	for key := range responses.Map() {
 		keys = append(keys, key)
 	}
 	sort.Strings(keys)
 	for _, key := range keys {
-		v := responses[key]
+		v := responses.Value(key)
 		if err := v.Validate(ctx); err != nil {
 			return err
 		}
 	}
-	return nil
+
+	return validateExtensions(ctx, responses.Extensions)
 }
 
 // Response is specified by OpenAPI/Swagger 3.0 standard.
diff --git a/openapi3filter/validate_response.go b/openapi3filter/validate_response.go
index 67bff3c..87a8a11 100644
--- a/openapi3filter/validate_response.go
+++ b/openapi3filter/validate_response.go
@@ -44,10 +44,10 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 
 	// Find input for the current status
 	responses := route.Operation.Responses
-	if len(responses) == 0 {
+	if responses.Len() == 0 {
 		return nil
 	}
-	responseRef := responses.Get(status) // Response
+	responseRef := responses.Status(status) // Response
 	if responseRef == nil {
 		responseRef = responses.Default() // Default input
 	}
@@ -104,7 +104,7 @@ func ValidateResponse(ctx context.Context, input *ResponseValidationInput) error
 	if contentType == nil {
 		return &ResponseError{
 			Input:  input,
-			Reason: fmt.Sprintf("response header Content-Type has unexpected value: %q", inputMIME),
+			Reason: fmt.Sprintf("response %s: %q", prefixInvalidCT, inputMIME),
 		}
 	}
 
diff --git a/routers/gorillamux/router.go b/routers/gorillamux/router.go
index bbf81ce..2d09242 100644
--- a/routers/gorillamux/router.go
+++ b/routers/gorillamux/router.go
@@ -57,7 +57,7 @@ func NewRouter(doc *openapi3.T) (routers.Router, error) {
 	muxRouter := mux.NewRouter().UseEncodedPath()
 	r := &Router{}
 	for _, path := range doc.Paths.InMatchingOrder() {
-		pathItem := doc.Paths[path]
+		pathItem := doc.Paths.Value(path)
 		if len(pathItem.Servers) > 0 {
 			if servers, err = makeServers(pathItem.Servers); err != nil {
 				return nil, err
@@ -113,7 +113,7 @@ func (r *Router) FindRoute(req *http.Request) (*routers.Route, map[string]string
 			}
 			route := *r.routes[i]
 			route.Method = req.Method
-			route.Operation = route.Spec.Paths[route.Path].GetOperation(route.Method)
+			route.Operation = route.Spec.Paths.Value(route.Path).GetOperation(route.Method)
 			return &route, vars, nil
 		}
 		switch match.MatchErr {
diff --git a/routers/legacy/router.go b/routers/legacy/router.go
index cc4ccc2..306449d 100644
--- a/routers/legacy/router.go
+++ b/routers/legacy/router.go
@@ -64,7 +64,7 @@ func NewRouter(doc *openapi3.T, opts ...openapi3.ValidationOption) (routers.Rout
 	}
 	router := &Router{doc: doc}
 	root := router.node()
-	for path, pathItem := range doc.Paths {
+	for path, pathItem := range doc.Paths.Map() {
 		for method, operation := range pathItem.Operations() {
 			method = strings.ToUpper(method)
 			if err := root.Add(method+" "+path, &routers.Route{
@@ -143,7 +143,7 @@ func (router *Router) FindRoute(req *http.Request) (*routers.Route, map[string]s
 		route, _ = node.Value.(*routers.Route)
 	}
 	if route == nil {
-		pathItem := doc.Paths[remainingPath]
+		pathItem := doc.Paths.Value(remainingPath)
 		if pathItem == nil {
 			return nil, nil, &routers.RouteError{Reason: routers.ErrPathNotFound.Error()}
 		}


Test patch:
diff --git a/openapi2conv/issue558_test.go b/openapi2conv/issue558_test.go
index 78661bf..2067301 100644
--- a/openapi2conv/issue558_test.go
+++ b/openapi2conv/issue558_test.go
@@ -27,7 +27,7 @@ paths:
 `
 	doc3, err := v2v3YAML([]byte(spec))
 	require.NoError(t, err)
-	require.NotEmpty(t, doc3.Paths["/test"].Get.Deprecated)
+	require.NotEmpty(t, doc3.Paths.Value("/test").Get.Deprecated)
 	_, err = yaml.Marshal(doc3)
 	require.NoError(t, err)
 
diff --git a/openapi2conv/issue573_test.go b/openapi2conv/issue573_test.go
index cefac40..0f9a35f 100644
--- a/openapi2conv/issue573_test.go
+++ b/openapi2conv/issue573_test.go
@@ -36,13 +36,13 @@ func TestIssue573(t *testing.T) {
 
 	// Make sure the response content appears for each mime-type originally
 	// appeared in "produces".
-	pingGetContent := v3.Paths["/ping"].Get.Responses["200"].Value.Content
+	pingGetContent := v3.Paths.Value("/ping").Get.Responses.Value("200").Value.Content
 	require.Len(t, pingGetContent, 2)
 	require.Contains(t, pingGetContent, "application/toml")
 	require.Contains(t, pingGetContent, "application/xml")
 
 	// Is "produces" is not explicitly specified, default to "application/json".
-	pingPostContent := v3.Paths["/ping"].Post.Responses["200"].Value.Content
+	pingPostContent := v3.Paths.Value("/ping").Post.Responses.Value("200").Value.Content
 	require.Len(t, pingPostContent, 1)
 	require.Contains(t, pingPostContent, "application/json")
 }
diff --git a/openapi2conv/issue847_test.go b/openapi2conv/issue847_test.go
index b4ea35d..79dbb21 100644
--- a/openapi2conv/issue847_test.go
+++ b/openapi2conv/issue847_test.go
@@ -35,9 +35,7 @@ paths:
 	err = v3.Validate(context.Background())
 	require.NoError(t, err)
 
-	schemaRequired := v3.Paths["/ping"].Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Required
-	require.Equal(t, schemaRequired, []string{"file"})
+	require.Equal(t, []string{"file"}, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Required)
 
-	fieldRequired := v3.Paths["/ping"].Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties["file"].Value.Required
-	require.Nil(t, fieldRequired)
+	require.Nil(t, v3.Paths.Value("/ping").Post.RequestBody.Value.Content["multipart/form-data"].Schema.Value.Properties["file"].Value.Required)
 }
diff --git a/openapi3/issue301_test.go b/openapi3/issue301_test.go
index a0225fd..ea14c3a 100644
--- a/openapi3/issue301_test.go
+++ b/openapi3/issue301_test.go
@@ -16,13 +16,19 @@ func TestIssue301(t *testing.T) {
 	err = doc.Validate(sl.Context)
 	require.NoError(t, err)
 
-	transCallbacks := doc.Paths["/trans"].Post.Callbacks["transactionCallback"].Value
-	require.Equal(t, "object", (*transCallbacks)["http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}"].Post.RequestBody.
-		Value.Content["application/json"].Schema.
-		Value.Type)
+	require.Equal(t, "object", doc.
+		Paths.Value("/trans").
+		Post.Callbacks["transactionCallback"].Value.
+		Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}").
+		Post.RequestBody.Value.
+		Content["application/json"].Schema.Value.
+		Type)
 
-	otherCallbacks := doc.Paths["/other"].Post.Callbacks["myEvent"].Value
-	require.Equal(t, "boolean", (*otherCallbacks)["{$request.query.queryUrl}"].Post.RequestBody.
-		Value.Content["application/json"].Schema.
-		Value.Type)
+	require.Equal(t, "boolean", doc.
+		Paths.Value("/other").
+		Post.Callbacks["myEvent"].Value.
+		Value("{$request.query.queryUrl}").
+		Post.RequestBody.Value.
+		Content["application/json"].Schema.Value.
+		Type)
 }
diff --git a/openapi3/issue341_test.go b/openapi3/issue341_test.go
index ba9bed7..2ceb459 100644
--- a/openapi3/issue341_test.go
+++ b/openapi3/issue341_test.go
@@ -21,9 +21,26 @@ func TestIssue341(t *testing.T) {
 
 	bs, err := doc.MarshalJSON()
 	require.NoError(t, err)
-	require.JSONEq(t, `{"info":{"title":"test file","version":"n/a"},"openapi":"3.0.0","paths":{"/testpath":{"$ref":"testpath.yaml#/paths/~1testpath"}}}`, string(bs))
+	require.JSONEq(t, `{
+	"info": {
+		"title": "test file",
+		"version": "n/a"
+	},
+	"openapi": "3.0.0",
+	"paths": {
+		"/testpath": {
+			"$ref": "testpath.yaml#/paths/~1testpath"
+		}
+	}
+}`, string(bs))
 
-	require.Equal(t, "string", doc.Paths["/testpath"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
+	require.Equal(t, "string", doc.
+		Paths.Value("/testpath").
+		Get.
+		Responses.Value("200").Value.
+		Content["application/json"].
+		Schema.Value.
+		Type)
 
 	doc.InternalizeRefs(context.Background(), nil)
 	bs, err = doc.MarshalJSON()
diff --git a/openapi3/issue376_test.go b/openapi3/issue376_test.go
index 825f1d1..fd92860 100644
--- a/openapi3/issue376_test.go
+++ b/openapi3/issue376_test.go
@@ -38,7 +38,7 @@ info:
 
 	require.Equal(t, "An API", doc.Info.Title)
 	require.Equal(t, 2, len(doc.Components.Schemas))
-	require.Equal(t, 0, len(doc.Paths))
+	require.Equal(t, 0, doc.Paths.Len())
 
 	require.Equal(t, "string", doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
 }
diff --git a/openapi3/issue513_test.go b/openapi3/issue513_test.go
index 442eac8..38454f6 100644
--- a/openapi3/issue513_test.go
+++ b/openapi3/issue513_test.go
@@ -152,7 +152,7 @@ components:
 	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`)
+	require.Contains(t, doc.Paths.Value("/v1/operation").Delete.Responses.Default().Value.Extensions, `x-my-extension`)
 	err = doc.Validate(sl.Context)
 	require.ErrorContains(t, err, `extra sibling fields: [schema]`)
 }
diff --git a/openapi3/issue594_test.go b/openapi3/issue594_test.go
index b36b73b..42bc7e7 100644
--- a/openapi3/issue594_test.go
+++ b/openapi3/issue594_test.go
@@ -23,7 +23,7 @@ func TestIssue594(t *testing.T) {
 	require.NoError(t, err)
 
 	doc.Info.Version = "1.2.3"
-	doc.Paths["/marketing/contacts/search/emails"].Post = nil
+	doc.Paths.Value("/marketing/contacts/search/emails").Post = nil
 	doc.Components.Schemas["full-segment"].Value.Example = nil
 
 	err = doc.Validate(sl.Context)
diff --git a/openapi3/issue753_test.go b/openapi3/issue753_test.go
index 4390641..46c18f7 100644
--- a/openapi3/issue753_test.go
+++ b/openapi3/issue753_test.go
@@ -15,6 +15,18 @@ func TestIssue753(t *testing.T) {
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 
-	require.NotNil(t, (*doc.Paths["/test1"].Post.Callbacks["callback1"].Value)["{$request.body#/callback}"].Post.RequestBody.Value.Content["application/json"].Schema.Value)
-	require.NotNil(t, (*doc.Paths["/test2"].Post.Callbacks["callback2"].Value)["{$request.body#/callback}"].Post.RequestBody.Value.Content["application/json"].Schema.Value)
+	require.NotNil(t, doc.
+		Paths.Value("/test1").
+		Post.Callbacks["callback1"].Value.
+		Value("{$request.body#/callback}").
+		Post.RequestBody.Value.
+		Content["application/json"].
+		Schema.Value)
+	require.NotNil(t, doc.
+		Paths.Value("/test2").
+		Post.Callbacks["callback2"].Value.
+		Value("{$request.body#/callback}").
+		Post.RequestBody.Value.
+		Content["application/json"].
+		Schema.Value)
 }
diff --git a/openapi3/issue819_test.go b/openapi3/issue819_test.go
index 80f2136..121b50d 100644
--- a/openapi3/issue819_test.go
+++ b/openapi3/issue819_test.go
@@ -33,7 +33,7 @@ paths:
 	err = doc.Validate(sl.Context)
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/v1/operation"].Get.Responses.Get(201))
-	require.Nil(t, doc.Paths["/v1/operation"].Get.Responses.Get(404))
-	require.Nil(t, doc.Paths["/v1/operation"].Get.Responses.Get(999))
+	require.NotNil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(201))
+	require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(404))
+	require.Nil(t, doc.Paths.Value("/v1/operation").Get.Responses.Status(999))
 }
diff --git a/openapi3/load_with_go_embed_test.go b/openapi3/load_with_go_embed_test.go
index e0fb915..3b77e5f 100644
--- a/openapi3/load_with_go_embed_test.go
+++ b/openapi3/load_with_go_embed_test.go
@@ -30,6 +30,15 @@ func Example() {
 		panic(err)
 	}
 
-	fmt.Println(doc.Paths["/foo"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Type)
+	fmt.Println(doc.
+		Paths.Value("/foo").
+		Get.Responses.Value("200").Value.
+		Content["application/json"].
+		Schema.Value.
+		Properties["foo2"].Value.
+		Properties["foo"].Value.
+		Properties["bar"].Value.
+		Type,
+	)
 	// Output: string
 }
diff --git a/openapi3/loader_empty_response_description_test.go b/openapi3/loader_empty_response_description_test.go
index 3c4b6bf..9c2225e 100644
--- a/openapi3/loader_empty_response_description_test.go
+++ b/openapi3/loader_empty_response_description_test.go
@@ -36,9 +36,7 @@ func TestJSONSpecResponseDescriptionEmptiness(t *testing.T) {
 		loader := NewLoader()
 		doc, err := loader.LoadFromData(spec)
 		require.NoError(t, err)
-		got := doc.Paths["/path1"].Get.Responses["200"].Value.Description
-		expected := ""
-		require.Equal(t, &expected, got)
+		require.Equal(t, "", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
 		t.Log("Empty description provided: valid spec")
 		err = doc.Validate(loader.Context)
 		require.NoError(t, err)
@@ -49,9 +47,7 @@ func TestJSONSpecResponseDescriptionEmptiness(t *testing.T) {
 		loader := NewLoader()
 		doc, err := loader.LoadFromData(spec)
 		require.NoError(t, err)
-		got := doc.Paths["/path1"].Get.Responses["200"].Value.Description
-		expected := "My response"
-		require.Equal(t, &expected, got)
+		require.Equal(t, "My response", *doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
 		t.Log("Non-empty description provided: valid spec")
 		err = doc.Validate(loader.Context)
 		require.NoError(t, err)
@@ -61,8 +57,7 @@ func TestJSONSpecResponseDescriptionEmptiness(t *testing.T) {
 		loader := NewLoader()
 		doc, err := loader.LoadFromData(data)
 		require.NoError(t, err)
-		got := doc.Paths["/path1"].Get.Responses["200"].Value.Description
-		require.Nil(t, got)
+		require.Nil(t, doc.Paths.Value("/path1").Get.Responses.Value("200").Value.Description)
 		t.Log("No description provided: invalid spec")
 		err = doc.Validate(loader.Context)
 		require.Error(t, err)
diff --git a/openapi3/loader_issue220_test.go b/openapi3/loader_issue220_test.go
index 57a44d5..0b25697 100644
--- a/openapi3/loader_issue220_test.go
+++ b/openapi3/loader_issue220_test.go
@@ -22,6 +22,11 @@ func TestIssue220(t *testing.T) {
 		err = doc.Validate(loader.Context)
 		require.NoError(t, err)
 
-		require.Equal(t, "integer", doc.Paths["/foo"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Properties["bar"].Value.Type)
+		require.Equal(t, "integer", doc.
+			Paths.Value("/foo").
+			Get.Responses.Value("200").Value.
+			Content["application/json"].
+			Schema.Value.Properties["bar"].Value.
+			Type)
 	}
 }
diff --git a/openapi3/loader_outside_refs_test.go b/openapi3/loader_outside_refs_test.go
index fc6a2a1..3f2cf7c 100644
--- a/openapi3/loader_outside_refs_test.go
+++ b/openapi3/loader_outside_refs_test.go
@@ -16,7 +16,16 @@ func TestLoadOutsideRefs(t *testing.T) {
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 
-	require.Equal(t, "string", doc.Paths["/service"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Items.Value.AllOf[0].Value.Properties["created_at"].Value.Type)
+	require.Equal(t, "string", doc.
+		Paths.Value("/service").
+		Get.
+		Responses.Value("200").Value.
+		Content["application/json"].
+		Schema.Value.
+		Items.Value.
+		AllOf[0].Value.
+		Properties["created_at"].Value.
+		Type)
 }
 
 func TestIssue423(t *testing.T) {
diff --git a/openapi3/loader_read_from_uri_func_test.go b/openapi3/loader_read_from_uri_func_test.go
index 50c060b..8269c8e 100644
--- a/openapi3/loader_read_from_uri_func_test.go
+++ b/openapi3/loader_read_from_uri_func_test.go
@@ -20,7 +20,16 @@ func TestLoaderReadFromURIFunc(t *testing.T) {
 	require.NoError(t, err)
 	require.NotNil(t, doc)
 	require.NoError(t, doc.Validate(loader.Context))
-	require.Equal(t, "bar", doc.Paths["/foo"].Get.Responses.Get(200).Value.Content.Get("application/json").Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example)
+	require.Equal(t, "bar", doc.
+		Paths.Value("/foo").
+		Get.
+		Responses.Status(200).Value.
+		Content.Get("application/json").
+		Schema.Value.
+		Properties["foo2"].Value.
+		Properties["foo"].Value.
+		Properties["bar"].Value.
+		Example)
 }
 
 type multipleSourceLoaderExample struct {
diff --git a/openapi3/loader_recursive_ref_test.go b/openapi3/loader_recursive_ref_test.go
index 924cb6b..85655ef 100644
--- a/openapi3/loader_recursive_ref_test.go
+++ b/openapi3/loader_recursive_ref_test.go
@@ -13,9 +13,24 @@ func TestLoaderSupportsRecursiveReference(t *testing.T) {
 	require.NoError(t, err)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "bar", doc.Paths["/foo"].Get.Responses.Get(200).Value.Content.Get("application/json").Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example)
-	require.Equal(t, "ErrorDetails", doc.Paths["/foo"].Get.Responses.Get(400).Value.Content.Get("application/json").Schema.Value.Title)
-	require.Equal(t, "ErrorDetails", doc.Paths["/double-ref-foo"].Get.Responses.Get(400).Value.Content.Get("application/json").Schema.Value.Title)
+
+	require.Equal(t, "bar", doc.
+		Paths.Value("/foo").
+		Get.Responses.Status(200).Value.
+		Content.Get("application/json").
+		Schema.Value.Properties["foo2"].Value.Properties["foo"].Value.Properties["bar"].Value.Example)
+
+	require.Equal(t, "ErrorDetails", doc.
+		Paths.Value("/foo").
+		Get.Responses.Status(400).Value.
+		Content.Get("application/json").
+		Schema.Value.Title)
+
+	require.Equal(t, "ErrorDetails", doc.
+		Paths.Value("/double-ref-foo").
+		Get.Responses.Status(400).Value.
+		Content.Get("application/json").
+		Schema.Value.Title)
 }
 
 func TestIssue447(t *testing.T) {
@@ -38,14 +53,9 @@ components:
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 	require.Equal(t, "object", doc.Components.
-		// Complex
 		Schemas["Complex"].
-		// parent
 		Value.Properties["parent"].
-		// parent
 		Value.Properties["parent"].
-		// parent
 		Value.Properties["parent"].
-		// type
 		Value.Type)
 }
diff --git a/openapi3/loader_relative_refs_test.go b/openapi3/loader_relative_refs_test.go
index 2efb0c7..2ebd91b 100644
--- a/openapi3/loader_relative_refs_test.go
+++ b/openapi3/loader_relative_refs_test.go
@@ -81,42 +81,42 @@ var refTestDataEntries = []refTestDataEntry{
 		name:            "PathParameterRef",
 		contentTemplate: externalPathParameterRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test/{id}"].Parameters[0].Value.Name)
-			require.Equal(t, "id", doc.Paths["/test/{id}"].Parameters[0].Value.Name)
+			require.NotNil(t, doc.Paths.Value("/test/{id}").Parameters[0].Value.Name)
+			require.Equal(t, "id", doc.Paths.Value("/test/{id}").Parameters[0].Value.Name)
 		},
 	},
 	{
 		name:            "PathOperationParameterRef",
 		contentTemplate: externalPathOperationParameterRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test/{id}"].Get.Parameters[0].Value)
-			require.Equal(t, "id", doc.Paths["/test/{id}"].Get.Parameters[0].Value.Name)
+			require.NotNil(t, doc.Paths.Value("/test/{id}").Get.Parameters[0].Value)
+			require.Equal(t, "id", doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Name)
 		},
 	},
 	{
 		name:            "PathOperationRequestBodyRef",
 		contentTemplate: externalPathOperationRequestBodyRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.RequestBody.Value)
-			require.NotNil(t, doc.Paths["/test"].Post.RequestBody.Value.Content)
+			require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value)
+			require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content)
 		},
 	},
 	{
 		name:            "PathOperationResponseRef",
 		contentTemplate: externalPathOperationResponseRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value)
+			require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
 			desc := "description"
-			require.Equal(t, &desc, doc.Paths["/test"].Post.Responses["default"].Value.Description)
+			require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
 		},
 	},
 	{
 		name:            "PathOperationParameterSchemaRef",
 		contentTemplate: externalPathOperationParameterSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test/{id}"].Get.Parameters[0].Value.Schema.Value)
-			require.Equal(t, "string", doc.Paths["/test/{id}"].Get.Parameters[0].Value.Schema.Value.Type)
-			require.Equal(t, "id", doc.Paths["/test/{id}"].Get.Parameters[0].Value.Name)
+			require.NotNil(t, doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Schema.Value)
+			require.Equal(t, "string", doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Schema.Value.Type)
+			require.Equal(t, "id", doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Name)
 		},
 	},
 
@@ -124,7 +124,7 @@ var refTestDataEntries = []refTestDataEntry{
 		name:            "PathOperationParameterRefWithContentInQuery",
 		contentTemplate: externalPathOperationParameterWithContentInQueryTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			schemaRef := doc.Paths["/test/{id}"].Get.Parameters[0].Value.Content["application/json"].Schema
+			schemaRef := doc.Paths.Value("/test/{id}").Get.Parameters[0].Value.Content["application/json"].Schema
 			require.NotNil(t, schemaRef.Value)
 			require.Equal(t, "string", schemaRef.Value.Type)
 		},
@@ -134,36 +134,36 @@ var refTestDataEntries = []refTestDataEntry{
 		name:            "PathOperationRequestBodyExampleRef",
 		contentTemplate: externalPathOperationRequestBodyExampleRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value)
-			require.Equal(t, "description", doc.Paths["/test"].Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value.Description)
+			require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value)
+			require.Equal(t, "description", doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Examples["application/json"].Value.Description)
 		},
 	},
 	{
 		name:            "PathOperationRequestBodyContentSchemaRef",
 		contentTemplate: externalPathOperationRequestBodyContentSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.RequestBody.Value.Content["application/json"].Schema.Value)
-			require.Equal(t, "string", doc.Paths["/test"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Type)
+			require.NotNil(t, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Schema.Value)
+			require.Equal(t, "string", doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Schema.Value.Type)
 		},
 	},
 	{
 		name:            "PathOperationResponseExampleRef",
 		contentTemplate: externalPathOperationResponseExampleRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value)
+			require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
 			desc := "testdescription"
-			require.Equal(t, &desc, doc.Paths["/test"].Post.Responses["default"].Value.Description)
-			require.Equal(t, "description", doc.Paths["/test"].Post.Responses["default"].Value.Content["application/json"].Examples["application/json"].Value.Description)
+			require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
+			require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Content["application/json"].Examples["application/json"].Value.Description)
 		},
 	},
 	{
 		name:            "PathOperationResponseSchemaRef",
 		contentTemplate: externalPathOperationResponseSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value)
+			require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
 			desc := "testdescription"
-			require.Equal(t, &desc, doc.Paths["/test"].Post.Responses["default"].Value.Description)
-			require.Equal(t, "string", doc.Paths["/test"].Post.Responses["default"].Value.Content["application/json"].Schema.Value.Type)
+			require.Equal(t, &desc, doc.Paths.Value("/test").Post.Responses.Default().Value.Description)
+			require.Equal(t, "string", doc.Paths.Value("/test").Post.Responses.Default().Value.Content["application/json"].Schema.Value.Type)
 		},
 	},
 	{
@@ -178,8 +178,8 @@ var refTestDataEntries = []refTestDataEntry{
 		name:            "RequestResponseHeaderRef",
 		contentTemplate: externalRequestResponseHeaderRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
-			require.Equal(t, "description", doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
+			require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
+			require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
 		},
 	},
 }
@@ -792,9 +792,9 @@ var relativeDocRefsTestDataEntries = []refTestDataEntry{
 		name:            "PathRef",
 		contentTemplate: relativePathDocsRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
-			require.NotNil(t, doc.Paths["/pets"])
-			require.NotNil(t, doc.Paths["/pets"].Get.Responses["200"])
-			require.NotNil(t, doc.Paths["/pets"].Get.Responses["200"].Value.Content["application/json"])
+			require.NotNil(t, doc.Paths.Value("/pets"))
+			require.NotNil(t, doc.Paths.Value("/pets").Get.Responses.Value("200"))
+			require.NotNil(t, doc.Paths.Value("/pets").Get.Responses.Value("200").Value.Content["application/json"])
 		},
 	},
 }
@@ -913,40 +913,40 @@ func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
 
 	// path in nested directory
 	// check parameter
-	nestedDirPath := doc.Paths["/pets/{id}"]
+	nestedDirPath := doc.Paths.Value("/pets/{id}")
 	require.Equal(t, "param", nestedDirPath.Patch.Parameters[0].Value.Name)
 	require.Equal(t, "path", nestedDirPath.Patch.Parameters[0].Value.In)
 	require.Equal(t, true, nestedDirPath.Patch.Parameters[0].Value.Required)
 
 	// check header
-	require.Equal(t, "header", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Rate-Limit-Reset"].Value.Description)
-	require.Equal(t, "header1", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Another"].Value.Description)
-	require.Equal(t, "header2", nestedDirPath.Patch.Responses["200"].Value.Headers["X-And-Another"].Value.Description)
+	require.Equal(t, "header", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Rate-Limit-Reset"].Value.Description)
+	require.Equal(t, "header1", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Another"].Value.Description)
+	require.Equal(t, "header2", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-And-Another"].Value.Description)
 
 	// check request body
 	require.Equal(t, "example request", nestedDirPath.Patch.RequestBody.Value.Description)
 
 	// check response schema and example
-	require.Equal(t, nestedDirPath.Patch.Responses["200"].Value.Content["application/json"].Schema.Value.Type, "string")
+	require.Equal(t, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type, "string")
 	expectedExample := "hello"
-	require.Equal(t, expectedExample, nestedDirPath.Patch.Responses["200"].Value.Content["application/json"].Examples["CustomTestExample"].Value.Value)
+	require.Equal(t, expectedExample, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Examples["CustomTestExample"].Value.Value)
 
 	// path in more nested directory
 	// check parameter
-	moreNestedDirPath := doc.Paths["/pets/{id}/{city}"]
+	moreNestedDirPath := doc.Paths.Value("/pets/{id}/{city}")
 	require.Equal(t, "param", moreNestedDirPath.Patch.Parameters[0].Value.Name)
 	require.Equal(t, "path", moreNestedDirPath.Patch.Parameters[0].Value.In)
 	require.Equal(t, true, moreNestedDirPath.Patch.Parameters[0].Value.Required)
 
 	// check header
-	require.Equal(t, "header", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Rate-Limit-Reset"].Value.Description)
-	require.Equal(t, "header1", nestedDirPath.Patch.Responses["200"].Value.Headers["X-Another"].Value.Description)
-	require.Equal(t, "header2", nestedDirPath.Patch.Responses["200"].Value.Headers["X-And-Another"].Value.Description)
+	require.Equal(t, "header", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Rate-Limit-Reset"].Value.Description)
+	require.Equal(t, "header1", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-Another"].Value.Description)
+	require.Equal(t, "header2", nestedDirPath.Patch.Responses.Value("200").Value.Headers["X-And-Another"].Value.Description)
 
 	// check request body
 	require.Equal(t, "example request", moreNestedDirPath.Patch.RequestBody.Value.Description)
 
 	// check response schema and example
-	require.Equal(t, "string", moreNestedDirPath.Patch.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
-	require.Equal(t, moreNestedDirPath.Patch.Responses["200"].Value.Content["application/json"].Examples["CustomTestExample"].Value.Value, expectedExample)
+	require.Equal(t, "string", moreNestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
+	require.Equal(t, moreNestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Examples["CustomTestExample"].Value.Value, expectedExample)
 }
diff --git a/openapi3/loader_test.go b/openapi3/loader_test.go
index 2c1d419..32cb524 100644
--- a/openapi3/loader_test.go
+++ b/openapi3/loader_test.go
@@ -60,8 +60,8 @@ paths:
 	require.NoError(t, err)
 	require.Equal(t, "An API", doc.Info.Title)
 	require.Equal(t, 2, len(doc.Components.Schemas))
-	require.Equal(t, 1, len(doc.Paths))
-	require.Equal(t, "unexpected error", *doc.Paths["/items"].Put.Responses.Default().Value.Description)
+	require.Equal(t, 1, doc.Paths.Len())
+	require.Equal(t, "unexpected error", *doc.Paths.Value("/items").Put.Responses.Default().Value.Description)
 
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
@@ -168,7 +168,7 @@ paths:
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 
-	example := doc.Paths["/"].Get.Responses.Get(200).Value.Content.Get("application/json").Examples["test"]
+	example := doc.Paths.Value("/").Get.Responses.Status(200).Value.Content.Get("application/json").Examples["test"]
 	require.NotNil(t, example.Value)
 	require.Equal(t, example.Value.Value.(map[string]interface{})["error"].(bool), false)
 }
@@ -231,7 +231,7 @@ paths:
 	doc, err := loader.LoadFromData(spec)
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/"].Parameters[0].Value)
+	require.NotNil(t, doc.Paths.Value("/").Parameters[0].Value)
 }
 
 func TestLoadRequestExampleRef(t *testing.T) {
@@ -263,7 +263,7 @@ paths:
 	doc, err := loader.LoadFromData(spec)
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/"].Post.RequestBody.Value.Content.Get("application/json").Examples["test"])
+	require.NotNil(t, doc.Paths.Value("/").Post.RequestBody.Value.Content.Get("application/json").Examples["test"])
 }
 
 func createTestServer(t *testing.T, handler http.Handler) *httptest.Server {
@@ -300,7 +300,7 @@ func TestLoadWithReferenceInReference(t *testing.T) {
 	require.NotNil(t, doc)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "string", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+	require.Equal(t, "string", doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
 }
 
 func TestLoadWithRecursiveReferenceInLocalReferenceInParentSubdir(t *testing.T) {
@@ -311,7 +311,7 @@ func TestLoadWithRecursiveReferenceInLocalReferenceInParentSubdir(t *testing.T)
 	require.NotNil(t, doc)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "object", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+	require.Equal(t, "object", doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
 }
 
 func TestLoadWithRecursiveReferenceInReferenceInLocalReference(t *testing.T) {
@@ -322,8 +322,8 @@ func TestLoadWithRecursiveReferenceInReferenceInLocalReference(t *testing.T) {
 	require.NotNil(t, doc)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "integer", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Type)
-	require.Equal(t, "int64", doc.Paths["/api/test/ref/in/ref"].Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Format)
+	require.Equal(t, "integer", doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Type)
+	require.Equal(t, "int64", doc.Paths.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["data"].Value.Properties["definition_reference"].Value.Properties["ref_prop_part"].Value.Properties["idPart"].Value.Format)
 }
 
 func TestLoadWithReferenceInReferenceInProperty(t *testing.T) {
@@ -334,7 +334,7 @@ func TestLoadWithReferenceInReferenceInProperty(t *testing.T) {
 	require.NotNil(t, doc)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "Problem details", doc.Paths["/api/test/ref/in/ref/in/property"].Post.Responses["401"].Value.Content["application/json"].Schema.Value.Properties["error"].Value.Title)
+	require.Equal(t, "Problem details", doc.Paths.Value("/api/test/ref/in/ref/in/property").Post.Responses.Value("401").Value.Content["application/json"].Schema.Value.Properties["error"].Value.Title)
 }
 
 func TestLoadFileWithExternalSchemaRef(t *testing.T) {
@@ -393,8 +393,8 @@ func TestLoadRequestResponseHeaderRef(t *testing.T) {
 	doc, err := loader.LoadFromData(spec)
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
-	require.Equal(t, "testheader", doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
+	require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
+	require.Equal(t, "testheader", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
 }
 
 func TestLoadFromDataWithExternalRequestResponseHeaderRemoteRef(t *testing.T) {
@@ -433,8 +433,8 @@ func TestLoadFromDataWithExternalRequestResponseHeaderRemoteRef(t *testing.T) {
 	doc, err := loader.LoadFromDataWithPath(spec, &url.URL{Path: "testdata/testfilename.openapi.json"})
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
-	require.Equal(t, "description", doc.Paths["/test"].Post.Responses["default"].Value.Headers["X-TEST-HEADER"].Value.Description)
+	require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
+	require.Equal(t, "description", doc.Paths.Value("/test").Post.Responses.Default().Value.Headers["X-TEST-HEADER"].Value.Description)
 }
 
 func TestLoadYamlFile(t *testing.T) {
@@ -461,8 +461,8 @@ func TestLoadYamlFileWithExternalPathRef(t *testing.T) {
 	doc, err := loader.LoadFromFile("testdata/pathref.openapi.yml")
 	require.NoError(t, err)
 
-	require.NotNil(t, doc.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
-	require.Equal(t, "string", doc.Paths["/test"].Get.Responses["200"].Value.Content["application/json"].Schema.Value.Type)
+	require.NotNil(t, doc.Paths.Value("/test").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
+	require.Equal(t, "string", doc.Paths.Value("/test").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
 }
 
 func TestResolveResponseLinkRef(t *testing.T) {
@@ -504,7 +504,7 @@ paths:
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 
-	response := doc.Paths[`/users/{id}`].Get.Responses.Get(200).Value
+	response := doc.Paths.Value("/users/{id}").Get.Responses.Status(200).Value
 	link := response.Links[`father`].Value
 	require.NotNil(t, link)
 	require.Equal(t, "getUserById", link.OperationID)
@@ -517,7 +517,7 @@ func TestLinksFromOAISpec(t *testing.T) {
 	require.NoError(t, err)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	response := doc.Paths[`/2.0/repositories/{username}/{slug}`].Get.Responses.Get(200).Value
+	response := doc.Paths.Value("/2.0/repositories/{username}/{slug}").Get.Responses.Status(200).Value
 	link := response.Links[`repositoryPullRequests`].Value
 	require.Equal(t, map[string]interface{}{
 		"username": "$response.body#/owner/username",
diff --git a/openapi3/maplike_test.go b/openapi3/maplike_test.go
new file mode 100644
index 0000000..7c502c4
--- /dev/null
+++ b/openapi3/maplike_test.go
@@ -0,0 +1,74 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestMaplikeMethods(t *testing.T) {
+	t.Parallel()
+
+	t.Run("*Responses", func(t *testing.T) {
+		t.Parallel()
+		t.Run("nil", func(t *testing.T) {
+			x := (*Responses)(nil)
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*ResponseRef)(nil), x.Map())
+			require.Equal(t, (*ResponseRef)(nil), x.Value("key"))
+			require.Panics(t, func() { x.Set("key", &ResponseRef{}) })
+		})
+		t.Run("nonnil", func(t *testing.T) {
+			x := &Responses{}
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*ResponseRef)(nil), x.Map())
+			require.Equal(t, (*ResponseRef)(nil), x.Value("key"))
+			x.Set("key", &ResponseRef{})
+			require.Equal(t, 1, x.Len())
+			require.Equal(t, map[string]*ResponseRef{"key": {}}, x.Map())
+			require.Equal(t, &ResponseRef{}, x.Value("key"))
+		})
+	})
+
+	t.Run("*Callback", func(t *testing.T) {
+		t.Parallel()
+		t.Run("nil", func(t *testing.T) {
+			x := (*Callback)(nil)
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*PathItem)(nil), x.Map())
+			require.Equal(t, (*PathItem)(nil), x.Value("key"))
+			require.Panics(t, func() { x.Set("key", &PathItem{}) })
+		})
+		t.Run("nonnil", func(t *testing.T) {
+			x := &Callback{}
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*PathItem)(nil), x.Map())
+			require.Equal(t, (*PathItem)(nil), x.Value("key"))
+			x.Set("key", &PathItem{})
+			require.Equal(t, 1, x.Len())
+			require.Equal(t, map[string]*PathItem{"key": {}}, x.Map())
+			require.Equal(t, &PathItem{}, x.Value("key"))
+		})
+	})
+
+	t.Run("*Paths", func(t *testing.T) {
+		t.Parallel()
+		t.Run("nil", func(t *testing.T) {
+			x := (*Paths)(nil)
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*PathItem)(nil), x.Map())
+			require.Equal(t, (*PathItem)(nil), x.Value("key"))
+			require.Panics(t, func() { x.Set("key", &PathItem{}) })
+		})
+		t.Run("nonnil", func(t *testing.T) {
+			x := &Paths{}
+			require.Equal(t, 0, x.Len())
+			require.Equal(t, (map[string]*PathItem)(nil), x.Map())
+			require.Equal(t, (*PathItem)(nil), x.Value("key"))
+			x.Set("key", &PathItem{})
+			require.Equal(t, 1, x.Len())
+			require.Equal(t, map[string]*PathItem{"key": {}}, x.Map())
+			require.Equal(t, &PathItem{}, x.Value("key"))
+		})
+	})
+}
diff --git a/openapi3/openapi3_test.go b/openapi3/openapi3_test.go
index 9cfd277..9892948 100644
--- a/openapi3/openapi3_test.go
+++ b/openapi3/openapi3_test.go
@@ -272,8 +272,8 @@ func spec() *T {
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: Paths{
-			"/hello": &PathItem{
+		Paths: NewPaths(
+			WithPath("/hello", &PathItem{
 				Post: &Operation{
 					Parameters: Parameters{
 						{
@@ -285,12 +285,12 @@ func spec() *T {
 						Ref:   "#/components/requestBodies/someRequestBody",
 						Value: requestBody,
 					},
-					Responses: Responses{
-						"200": &ResponseRef{
+					Responses: NewResponses(
+						WithStatus(200, &ResponseRef{
 							Ref:   "#/components/responses/someResponse",
 							Value: response,
-						},
-					},
+						}),
+					),
 				},
 				Parameters: Parameters{
 					{
@@ -298,8 +298,8 @@ func spec() *T {
 						Value: parameter,
 					},
 				},
-			},
-		},
+			}),
+		),
 		Components: &Components{
 			Parameters: ParametersMap{
 				"someParameter": {Value: parameter},
diff --git a/openapi3/operation_test.go b/openapi3/operation_test.go
index 50684a3..4c3a7bd 100644
--- a/openapi3/operation_test.go
+++ b/openapi3/operation_test.go
@@ -8,17 +8,16 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-var operation *Operation
-
-func initOperation() {
-	operation = NewOperation()
+func initOperation() *Operation {
+	operation := NewOperation()
 	operation.Description = "Some description"
 	operation.Summary = "Some summary"
 	operation.Tags = []string{"tag1", "tag2"}
+	return operation
 }
 
 func TestAddParameter(t *testing.T) {
-	initOperation()
+	operation := initOperation()
 	operation.AddParameter(NewQueryParameter("param1"))
 	operation.AddParameter(NewCookieParameter("param2"))
 	require.Equal(t, "param1", operation.Parameters.GetByInAndName("query", "param1").Name)
@@ -26,20 +25,20 @@ func TestAddParameter(t *testing.T) {
 }
 
 func TestAddResponse(t *testing.T) {
-	initOperation()
+	operation := initOperation()
 	operation.AddResponse(200, NewResponse())
 	operation.AddResponse(400, NewResponse())
-	require.NotNil(t, "status 200", operation.Responses.Get(200).Value)
-	require.NotNil(t, "status 400", operation.Responses.Get(400).Value)
+	require.NotNil(t, "status 200", operation.Responses.Status(200).Value)
+	require.NotNil(t, "status 400", operation.Responses.Status(400).Value)
 }
 
 func operationWithoutResponses() *Operation {
-	initOperation()
+	operation := initOperation()
 	return operation
 }
 
 func operationWithResponses() *Operation {
-	initOperation()
+	operation := initOperation()
 	operation.AddResponse(200, NewResponse().WithDescription("some response"))
 	return operation
 }
diff --git a/openapi3/refs_test.go b/openapi3/refs_test.go
index 3f04430..e328c33 100644
--- a/openapi3/refs_test.go
+++ b/openapi3/refs_test.go
@@ -233,13 +233,15 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
-	require.IsType(t, Paths{}, v)
-	require.Equal(t, reflect.TypeOf(Paths{}).Kind(), kind)
+	require.NotNil(t, v)
+	require.IsType(t, &Paths{}, v)
+	require.Equal(t, reflect.TypeOf(&Paths{}).Kind(), kind)
 
 	ptr, err = jsonpointer.New("/paths/~1pet")
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &PathItem{}, v)
 	require.Equal(t, reflect.TypeOf(&PathItem{}).Kind(), kind)
 
@@ -247,6 +249,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Operation{}, v)
 	require.Equal(t, reflect.TypeOf(&Operation{}).Kind(), kind)
 
@@ -254,13 +257,15 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
-	require.IsType(t, Responses{}, v)
-	require.Equal(t, reflect.TypeOf(Responses{}).Kind(), kind)
+	require.NotNil(t, v)
+	require.IsType(t, &Responses{}, v)
+	require.Equal(t, reflect.TypeOf(&Responses{}).Kind(), kind)
 
 	ptr, err = jsonpointer.New("/paths/~1pet/put/responses/200")
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Response{}, v)
 	require.Equal(t, reflect.TypeOf(&Response{}).Kind(), kind)
 
@@ -268,6 +273,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, Content{}, v)
 	require.Equal(t, reflect.TypeOf(Content{}).Kind(), kind)
 
@@ -275,6 +281,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Ref{}, v)
 	require.Equal(t, reflect.Ptr, kind)
 	require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref)
@@ -283,6 +290,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Ref{}, v)
 	require.Equal(t, reflect.Ptr, kind)
 	require.Equal(t, "#/components/schemas/Pet", v.(*Ref).Ref)
@@ -291,6 +299,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
 	require.Equal(t, "integer", v.(*Schema).Type)
@@ -299,6 +308,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
 	require.Equal(t, "string", v.(*Schema).Type)
@@ -307,6 +317,7 @@ components:
 	require.NoError(t, err)
 	v, kind, err = ptr.Get(doc)
 	require.NoError(t, err)
+	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
 	require.Equal(t, "integer", v.(*Schema).Type)
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 5347a93..967f1ba 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -1042,7 +1042,7 @@ func TestDecodeParameter(t *testing.T) {
 						Title:   "MyAPI",
 						Version: "0.1",
 					}
-					doc := &openapi3.T{OpenAPI: "3.0.0", Info: info, Paths: openapi3.Paths{}}
+					doc := &openapi3.T{OpenAPI: "3.0.0", Info: info, Paths: openapi3.NewPaths()}
 					op := &openapi3.Operation{
 						OperationID: "test",
 						Parameters:  []*openapi3.ParameterRef{{Value: tc.param}},
diff --git a/openapi3filter/validation_test.go b/openapi3filter/validation_test.go
index a95b085..85ff0dc 100644
--- a/openapi3filter/validation_test.go
+++ b/openapi3filter/validation_test.go
@@ -56,8 +56,8 @@ func TestFilter(t *testing.T) {
 				URL: "http://example.com/api/",
 			},
 		},
-		Paths: openapi3.Paths{
-			"/prefix/{pathArg}/suffix": &openapi3.PathItem{
+		Paths: openapi3.NewPaths(
+			openapi3.WithPath("/prefix/{pathArg}/suffix", &openapi3.PathItem{
 				Post: &openapi3.Operation{
 					Parameters: openapi3.Parameters{
 						{
@@ -136,9 +136,9 @@ func TestFilter(t *testing.T) {
 					},
 					Responses: openapi3.NewResponses(),
 				},
-			},
+			}),
 
-			"/issue151": &openapi3.PathItem{
+			openapi3.WithPath("/issue151", &openapi3.PathItem{
 				Get: &openapi3.Operation{
 					Responses: openapi3.NewResponses(),
 				},
@@ -152,8 +152,8 @@ func TestFilter(t *testing.T) {
 						},
 					},
 				},
-			},
-		},
+			}),
+		),
 	}
 
 	err := doc.Validate(context.Background())
@@ -527,7 +527,7 @@ func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *test
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: map[string]*openapi3.PathItem{},
+		Paths: openapi3.NewPaths(),
 		Security: openapi3.SecurityRequirements{
 			{
 				securitySchemes[1].Name: {},
@@ -555,12 +555,12 @@ func TestRootSecurityRequirementsAreUsedIfNotProvidedAtTheOperationLevel(t *test
 			}
 			securityRequirements = tempS
 		}
-		doc.Paths[tc.name] = &openapi3.PathItem{
+		doc.Paths.Set(tc.name, &openapi3.PathItem{
 			Get: &openapi3.Operation{
 				Security:  securityRequirements,
 				Responses: openapi3.NewResponses(),
 			},
-		}
+		})
 	}
 
 	err := doc.Validate(context.Background())
@@ -655,7 +655,7 @@ func TestAnySecurityRequirementMet(t *testing.T) {
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: map[string]*openapi3.PathItem{},
+		Paths: openapi3.NewPaths(),
 		Components: &openapi3.Components{
 			SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
 		},
@@ -680,12 +680,12 @@ func TestAnySecurityRequirementMet(t *testing.T) {
 		}
 
 		// Create the path with the security requirements
-		doc.Paths[tc.name] = &openapi3.PathItem{
+		doc.Paths.Set(tc.name, &openapi3.PathItem{
 			Get: &openapi3.Operation{
 				Security:  securityRequirements,
 				Responses: openapi3.NewResponses(),
 			},
-		}
+		})
 	}
 
 	err := doc.Validate(context.Background())
@@ -752,7 +752,7 @@ func TestAllSchemesMet(t *testing.T) {
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: map[string]*openapi3.PathItem{},
+		Paths: openapi3.NewPaths(),
 		Components: &openapi3.Components{
 			SecuritySchemes: map[string]*openapi3.SecuritySchemeRef{},
 		},
@@ -780,14 +780,14 @@ func TestAllSchemesMet(t *testing.T) {
 			}
 		}
 
-		doc.Paths[tc.name] = &openapi3.PathItem{
+		doc.Paths.Set(tc.name, &openapi3.PathItem{
 			Get: &openapi3.Operation{
 				Security: &openapi3.SecurityRequirements{
 					securityRequirement,
 				},
 				Responses: openapi3.NewResponses(),
 			},
-		}
+		})
 	}
 
 	err := doc.Validate(context.Background())
diff --git a/routers/gorillamux/router_test.go b/routers/gorillamux/router_test.go
index bebe056..4f40719 100644
--- a/routers/gorillamux/router_test.go
+++ b/routers/gorillamux/router_test.go
@@ -32,8 +32,8 @@ func TestRouter(t *testing.T) {
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: openapi3.Paths{
-			"/hello": &openapi3.PathItem{
+		Paths: openapi3.NewPaths(
+			openapi3.WithPath("/hello", &openapi3.PathItem{
 				Connect: helloCONNECT,
 				Delete:  helloDELETE,
 				Get:     helloGET,
@@ -43,34 +43,34 @@ func TestRouter(t *testing.T) {
 				Post:    helloPOST,
 				Put:     helloPUT,
 				Trace:   helloTRACE,
-			},
-			"/onlyGET": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/onlyGET", &openapi3.PathItem{
 				Get: helloGET,
-			},
-			"/params/{x}/{y}/{z:.*}": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/params/{x}/{y}/{z:.*}", &openapi3.PathItem{
 				Get: paramsGET,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("x").WithSchema(openapi3.NewStringSchema())},
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("y").WithSchema(openapi3.NewFloat64Schema())},
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("z").WithSchema(openapi3.NewIntegerSchema())},
 				},
-			},
-			"/books/{bookid}": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/books/{bookid}", &openapi3.PathItem{
 				Get: paramsGET,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid").WithSchema(openapi3.NewStringSchema())},
 				},
-			},
-			"/books/{bookid}.json": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/books/{bookid}.json", &openapi3.PathItem{
 				Post: booksPOST,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid2").WithSchema(openapi3.NewStringSchema())},
 				},
-			},
-			"/partial": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/partial", &openapi3.PathItem{
 				Get: partialGET,
-			},
-		},
+			}),
+		),
 	}
 
 	expect := func(r routers.Router, method string, uri string, operation *openapi3.Operation, params map[string]string) {
@@ -80,7 +80,7 @@ func TestRouter(t *testing.T) {
 		route, pathParams, err := r.FindRoute(req)
 		if err != nil {
 			if operation == nil {
-				pathItem := doc.Paths[uri]
+				pathItem := doc.Paths.Value(uri)
 				if pathItem == nil {
 					if err.Error() != routers.ErrPathNotFound.Error() {
 						t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrPathNotFound, err)
@@ -259,7 +259,9 @@ func TestServerPath(t *testing.T) {
 		newServerWithVariables(
 			"/",
 			nil,
-		)},
+		),
+	},
+		Paths: openapi3.NewPaths(),
 	})
 	require.NoError(t, err)
 }
@@ -277,16 +279,16 @@ func TestServerOverrideAtPathLevel(t *testing.T) {
 				URL: "https://example.com",
 			},
 		},
-		Paths: openapi3.Paths{
-			"/hello": &openapi3.PathItem{
+		Paths: openapi3.NewPaths(
+			openapi3.WithPath("/hello", &openapi3.PathItem{
 				Servers: openapi3.Servers{
 					&openapi3.Server{
 						URL: "https://another.com",
 					},
 				},
 				Get: helloGET,
-			},
-		},
+			}),
+		),
 	}
 	err := doc.Validate(context.Background())
 	require.NoError(t, err)
@@ -318,11 +320,11 @@ func TestRelativeURL(t *testing.T) {
 				URL: "/api/v1",
 			},
 		},
-		Paths: openapi3.Paths{
-			"/hello": &openapi3.PathItem{
+		Paths: openapi3.NewPaths(
+			openapi3.WithPath("/hello", &openapi3.PathItem{
 				Get: helloGET,
-			},
-		},
+			}),
+		),
 	}
 	err := doc.Validate(context.Background())
 	require.NoError(t, err)
diff --git a/routers/legacy/router_test.go b/routers/legacy/router_test.go
index 554ecd1..99704b1 100644
--- a/routers/legacy/router_test.go
+++ b/routers/legacy/router_test.go
@@ -31,8 +31,8 @@ func TestRouter(t *testing.T) {
 			Title:   "MyAPI",
 			Version: "0.1",
 		},
-		Paths: openapi3.Paths{
-			"/hello": &openapi3.PathItem{
+		Paths: openapi3.NewPaths(
+			openapi3.WithPath("/hello", &openapi3.PathItem{
 				Connect: helloCONNECT,
 				Delete:  helloDELETE,
 				Get:     helloGET,
@@ -42,34 +42,34 @@ func TestRouter(t *testing.T) {
 				Post:    helloPOST,
 				Put:     helloPUT,
 				Trace:   helloTRACE,
-			},
-			"/onlyGET": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/onlyGET", &openapi3.PathItem{
 				Get: helloGET,
-			},
-			"/params/{x}/{y}/{z.*}": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/params/{x}/{y}/{z.*}", &openapi3.PathItem{
 				Get: paramsGET,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("x").WithSchema(openapi3.NewStringSchema())},
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("y").WithSchema(openapi3.NewFloat64Schema())},
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("z").WithSchema(openapi3.NewIntegerSchema())},
 				},
-			},
-			"/books/{bookid}": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/books/{bookid}", &openapi3.PathItem{
 				Get: paramsGET,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid").WithSchema(openapi3.NewStringSchema())},
 				},
-			},
-			"/books/{bookid2}.json": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/books/{bookid2}.json", &openapi3.PathItem{
 				Post: booksPOST,
 				Parameters: openapi3.Parameters{
 					&openapi3.ParameterRef{Value: openapi3.NewPathParameter("bookid2").WithSchema(openapi3.NewStringSchema())},
 				},
-			},
-			"/partial": &openapi3.PathItem{
+			}),
+			openapi3.WithPath("/partial", &openapi3.PathItem{
 				Get: partialGET,
-			},
-		},
+			}),
+		),
 	}
 
 	expect := func(r routers.Router, method string, uri string, operation *openapi3.Operation, params map[string]string) {
@@ -78,7 +78,7 @@ func TestRouter(t *testing.T) {
 		route, pathParams, err := r.FindRoute(req)
 		if err != nil {
 			if operation == nil {
-				pathItem := doc.Paths[uri]
+				pathItem := doc.Paths.Value(uri)
 				if pathItem == nil {
 					if err.Error() != routers.ErrPathNotFound.Error() {
 						t.Fatalf("'%s %s': should have returned %q, but it returned an error: %v", method, uri, routers.ErrPathNotFound, err)
@@ -200,10 +200,10 @@ func TestRouter(t *testing.T) {
 	}
 	content := openapi3.NewContentWithJSONSchema(schema)
 	responses := openapi3.NewResponses()
-	responses["default"].Value.Content = content
-	doc.Paths["/withExamples"] = &openapi3.PathItem{
+	responses.Value("default").Value.Content = content
+	doc.Paths.Set("/withExamples", &openapi3.PathItem{
 		Get: &openapi3.Operation{Responses: responses},
-	}
+	})
 	err = doc.Validate(context.Background())
 	require.Error(t, err)
 	r, err = NewRouter(doc)