Repository: getkin/kin-openapi
Pull request #912 (2024-02-18 22:10:29)

openapi2,3: support array of types in `type` field
Resolves https://github.com/getkin/kin-openapi/issues/563

The discussion in issue #563 mentions that this is only supported by openapi2, but the JSON Schema validation spec seems to imply that this should still be supported: https://json-schema.org/draft/2020-12/json-schema-validation#name-type

> If the value of "type" is an array, then an instance validates successfully if its type matches any of the types indicated by the strings in the array.

This PR is a big and breaking change and only really adds two tests. Putting it up for discussion sake. Totally understood if an alternative approach is preferred.

fenollp: > The discussion in issue https://github.com/getkin/kin-openapi/issues/563 mentions that this is only supported by openapi2, but the JSON Schema validation spec seems to imply that this should still be supported

Note that OpenAPI`vN when N<3.1` does not use the JSON Schema draft, it defines its own JSON schema.
So in `openapi3`'s `schema.go` there should be a new `Validate()` check that returns an error IFF `len(schema.Type) >1`.
And in the v2-to-v3 code here's the comment: https://github.com/getkin/kin-openapi/issues/563#issuecomment-1199635193
brandonbloom: I've run `./docs.sh` and resolved conflicts with mainline changes.

Regarding the legality of types being an array
brandonbloom: (accidentally closed, reopening now)

Patch:
diff --git a/.github/docs/openapi2.txt b/.github/docs/openapi2.txt
index d65cd18..3283841 100644
--- a/.github/docs/openapi2.txt
+++ b/.github/docs/openapi2.txt
@@ -51,7 +51,7 @@ type Parameter struct {
 	Name             string              `json:"name,omitempty" yaml:"name,omitempty"`
 	Description      string              `json:"description,omitempty" yaml:"description,omitempty"`
 	CollectionFormat string              `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
-	Type             string              `json:"type,omitempty" yaml:"type,omitempty"`
+	Type             *openapi3.Types     `json:"type,omitempty" yaml:"type,omitempty"`
 	Format           string              `json:"format,omitempty" yaml:"format,omitempty"`
 	Pattern          string              `json:"pattern,omitempty" yaml:"pattern,omitempty"`
 	AllowEmptyValue  bool                `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
diff --git a/.github/docs/openapi3.txt b/.github/docs/openapi3.txt
index 7418add..8090952 100644
--- a/.github/docs/openapi3.txt
+++ b/.github/docs/openapi3.txt
@@ -19,6 +19,7 @@ const (
 	TypeNumber  = "number"
 	TypeObject  = "object"
 	TypeString  = "string"
+	TypeNull    = "null"
 )
 const (
 	// FormatOfStringForUUIDOfRFC4122 is an optional predefined format for UUID v1-v5 as specified by RFC4122
@@ -1197,7 +1198,7 @@ type Schema struct {
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
 	AllOf        SchemaRefs    `json:"allOf,omitempty" yaml:"allOf,omitempty"`
 	Not          *SchemaRef    `json:"not,omitempty" yaml:"not,omitempty"`
-	Type         string        `json:"type,omitempty" yaml:"type,omitempty"`
+	Type         *Types        `json:"type,omitempty" yaml:"type,omitempty"`
 	Title        string        `json:"title,omitempty" yaml:"title,omitempty"`
 	Format       string        `json:"format,omitempty" yaml:"format,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
@@ -1299,6 +1300,8 @@ func (schema Schema) MarshalJSON() ([]byte, error)
 
 func (schema *Schema) NewRef() *SchemaRef
 
+func (schema *Schema) PermitsNull() bool
+
 func (schema *Schema) UnmarshalJSON(data []byte) error
     UnmarshalJSON sets Schema to a copy of data.
 
@@ -1721,6 +1724,22 @@ func (tags Tags) Get(name string) *Tag
 func (tags Tags) Validate(ctx context.Context, opts ...ValidationOption) error
     Validate returns an error if Tags does not comply with the OpenAPI spec.
 
+type Types []string
+
+func (pTypes *Types) Includes(typ string) bool
+
+func (types *Types) Is(typ string) bool
+
+func (pTypes *Types) MarshalJSON() ([]byte, error)
+
+func (pTypes *Types) MarshalYAML() (interface{}, error)
+
+func (types *Types) Permits(typ string) bool
+
+func (types *Types) Slice() []string
+
+func (types *Types) UnmarshalJSON(data []byte) error
+
 type ValidationOption func(options *ValidationOptions)
     ValidationOption allows the modification of how the OpenAPI document is
     validated.
diff --git a/openapi2/parameter.go b/openapi2/parameter.go
index 0255098..1203853 100644
--- a/openapi2/parameter.go
+++ b/openapi2/parameter.go
@@ -32,7 +32,7 @@ type Parameter struct {
 	Name             string              `json:"name,omitempty" yaml:"name,omitempty"`
 	Description      string              `json:"description,omitempty" yaml:"description,omitempty"`
 	CollectionFormat string              `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
-	Type             string              `json:"type,omitempty" yaml:"type,omitempty"`
+	Type             *openapi3.Types     `json:"type,omitempty" yaml:"type,omitempty"`
 	Format           string              `json:"format,omitempty" yaml:"format,omitempty"`
 	Pattern          string              `json:"pattern,omitempty" yaml:"pattern,omitempty"`
 	AllowEmptyValue  bool                `json:"allowEmptyValue,omitempty" yaml:"allowEmptyValue,omitempty"`
@@ -76,7 +76,7 @@ func (parameter Parameter) MarshalJSON() ([]byte, error) {
 	if x := parameter.CollectionFormat; x != "" {
 		m["collectionFormat"] = x
 	}
-	if x := parameter.Type; x != "" {
+	if x := parameter.Type; x != nil {
 		m["type"] = x
 	}
 	if x := parameter.Format; x != "" {
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 2404e90..a79f72c 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -248,8 +248,8 @@ func ToV3Parameter(components *openapi3.Components, parameter *openapi2.Paramete
 
 	case "formData":
 		format, typ := parameter.Format, parameter.Type
-		if typ == "file" {
-			format, typ = "binary", "string"
+		if typ.Is("file") {
+			format, typ = "binary", &openapi3.Types{"string"}
 		}
 		if parameter.Extensions == nil {
 			parameter.Extensions = make(map[string]interface{}, 1)
@@ -347,7 +347,7 @@ func formDataBody(bodies map[string]*openapi3.SchemaRef, reqs map[string]bool, c
 		}
 	}
 	schema := &openapi3.Schema{
-		Type:       "object",
+		Type:       &openapi3.Types{"object"},
 		Properties: ToV3Schemas(bodies),
 		Required:   requireds,
 	}
@@ -772,8 +772,8 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 	}
 
 	if schema.Value != nil {
-		if schema.Value.Type == "string" && schema.Value.Format == "binary" {
-			paramType := "file"
+		if schema.Value.Type.Is("string") && schema.Value.Format == "binary" {
+			paramType := &openapi3.Types{"file"}
 			required := false
 
 			value, _ := schema.Value.Extensions["x-formData-name"]
@@ -825,7 +825,7 @@ func FromV3SchemaRef(schema *openapi3.SchemaRef, components *openapi3.Components
 	for i, v := range schema.Value.AllOf {
 		schema.Value.AllOf[i], _ = FromV3SchemaRef(v, components)
 	}
-	if schema.Value.Nullable {
+	if schema.Value.PermitsNull() {
 		schema.Value.Nullable = false
 		if schema.Value.Extensions == nil {
 			schema.Value.Extensions = make(map[string]interface{})
@@ -893,7 +893,7 @@ func FromV3RequestBodyFormData(mediaType *openapi3.MediaType) openapi2.Parameter
 		val := schemaRef.Value
 		typ := val.Type
 		if val.Format == "binary" {
-			typ = "file"
+			typ = &openapi3.Types{"file"}
 		}
 		required := false
 		for _, name := range val.Required {
diff --git a/openapi3/schema.go b/openapi3/schema.go
index e8630b3..32027a1 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -27,6 +27,7 @@ const (
 	TypeNumber  = "number"
 	TypeObject  = "object"
 	TypeString  = "string"
+	TypeNull    = "null"
 
 	// constants for integer formats
 	formatMinInt32 = float64(math.MinInt32)
@@ -92,7 +93,7 @@ type Schema struct {
 	AnyOf        SchemaRefs    `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
 	AllOf        SchemaRefs    `json:"allOf,omitempty" yaml:"allOf,omitempty"`
 	Not          *SchemaRef    `json:"not,omitempty" yaml:"not,omitempty"`
-	Type         string        `json:"type,omitempty" yaml:"type,omitempty"`
+	Type         *Types        `json:"type,omitempty" yaml:"type,omitempty"`
 	Title        string        `json:"title,omitempty" yaml:"title,omitempty"`
 	Format       string        `json:"format,omitempty" yaml:"format,omitempty"`
 	Description  string        `json:"description,omitempty" yaml:"description,omitempty"`
@@ -138,6 +139,75 @@ type Schema struct {
 	Discriminator        *Discriminator       `json:"discriminator,omitempty" yaml:"discriminator,omitempty"`
 }
 
+type Types []string
+
+func (types *Types) Is(typ string) bool {
+	return types != nil && len(*types) == 1 && (*types)[0] == typ
+}
+
+func (types *Types) Slice() []string {
+	if types == nil {
+		return nil
+	}
+	return *types
+}
+
+func (pTypes *Types) Includes(typ string) bool {
+	if pTypes == nil {
+		return false
+	}
+	types := *pTypes
+	for _, candidate := range types {
+		if candidate == typ {
+			return true
+		}
+	}
+	return false
+}
+
+func (types *Types) Permits(typ string) bool {
+	if types == nil {
+		return true
+	}
+	return types.Includes(typ)
+}
+
+func (pTypes *Types) MarshalJSON() ([]byte, error) {
+	x, err := pTypes.MarshalYAML()
+	if err != nil {
+		return nil, err
+	}
+	return json.Marshal(x)
+}
+
+func (pTypes *Types) MarshalYAML() (interface{}, error) {
+	if pTypes == nil {
+		return nil, nil
+	}
+	types := *pTypes
+	switch len(types) {
+	case 0:
+		return nil, nil
+	case 1:
+		return types[0], nil
+	default:
+		return []string(types), nil
+	}
+}
+
+func (types *Types) UnmarshalJSON(data []byte) error {
+	var strings []string
+	if err := json.Unmarshal(data, &strings); err != nil {
+		var s string
+		if err := json.Unmarshal(data, &s); err != nil {
+			return unmarshalError(err)
+		}
+		strings = []string{s}
+	}
+	*types = strings
+	return nil
+}
+
 type AdditionalProperties struct {
 	Has    *bool
 	Schema *SchemaRef
@@ -208,7 +278,7 @@ func (schema Schema) MarshalJSON() ([]byte, error) {
 	if x := schema.Not; x != nil {
 		m["not"] = x
 	}
-	if x := schema.Type; len(x) != 0 {
+	if x := schema.Type; x != nil {
 		m["type"] = x
 	}
 	if x := schema.Title; len(x) != 0 {
@@ -530,72 +600,72 @@ func NewAllOfSchema(schemas ...*Schema) *Schema {
 
 func NewBoolSchema() *Schema {
 	return &Schema{
-		Type: TypeBoolean,
+		Type: &Types{TypeBoolean},
 	}
 }
 
 func NewFloat64Schema() *Schema {
 	return &Schema{
-		Type: TypeNumber,
+		Type: &Types{TypeNumber},
 	}
 }
 
 func NewIntegerSchema() *Schema {
 	return &Schema{
-		Type: TypeInteger,
+		Type: &Types{TypeInteger},
 	}
 }
 
 func NewInt32Schema() *Schema {
 	return &Schema{
-		Type:   TypeInteger,
+		Type:   &Types{TypeInteger},
 		Format: "int32",
 	}
 }
 
 func NewInt64Schema() *Schema {
 	return &Schema{
-		Type:   TypeInteger,
+		Type:   &Types{TypeInteger},
 		Format: "int64",
 	}
 }
 
 func NewStringSchema() *Schema {
 	return &Schema{
-		Type: TypeString,
+		Type: &Types{TypeString},
 	}
 }
 
 func NewDateTimeSchema() *Schema {
 	return &Schema{
-		Type:   TypeString,
+		Type:   &Types{TypeString},
 		Format: "date-time",
 	}
 }
 
 func NewUUIDSchema() *Schema {
 	return &Schema{
-		Type:   TypeString,
+		Type:   &Types{TypeString},
 		Format: "uuid",
 	}
 }
 
 func NewBytesSchema() *Schema {
 	return &Schema{
-		Type:   TypeString,
+		Type:   &Types{TypeString},
 		Format: "byte",
 	}
 }
 
 func NewArraySchema() *Schema {
 	return &Schema{
-		Type: TypeArray,
+		Type: &Types{TypeArray},
 	}
 }
 
 func NewObjectSchema() *Schema {
 	return &Schema{
-		Type:       TypeObject,
+		Type:       &Types{TypeObject},
 		Properties: make(Schemas),
 	}
 }
@@ -770,9 +840,13 @@ func (schema *Schema) WithAdditionalProperties(v *Schema) *Schema {
 	return schema
 }
 
+func (schema *Schema) PermitsNull() bool {
+	return schema.Nullable || schema.Type.Includes("null")
+}
+
 // IsEmpty tells whether schema is equivalent to the empty schema `{}`.
 func (schema *Schema) IsEmpty() bool {
-	if schema.Type != "" || schema.Format != "" || len(schema.Enum) != 0 ||
+	if schema.Type != nil || schema.Format != "" || len(schema.Enum) != 0 ||
 		schema.UniqueItems || schema.ExclusiveMin || schema.ExclusiveMax ||
 		schema.Nullable || schema.ReadOnly || schema.WriteOnly || schema.AllowEmptyValue ||
 		schema.Min != nil || schema.Max != nil || schema.MultipleOf != nil ||
@@ -887,64 +961,64 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema,
 		}
 	}
 
-	schemaType := schema.Type
-	switch schemaType {
-	case "":
-	case TypeBoolean:
-	case TypeNumber:
-		if format := schema.Format; len(format) > 0 {
-			switch format {
-			case "float", "double":
-			default:
-				if validationOpts.schemaFormatValidationEnabled {
-					return stack, unsupportedFormat(format)
+	for _, schemaType := range schema.Type.Slice() {
+		switch schemaType {
+		case TypeBoolean:
+		case TypeNumber:
+			if format := schema.Format; len(format) > 0 {
+				switch format {
+				case "float", "double":
+				default:
+					if validationOpts.schemaFormatValidationEnabled {
+						return stack, unsupportedFormat(format)
+					}
 				}
 			}
-		}
-	case TypeInteger:
-		if format := schema.Format; len(format) > 0 {
-			switch format {
-			case "int32", "int64":
-			default:
-				if validationOpts.schemaFormatValidationEnabled {
-					return stack, unsupportedFormat(format)
+		case TypeInteger:
+			if format := schema.Format; len(format) > 0 {
+				switch format {
+				case "int32", "int64":
+				default:
+					if validationOpts.schemaFormatValidationEnabled {
+						return stack, unsupportedFormat(format)
+					}
 				}
 			}
-		}
-	case TypeString:
-		if format := schema.Format; len(format) > 0 {
-			switch format {
-			// Supported by OpenAPIv3.0.3:
-			// https://spec.openapis.org/oas/v3.0.3
-			case "byte", "binary", "date", "date-time", "password":
-			// In JSON Draft-07 (not validated yet though):
-			// https://json-schema.org/draft-07/json-schema-release-notes.html#formats
-			case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname":
-			case "json-pointer", "relative-json-pointer", "regex", "time":
-			// In JSON Draft 2019-09 (not validated yet though):
-			// https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary
-			case "duration", "uuid":
-			// Defined in some other specification
-			case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
-			default:
-				// Try to check for custom defined formats
-				if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
-					return stack, unsupportedFormat(format)
+		case TypeString:
+			if format := schema.Format; len(format) > 0 {
+				switch format {
+				// Supported by OpenAPIv3.0.3:
+				// https://spec.openapis.org/oas/v3.0.3
+				case "byte", "binary", "date", "date-time", "password":
+				// In JSON Draft-07 (not validated yet though):
+				// https://json-schema.org/draft-07/json-schema-release-notes.html#formats
+				case "iri", "iri-reference", "uri-template", "idn-email", "idn-hostname":
+				case "json-pointer", "relative-json-pointer", "regex", "time":
+				// In JSON Draft 2019-09 (not validated yet though):
+				// https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary
+				case "duration", "uuid":
+				// Defined in some other specification
+				case "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference":
+				default:
+					// Try to check for custom defined formats
+					if _, ok := SchemaStringFormats[format]; !ok && validationOpts.schemaFormatValidationEnabled {
+						return stack, unsupportedFormat(format)
+					}
 				}
 			}
-		}
-		if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" {
-			if _, err := schema.compilePattern(); err != nil {
-				return stack, err
+			if !validationOpts.schemaPatternValidationDisabled && schema.Pattern != "" {
+				if _, err := schema.compilePattern(); err != nil {
+					return stack, err
+				}
 			}
+		case TypeArray:
+			if schema.Items == nil {
+				return stack, errors.New("when schema type is 'array', schema 'items' must be non-null")
+			}
+		case TypeObject:
+		default:
+			return stack, fmt.Errorf("unsupported 'type' value %q", schemaType)
 		}
-	case TypeArray:
-		if schema.Items == nil {
-			return stack, errors.New("when schema type is 'array', schema 'items' must be non-null")
-		}
-	case TypeObject:
-	default:
-		return stack, fmt.Errorf("unsupported 'type' value %q", schemaType)
 	}
 
 	if ref := schema.Items; ref != nil {
@@ -1053,7 +1127,7 @@ func (schema *Schema) visitJSON(settings *schemaValidationSettings, value interf
 	case nil:
 		// Don't use VisitJSONNull, as we still want to reach 'visitXOFOperations', since
 		// those could allow for a nullable value even though this one doesn't
-		if schema.Nullable {
+		if schema.PermitsNull() {
 			return
 		}
 	case float64:
@@ -1371,7 +1445,7 @@ func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, val
 // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#data-types
 // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#schema-object
 func (schema *Schema) visitJSONNull(settings *schemaValidationSettings) (err error) {
-	if schema.Nullable {
+	if schema.PermitsNull() {
 		return
 	}
 	if settings.failfast {
@@ -1392,7 +1466,7 @@ func (schema *Schema) VisitJSONBoolean(value bool) error {
 }
 
 func (schema *Schema) visitJSONBoolean(settings *schemaValidationSettings, value bool) (err error) {
-	if schemaType := schema.Type; schemaType != "" && schemaType != TypeBoolean {
+	if !schema.Type.Permits(TypeBoolean) {
 		return schema.expectedType(settings, value)
 	}
 	return
@@ -1406,7 +1480,9 @@ func (schema *Schema) VisitJSONNumber(value float64) error {
 func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value float64) error {
 	var me MultiError
 	schemaType := schema.Type
-	if schemaType == TypeInteger {
+	requireInteger := false
+	if schemaType.Permits(TypeInteger) && !schemaType.Permits(TypeNumber) {
+		requireInteger = true
 		if bigFloat := big.NewFloat(value); !bigFloat.IsInt() {
 			if settings.failfast {
 				return errSchema
@@ -1423,12 +1499,12 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 			}
 			me = append(me, err)
 		}
-	} else if schemaType != "" && schemaType != TypeNumber {
+	} else if !(schemaType.Permits(TypeInteger) || schemaType.Permits(TypeNumber)) {
 		return schema.expectedType(settings, value)
 	}
 
 	// formats
-	if schemaType == TypeInteger && schema.Format != "" {
+	if requireInteger && schema.Format != "" {
 		formatMin := float64(0)
 		formatMax := float64(0)
 		switch schema.Format {
@@ -1568,7 +1644,7 @@ func (schema *Schema) VisitJSONString(value string) error {
 }
 
 func (schema *Schema) visitJSONString(settings *schemaValidationSettings, value string) error {
-	if schemaType := schema.Type; schemaType != "" && schemaType != TypeString {
+	if !schema.Type.Permits(TypeString) {
 		return schema.expectedType(settings, value)
 	}
 
@@ -1703,7 +1779,7 @@ func (schema *Schema) VisitJSONArray(value []interface{}) error {
 }
 
 func (schema *Schema) visitJSONArray(settings *schemaValidationSettings, value []interface{}) error {
-	if schemaType := schema.Type; schemaType != "" && schemaType != TypeArray {
+	if !schema.Type.Permits(TypeArray) {
 		return schema.expectedType(settings, value)
 	}
 
@@ -1802,7 +1878,7 @@ func (schema *Schema) VisitJSONObject(value map[string]interface{}) error {
 }
 
 func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value map[string]interface{}) error {
-	if schemaType := schema.Type; schemaType != "" && schemaType != TypeObject {
+	if !schema.Type.Permits(TypeObject) {
 		return schema.expectedType(settings, value)
 	}
 
@@ -1986,15 +2062,23 @@ func (schema *Schema) expectedType(settings *schemaValidationSettings, value int
 	}
 
 	a := "a"
-	switch schema.Type {
-	case TypeArray, TypeObject, TypeInteger:
-		a = "an"
+	var x string
+	schemaTypes := (*schema.Type)
+	if len(schemaTypes) == 1 {
+		x = schemaTypes[0]
+		switch x {
+		case TypeArray, TypeObject, TypeInteger:
+			a = "an"
+		}
+	} else {
+		a = "one of"
+		x = strings.Join(schemaTypes, ", ")
 	}
 	return &SchemaError{
 		Value:                 value,
 		Schema:                schema,
 		SchemaField:           "type",
-		Reason:                fmt.Sprintf("value must be %s %s", a, schema.Type),
+		Reason:                fmt.Sprintf("value must be %s %s", a, x),
 		customizeMessageError: settings.customizeMessageError,
 	}
 }
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 8d9dca2..60afc54 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -194,7 +194,7 @@ func defaultContentParameterDecoder(param *openapi3.Parameter, values []string)
 
 	unmarshal := func(encoded string, paramSchema *openapi3.SchemaRef) (decoded interface{}, err error) {
 		if err = json.Unmarshal([]byte(encoded), &decoded); err != nil {
-			if paramSchema != nil && paramSchema.Value.Type != "object" {
+			if paramSchema != nil && !paramSchema.Value.Type.Is("object") {
 				decoded, err = encoded, nil
 			}
 		}
@@ -315,14 +315,14 @@ func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMetho
 		return nil, found, errors.New("not implemented: decoding 'not'")
 	}
 
-	if schema.Value.Type != "" {
+	if schema.Value.Type != nil {
 		var decodeFn func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error)
-		switch schema.Value.Type {
-		case "array":
+		switch {
+		case schema.Value.Type.Is("array"):
 			decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
 				return dec.DecodeArray(param, sm, schema)
 			}
-		case "object":
+		case schema.Value.Type.Is("object"):
 			decodeFn = func(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (interface{}, bool, error) {
 				return dec.DecodeObject(param, sm, schema)
 			}
@@ -505,7 +505,7 @@ func (d *urlValuesDecoder) DecodePrimitive(param string, sm *openapi3.Serializat
 		return nil, ok, nil
 	}
 
-	if schema.Value.Type == "" && schema.Value.Pattern != "" {
+	if schema.Value.Type == nil && schema.Value.Pattern != "" {
 		return values[0], ok, nil
 	}
 	val, err := parsePrimitive(values[0], schema)
@@ -693,7 +693,7 @@ func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.Serialization
 			break
 		}
 
-		if schema.Value.Type == "array" || schema.Value.Type == "object" {
+		if schema.Value.Type.Permits("array") || schema.Value.Type.Permits("object") {
 			for k := range props {
 				path := strings.Split(k, urlDecoderDelimiter)
 				if _, ok := deepGet(val, path...); ok {
@@ -915,8 +915,8 @@ func findNestedSchema(parentSchema *openapi3.SchemaRef, keys []string) (*openapi
 func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string]interface{}, error) {
 	obj := make(map[string]interface{})
 	for propName, propSchema := range schema.Value.Properties {
-		switch propSchema.Value.Type {
-		case "array":
+		switch {
+		case propSchema.Value.Type.Is("array"):
 			vals := strings.Split(props[propName], urlDecoderDelimiter)
 			for _, v := range vals {
 				_, err := parsePrimitive(v, propSchema.Value.Items)
@@ -925,7 +925,7 @@ func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string
 				}
 			}
 			obj[propName] = vals
-		case "object":
+		case propSchema.Value.Type.Is("object"):
 			for prop := range props {
 				if !strings.HasPrefix(prop, propName+urlDecoderDelimiter) {
 					continue
@@ -935,7 +935,7 @@ func makeObject(props map[string]string, schema *openapi3.SchemaRef) (map[string
 				if err != nil {
 					return nil, &ParseError{path: pathFromKeys(mapKeys), Reason: err.Error()}
 				}
-				if nestedSchema.Value.Type == "array" {
+				if nestedSchema.Value.Type.Permits("array") {
 					vals := strings.Split(props[prop], urlDecoderDelimiter)
 					for _, v := range vals {
 						_, err := parsePrimitive(v, nestedSchema.Value.Items)
@@ -1005,40 +1005,50 @@ func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]interface{}, err
 // parsePrimitive returns a value that is created by parsing a source string to a primitive type
 // that is specified by a schema. The function returns nil when the source string is empty.
 // The function panics when a schema has a non-primitive type.
-func parsePrimitive(raw string, schema *openapi3.SchemaRef) (interface{}, error) {
+func parsePrimitive(raw string, schema *openapi3.SchemaRef) (v interface{}, err error) {
 	if raw == "" {
 		return nil, nil
 	}
-	switch schema.Value.Type {
+	for _, typ := range schema.Value.Type.Slice() {
+		v, err = parsePrimitiveCase(raw, schema, typ)
+		if err == nil {
+			return
+		}
+	}
+	return
+}
+
+func parsePrimitiveCase(raw string, schema *openapi3.SchemaRef, typ string) (interface{}, error) {
+	switch typ {
 	case "integer":
 		if schema.Value.Format == "int32" {
 			v, err := strconv.ParseInt(raw, 0, 32)
 			if err != nil {
-				return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
+				return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
 			}
 			return int32(v), nil
 		}
 		v, err := strconv.ParseInt(raw, 0, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "number":
 		v, err := strconv.ParseFloat(raw, 64)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "boolean":
 		v, err := strconv.ParseBool(raw)
 		if err != nil {
-			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + schema.Value.Type, Cause: err.(*strconv.NumError).Err}
+			return nil, &ParseError{Kind: KindInvalidFormat, Value: raw, Reason: "an invalid " + typ, Cause: err.(*strconv.NumError).Err}
 		}
 		return v, nil
 	case "string":
 		return raw, nil
 	default:
-		return nil, &ParseError{Kind: KindOther, Value: raw, Reason: "schema has non primitive type " + schema.Value.Type}
+		return nil, &ParseError{Kind: KindOther, Value: raw, Reason: "schema has non primitive type " + typ}
 	}
 }
 
@@ -1165,16 +1175,17 @@ func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.
 	// Validate schema of request body.
 	// By the OpenAPI 3 specification request body's schema must have type "object".
 	// Properties of the schema describes individual parts of request body.
-	if schema.Value.Type != "object" {
+	if !schema.Value.Type.Is("object") {
 		return nil, errors.New("unsupported schema of request body")
 	}
 	for propName, propSchema := range schema.Value.Properties {
-		switch propSchema.Value.Type {
-		case "object":
+		propType := propSchema.Value.Type
+		switch {
+		case propType.Is("object"):
 			return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
-		case "array":
+		case propType.Is("array"):
 			items := propSchema.Value.Items.Value
-			if items.Type != "string" && items.Type != "integer" && items.Type != "number" && items.Type != "boolean" {
+			if !(items.Type.Is("string") || items.Type.Is("integer") || items.Type.Is("number") || items.Type.Is("boolean")) {
 				return nil, fmt.Errorf("unsupported schema of request body's property %q", propName)
 			}
 		}
@@ -1246,7 +1257,7 @@ func decodeProperty(dec valueDecoder, name string, prop *openapi3.SchemaRef, enc
 }
 
 func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (interface{}, error) {
-	if schema.Value.Type != "object" {
+	if !schema.Value.Type.Is("object") {
 		return nil, errors.New("unsupported schema of request body")
 	}
 
@@ -1309,7 +1320,7 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 					return nil, &ParseError{Kind: KindOther, Cause: fmt.Errorf("part %s: undefined", name)}
 				}
 			}
-			if valueSchema.Value.Type == "array" {
+			if valueSchema.Value.Type.Is("array") {
 				valueSchema = valueSchema.Value.Items
 			}
 		}
@@ -1354,7 +1365,7 @@ func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.S
 		if len(vv) == 0 {
 			continue
 		}
-		if prop.Value.Type == "array" {
+		if prop.Value.Type.Is("array") {
 			obj[name] = vv
 		} else {
 			obj[name] = vv[0]
diff --git a/openapi3gen/openapi3gen.go b/openapi3gen/openapi3gen.go
index eccabd8..95805ee 100644
--- a/openapi3gen/openapi3gen.go
+++ b/openapi3gen/openapi3gen.go
@@ -200,63 +200,63 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
 		return nil, nil // ignore
 
 	case reflect.Bool:
-		schema.Type = "boolean"
+		schema.Type = &openapi3.Types{"boolean"}
 
 	case reflect.Int:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 	case reflect.Int8:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &minInt8
 		schema.Max = &maxInt8
 	case reflect.Int16:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &minInt16
 		schema.Max = &maxInt16
 	case reflect.Int32:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Format = "int32"
 	case reflect.Int64:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Format = "int64"
 	case reflect.Uint:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &zeroInt
 	case reflect.Uint8:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &zeroInt
 		schema.Max = &maxUint8
 	case reflect.Uint16:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &zeroInt
 		schema.Max = &maxUint16
 	case reflect.Uint32:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &zeroInt
 		schema.Max = &maxUint32
 	case reflect.Uint64:
-		schema.Type = "integer"
+		schema.Type = &openapi3.Types{"integer"}
 		schema.Min = &zeroInt
 		schema.Max = &maxUint64
 
 	case reflect.Float32:
-		schema.Type = "number"
+		schema.Type = &openapi3.Types{"number"}
 		schema.Format = "float"
 	case reflect.Float64:
-		schema.Type = "number"
+		schema.Type = &openapi3.Types{"number"}
 		schema.Format = "double"
 
 	case reflect.String:
-		schema.Type = "string"
+		schema.Type = &openapi3.Types{"string"}
 
 	case reflect.Slice:
 		if t.Elem().Kind() == reflect.Uint8 {
 			if t == rawMessageType {
 				return &openapi3.SchemaRef{Value: schema}, nil
 			}
-			schema.Type = "string"
+			schema.Type = &openapi3.Types{"string"}
 			schema.Format = "byte"
 		} else {
-			schema.Type = "array"
+			schema.Type = &openapi3.Types{"array"}
 			items, err := g.generateSchemaRefFor(parents, t.Elem(), name, tag)
 			if err != nil {
 				if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
@@ -272,7 +272,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
 		}
 
 	case reflect.Map:
-		schema.Type = "object"
+		schema.Type = &openapi3.Types{"object"}
 		additionalProperties, err := g.generateSchemaRefFor(parents, t.Elem(), name, tag)
 		if err != nil {
 			if _, ok := err.(*CycleError); ok && !g.opts.throwErrorOnCycle {
@@ -288,7 +288,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
 
 	case reflect.Struct:
 		if t == timeType {
-			schema.Type = "string"
+			schema.Type = &openapi3.Types{"string"}
 			schema.Format = "date-time"
 		} else {
 			for _, fieldInfo := range typeInfo.Fields {
@@ -344,7 +344,7 @@ func (g *Generator) generateWithoutSaving(parents []*theTypeInfo, t reflect.Type
 
 			// Object only if it has properties
 			if schema.Properties != nil {
-				schema.Type = "object"
+				schema.Type = &openapi3.Types{"object"}
 			}
 		}
 	}
@@ -366,13 +366,13 @@ func (g *Generator) generateCycleSchemaRef(t reflect.Type, schema *openapi3.Sche
 	case reflect.Slice:
 		ref := g.generateCycleSchemaRef(t.Elem(), schema)
 		sliceSchema := openapi3.NewSchema()
-		sliceSchema.Type = "array"
+		sliceSchema.Type = &openapi3.Types{"array"}
 		sliceSchema.Items = ref
 		return openapi3.NewSchemaRef("", sliceSchema)
 	case reflect.Map:
 		ref := g.generateCycleSchemaRef(t.Elem(), schema)
 		mapSchema := openapi3.NewSchema()
-		mapSchema.Type = "object"
+		mapSchema.Type = &openapi3.Types{"object"}
 		mapSchema.AdditionalProperties = openapi3.AdditionalProperties{Schema: ref}
 		return openapi3.NewSchemaRef("", mapSchema)
 	default:


Test patch:
diff --git a/openapi3/issue301_test.go b/openapi3/issue301_test.go
index ea14c3a..cf5350d 100644
--- a/openapi3/issue301_test.go
+++ b/openapi3/issue301_test.go
@@ -16,7 +16,7 @@ func TestIssue301(t *testing.T) {
 	err = doc.Validate(sl.Context)
 	require.NoError(t, err)
 
-	require.Equal(t, "object", doc.
+	require.Equal(t, &Types{"object"}, doc.
 		Paths.Value("/trans").
 		Post.Callbacks["transactionCallback"].Value.
 		Value("http://notificationServer.com?transactionId={$request.body#/id}&email={$request.body#/email}").
@@ -24,7 +24,7 @@ func TestIssue301(t *testing.T) {
 		Content["application/json"].Schema.Value.
 		Type)
 
-	require.Equal(t, "boolean", doc.
+	require.Equal(t, &Types{"boolean"}, doc.
 		Paths.Value("/other").
 		Post.Callbacks["myEvent"].Value.
 		Value("{$request.query.queryUrl}").
diff --git a/openapi3/issue341_test.go b/openapi3/issue341_test.go
index 2ceb459..6154591 100644
--- a/openapi3/issue341_test.go
+++ b/openapi3/issue341_test.go
@@ -34,7 +34,7 @@ func TestIssue341(t *testing.T) {
 	}
 }`, string(bs))
 
-	require.Equal(t, "string", doc.
+	require.Equal(t, &Types{"string"}, doc.
 		Paths.Value("/testpath").
 		Get.
 		Responses.Value("200").Value.
diff --git a/openapi3/issue344_test.go b/openapi3/issue344_test.go
index 44ba2b7..03fc1b2 100644
--- a/openapi3/issue344_test.go
+++ b/openapi3/issue344_test.go
@@ -16,5 +16,5 @@ func TestIssue344(t *testing.T) {
 	err = doc.Validate(sl.Context)
 	require.NoError(t, err)
 
-	require.Equal(t, "string", doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
+	require.Equal(t, &Types{"string"}, doc.Components.Schemas["Test"].Value.Properties["test"].Value.Properties["name"].Value.Type)
 }
diff --git a/openapi3/issue376_test.go b/openapi3/issue376_test.go
index fd92860..01c0a18 100644
--- a/openapi3/issue376_test.go
+++ b/openapi3/issue376_test.go
@@ -40,7 +40,7 @@ info:
 	require.Equal(t, 2, len(doc.Components.Schemas))
 	require.Equal(t, 0, doc.Paths.Len())
 
-	require.Equal(t, "string", doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
+	require.Equal(t, &Types{"string"}, doc.Components.Schemas["schema2"].Value.Properties["prop"].Value.Type)
 }
 
 func TestExclusiveValuesOfValuesAdditionalProperties(t *testing.T) {
diff --git a/openapi3/issue495_test.go b/openapi3/issue495_test.go
index 0ed52df..ee4cad5 100644
--- a/openapi3/issue495_test.go
+++ b/openapi3/issue495_test.go
@@ -81,7 +81,7 @@ paths:
 	err = doc.Validate(sl.Context)
 	require.NoError(t, err)
 
-	require.Equal(t, &Schema{Type: "object"}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
+	require.Equal(t, &Schema{Type: &Types{"object"}}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
 }
 
 func TestIssue495WithDraft04(t *testing.T) {
diff --git a/openapi3/issue638_test.go b/openapi3/issue638_test.go
index 1db8a6f..967195f 100644
--- a/openapi3/issue638_test.go
+++ b/openapi3/issue638_test.go
@@ -16,6 +16,6 @@ func TestIssue638(t *testing.T) {
 		// testdata/issue638/test1.yaml             : reproduce
 		doc, err := loader.LoadFromFile("testdata/issue638/test1.yaml")
 		require.NoError(t, err)
-		require.Equal(t, "int", doc.Components.Schemas["test1d"].Value.Type)
+		require.Equal(t, &Types{"int"}, doc.Components.Schemas["test1d"].Value.Type)
 	}
 }
diff --git a/openapi3/issue652_test.go b/openapi3/issue652_test.go
index f36e920..4036b48 100644
--- a/openapi3/issue652_test.go
+++ b/openapi3/issue652_test.go
@@ -24,6 +24,6 @@ func TestIssue652(t *testing.T) {
 
 		schema := spec.Components.Schemas[schemaName]
 		assert.Equal(t, schema.Ref, "../definitions.yml#/components/schemas/TestSchema")
-		assert.Equal(t, schema.Value.Type, "string")
+		assert.Equal(t, schema.Value.Type, &openapi3.Types{"string"})
 	})
 }
diff --git a/openapi3/issue689_test.go b/openapi3/issue689_test.go
index cafbadf..44058a8 100644
--- a/openapi3/issue689_test.go
+++ b/openapi3/issue689_test.go
@@ -22,7 +22,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "read-only property succeeds when read-only validation is disabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", ReadOnly: true}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
 			value: map[string]interface{}{"foo": true},
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest(),
@@ -32,7 +32,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "non read-only property succeeds when read-only validation is disabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", ReadOnly: false}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest()},
 			value:    map[string]interface{}{"foo": true},
@@ -41,7 +41,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "read-only property fails when read-only validation is enabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", ReadOnly: true}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: true}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest()},
 			value:    map[string]interface{}{"foo": true},
@@ -50,7 +50,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "non read-only property succeeds when read-only validation is enabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", ReadOnly: false}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, ReadOnly: false}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest()},
 			value:    map[string]interface{}{"foo": true},
@@ -60,7 +60,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "write-only property succeeds when write-only validation is disabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", WriteOnly: true}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
 			value: map[string]interface{}{"foo": true},
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsResponse(),
@@ -70,7 +70,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "non write-only property succeeds when write-only validation is disabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", WriteOnly: false}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsResponse()},
 			value:    map[string]interface{}{"foo": true},
@@ -79,7 +79,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "write-only property fails when write-only validation is enabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", WriteOnly: true}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: true}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsResponse()},
 			value:    map[string]interface{}{"foo": true},
@@ -88,7 +88,7 @@ func TestIssue689(t *testing.T) {
 		{
 			name: "non write-only property succeeds when write-only validation is enabled",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", WriteOnly: false}}),
+				"foo": {Type: &openapi3.Types{"boolean"}, WriteOnly: false}}),
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsResponse()},
 			value:    map[string]interface{}{"foo": true},
diff --git a/openapi3/issue767_test.go b/openapi3/issue767_test.go
index d498877..55fb52f 100644
--- a/openapi3/issue767_test.go
+++ b/openapi3/issue767_test.go
@@ -21,7 +21,7 @@ func TestIssue767(t *testing.T) {
 		{
 			name: "default values disabled should fail with minProps 1",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
+				"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
 			value: map[string]interface{}{},
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest(),
@@ -31,7 +31,7 @@ func TestIssue767(t *testing.T) {
 		{
 			name: "default values enabled should pass with minProps 1",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
+				"foo": {Type: &openapi3.Types{"boolean"}, Default: true}}).WithMinProperties(1),
 			value: map[string]interface{}{},
 			opts: []openapi3.SchemaValidationOption{
 				openapi3.VisitAsRequest(),
@@ -42,8 +42,8 @@ func TestIssue767(t *testing.T) {
 		{
 			name: "default values enabled should pass with minProps 2",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", Default: true},
-				"bar": {Type: "boolean"},
+				"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
+				"bar": {Type: &openapi3.Types{"boolean"}},
 			}).WithMinProperties(2),
 			value: map[string]interface{}{"bar": false},
 			opts: []openapi3.SchemaValidationOption{
@@ -55,8 +55,8 @@ func TestIssue767(t *testing.T) {
 		{
 			name: "default values enabled should fail with maxProps 1",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", Default: true},
-				"bar": {Type: "boolean"},
+				"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
+				"bar": {Type: &openapi3.Types{"boolean"}},
 			}).WithMaxProperties(1),
 			value: map[string]interface{}{"bar": false},
 			opts: []openapi3.SchemaValidationOption{
@@ -68,8 +68,8 @@ func TestIssue767(t *testing.T) {
 		{
 			name: "default values disabled should pass with maxProps 1",
 			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
-				"foo": {Type: "boolean", Default: true},
-				"bar": {Type: "boolean"},
+				"foo": {Type: &openapi3.Types{"boolean"}, Default: true},
+				"bar": {Type: &openapi3.Types{"boolean"}},
 			}).WithMaxProperties(1),
 			value: map[string]interface{}{"bar": false},
 			opts: []openapi3.SchemaValidationOption{
diff --git a/openapi3/load_cicular_ref_with_external_file_test.go b/openapi3/load_cicular_ref_with_external_file_test.go
index 9bcaaf7..7a99e76 100644
--- a/openapi3/load_cicular_ref_with_external_file_test.go
+++ b/openapi3/load_cicular_ref_with_external_file_test.go
@@ -35,7 +35,7 @@ func TestLoadCircularRefFromFile(t *testing.T) {
 					Value: &openapi3.Schema{
 						Properties: map[string]*openapi3.SchemaRef{
 							"id": {
-								Value: &openapi3.Schema{Type: "string"}},
+								Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
 						},
 					},
 				},
diff --git a/openapi3/load_with_go_embed_test.go b/openapi3/load_with_go_embed_test.go
index 3b77e5f..27b46b2 100644
--- a/openapi3/load_with_go_embed_test.go
+++ b/openapi3/load_with_go_embed_test.go
@@ -40,5 +40,5 @@ func Example() {
 		Properties["bar"].Value.
 		Type,
 	)
-	// Output: string
+	// Output: &[string]
 }
diff --git a/openapi3/loader_issue212_test.go b/openapi3/loader_issue212_test.go
index 252d0d2..4072155 100644
--- a/openapi3/loader_issue212_test.go
+++ b/openapi3/loader_issue212_test.go
@@ -79,11 +79,11 @@ components:
 	require.NoError(t, err)
 
 	expected, err := json.Marshal(&Schema{
-		Type:     "object",
+		Type:     &Types{"object"},
 		Required: []string{"id", "uri"},
 		Properties: Schemas{
-			"id":  {Value: &Schema{Type: "string"}},
-			"uri": {Value: &Schema{Type: "string"}},
+			"id":  {Value: &Schema{Type: &Types{"string"}}},
+			"uri": {Value: &Schema{Type: &Types{"string"}}},
 		},
 	},
 	)
diff --git a/openapi3/loader_issue220_test.go b/openapi3/loader_issue220_test.go
index 0b25697..1b0efd6 100644
--- a/openapi3/loader_issue220_test.go
+++ b/openapi3/loader_issue220_test.go
@@ -22,7 +22,7 @@ func TestIssue220(t *testing.T) {
 		err = doc.Validate(loader.Context)
 		require.NoError(t, err)
 
-		require.Equal(t, "integer", doc.
+		require.Equal(t, &Types{"integer"}, doc.
 			Paths.Value("/foo").
 			Get.Responses.Value("200").Value.
 			Content["application/json"].
diff --git a/openapi3/loader_outside_refs_test.go b/openapi3/loader_outside_refs_test.go
index 3f2cf7c..7300c2c 100644
--- a/openapi3/loader_outside_refs_test.go
+++ b/openapi3/loader_outside_refs_test.go
@@ -16,7 +16,7 @@ func TestLoadOutsideRefs(t *testing.T) {
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
 
-	require.Equal(t, "string", doc.
+	require.Equal(t, &Types{"string"}, doc.
 		Paths.Value("/service").
 		Get.
 		Responses.Value("200").Value.
diff --git a/openapi3/loader_recursive_ref_test.go b/openapi3/loader_recursive_ref_test.go
index 85655ef..adf91d2 100644
--- a/openapi3/loader_recursive_ref_test.go
+++ b/openapi3/loader_recursive_ref_test.go
@@ -52,7 +52,7 @@ components:
 	require.NoError(t, err)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	require.Equal(t, "object", doc.Components.
+	require.Equal(t, &Types{"object"}, doc.Components.
 		Schemas["Complex"].
 		Value.Properties["parent"].
 		Value.Properties["parent"].
diff --git a/openapi3/loader_relative_refs_test.go b/openapi3/loader_relative_refs_test.go
index 2ebd91b..cca44ba 100644
--- a/openapi3/loader_relative_refs_test.go
+++ b/openapi3/loader_relative_refs_test.go
@@ -27,7 +27,7 @@ var refTestDataEntries = []refTestDataEntry{
 		contentTemplate: externalSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
 			require.NotNil(t, doc.Components.Schemas["TestSchema"].Value.Type)
-			require.Equal(t, "string", doc.Components.Schemas["TestSchema"].Value.Type)
+			require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
 		},
 	},
 	{
@@ -115,7 +115,7 @@ var refTestDataEntries = []refTestDataEntry{
 		contentTemplate: externalPathOperationParameterSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
 			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, &Types{"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)
 		},
 	},
@@ -126,7 +126,7 @@ var refTestDataEntries = []refTestDataEntry{
 		testFunc: func(t *testing.T, doc *T) {
 			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)
+			require.Equal(t, &Types{"string"}, schemaRef.Value.Type)
 		},
 	},
 
@@ -143,7 +143,7 @@ var refTestDataEntries = []refTestDataEntry{
 		contentTemplate: externalPathOperationRequestBodyContentSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
 			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)
+			require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Post.RequestBody.Value.Content["application/json"].Schema.Value.Type)
 		},
 	},
 	{
@@ -163,7 +163,7 @@ var refTestDataEntries = []refTestDataEntry{
 			require.NotNil(t, doc.Paths.Value("/test").Post.Responses.Default().Value)
 			desc := "testdescription"
 			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)
+			require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Post.Responses.Default().Value.Content["application/json"].Schema.Value.Type)
 		},
 	},
 	{
@@ -171,7 +171,7 @@ var refTestDataEntries = []refTestDataEntry{
 		contentTemplate: externalComponentHeaderSchemaRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
 			require.NotNil(t, doc.Components.Headers["TestHeader"].Value)
-			require.Equal(t, "string", doc.Components.Headers["TestHeader"].Value.Schema.Value.Type)
+			require.Equal(t, &Types{"string"}, doc.Components.Headers["TestHeader"].Value.Schema.Value.Type)
 		},
 	},
 	{
@@ -725,7 +725,7 @@ var relativeDocRefsTestDataEntries = []refTestDataEntry{
 		contentTemplate: relativeSchemaDocsRefTemplate,
 		testFunc: func(t *testing.T, doc *T) {
 			require.NotNil(t, doc.Components.Schemas["TestSchema"].Value.Type)
-			require.Equal(t, "string", doc.Components.Schemas["TestSchema"].Value.Type)
+			require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
 		},
 	},
 	{
@@ -927,7 +927,7 @@ func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
 	require.Equal(t, "example request", nestedDirPath.Patch.RequestBody.Value.Description)
 
 	// check response schema and example
-	require.Equal(t, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type, "string")
+	require.Equal(t, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type, &Types{"string"})
 	expectedExample := "hello"
 	require.Equal(t, expectedExample, nestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Examples["CustomTestExample"].Value.Value)
 
@@ -947,6 +947,6 @@ func TestLoadSpecWithRelativeDocumentRefs2(t *testing.T) {
 	require.Equal(t, "example request", moreNestedDirPath.Patch.RequestBody.Value.Description)
 
 	// check response schema and example
-	require.Equal(t, "string", moreNestedDirPath.Patch.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
+	require.Equal(t, &Types{"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 18c28f1..7dcb3ed 100644
--- a/openapi3/loader_test.go
+++ b/openapi3/loader_test.go
@@ -290,7 +290,7 @@ func TestLoadFromRemoteURL(t *testing.T) {
 	doc, err := loader.LoadFromURI(url)
 	require.NoError(t, err)
 
-	require.Equal(t, "string", doc.Components.Schemas["TestSchema"].Value.Type)
+	require.Equal(t, &Types{"string"}, doc.Components.Schemas["TestSchema"].Value.Type)
 }
 
 func TestLoadWithReferenceInReference(t *testing.T) {
@@ -301,7 +301,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.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+	require.Equal(t, &Types{"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) {
@@ -312,7 +312,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.Value("/api/test/ref/in/ref").Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties["definition_reference"].Value.Type)
+	require.Equal(t, &Types{"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) {
@@ -323,7 +323,7 @@ func TestLoadWithRecursiveReferenceInReferenceInLocalReference(t *testing.T) {
 	require.NotNil(t, doc)
 	err = doc.Validate(loader.Context)
 	require.NoError(t, err)
-	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, &Types{"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)
 }
 
@@ -463,7 +463,7 @@ func TestLoadYamlFileWithExternalPathRef(t *testing.T) {
 	require.NoError(t, err)
 
 	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)
+	require.Equal(t, &Types{"string"}, doc.Paths.Value("/test").Get.Responses.Value("200").Value.Content["application/json"].Schema.Value.Type)
 }
 
 func TestResolveResponseLinkRef(t *testing.T) {
diff --git a/openapi3/refs_test.go b/openapi3/refs_test.go
index e328c33..8a12c33 100644
--- a/openapi3/refs_test.go
+++ b/openapi3/refs_test.go
@@ -302,7 +302,7 @@ components:
 	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
-	require.Equal(t, "integer", v.(*Schema).Type)
+	require.Equal(t, &Types{"integer"}, v.(*Schema).Type)
 
 	ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/0")
 	require.NoError(t, err)
@@ -311,7 +311,7 @@ components:
 	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
-	require.Equal(t, "string", v.(*Schema).Type)
+	require.Equal(t, &Types{"string"}, v.(*Schema).Type)
 
 	ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/1")
 	require.NoError(t, err)
@@ -320,7 +320,7 @@ components:
 	require.NotNil(t, v)
 	require.IsType(t, &Schema{}, v)
 	require.Equal(t, reflect.Ptr, kind)
-	require.Equal(t, "integer", v.(*Schema).Type)
+	require.Equal(t, &Types{"integer"}, v.(*Schema).Type)
 
 	ptr, err = jsonpointer.New("/components/schemas/OneOfTest/oneOf/5")
 	require.NoError(t, err)
diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go
index fe39139..b9f2dbf 100644
--- a/openapi3/schema_test.go
+++ b/openapi3/schema_test.go
@@ -153,6 +153,40 @@ var schemaExamples = []schemaExample{
 		},
 	},
 
+	{
+		Title: "PRIMITIVES WITHOUT NULL",
+		Schema: &Schema{
+			Type: &Types{TypeString, TypeBoolean},
+		},
+		AllValid: []interface{}{
+			"",
+			"xyz",
+			true,
+			false,
+		},
+		AllInvalid: []interface{}{
+			1,
+			nil,
+		},
+	},
+
+	{
+		Title: "PRIMITIVES WITH NULL",
+		Schema: &Schema{
+			Type: &Types{TypeNumber, TypeNull},
+		},
+		AllValid: []interface{}{
+			0,
+			1,
+			2.3,
+			nil,
+		},
+		AllInvalid: []interface{}{
+			"x",
+			[]interface{}{},
+		},
+	},
+
 	{
 		Title: "NULLABLE ANYOF",
 		Schema: NewAnyOfSchema(
@@ -509,7 +543,7 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "ARRAY",
 		Schema: &Schema{
-			Type:        "array",
+			Type:        &Types{"array"},
 			MinItems:    2,
 			MaxItems:    Uint64Ptr(3),
 			UniqueItems: true,
@@ -549,10 +583,10 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "ARRAY : items format 'object'",
 		Schema: &Schema{
-			Type:        "array",
+			Type:        &Types{"array"},
 			UniqueItems: true,
 			Items: (&Schema{
-				Type: "object",
+				Type: &Types{"object"},
 				Properties: Schemas{
 					"key1": NewFloat64Schema().NewRef(),
 				},
@@ -606,13 +640,13 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "ARRAY : items format 'object' and object with a property of array type ",
 		Schema: &Schema{
-			Type:        "array",
+			Type:        &Types{"array"},
 			UniqueItems: true,
 			Items: (&Schema{
-				Type: "object",
+				Type: &Types{"object"},
 				Properties: Schemas{
 					"key1": (&Schema{
-						Type:        "array",
+						Type:        &Types{"array"},
 						UniqueItems: true,
 						Items:       NewFloat64Schema().NewRef(),
 					}).NewRef(),
@@ -692,10 +726,10 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "ARRAY : items format 'array'",
 		Schema: &Schema{
-			Type:        "array",
+			Type:        &Types{"array"},
 			UniqueItems: true,
 			Items: (&Schema{
-				Type:        "array",
+				Type:        &Types{"array"},
 				UniqueItems: true,
 				Items:       NewFloat64Schema().NewRef(),
 			}).NewRef(),
@@ -736,13 +770,13 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "ARRAY : items format 'array' and array with object type items",
 		Schema: &Schema{
-			Type:        "array",
+			Type:        &Types{"array"},
 			UniqueItems: true,
 			Items: (&Schema{
-				Type:        "array",
+				Type:        &Types{"array"},
 				UniqueItems: true,
 				Items: (&Schema{
-					Type: "object",
+					Type: &Types{"object"},
 					Properties: Schemas{
 						"key1": NewFloat64Schema().NewRef(),
 					},
@@ -840,7 +874,7 @@ var schemaExamples = []schemaExample{
 	{
 		Title: "OBJECT",
 		Schema: &Schema{
-			Type:     "object",
+			Type:     &Types{"object"},
 			MaxProps: Uint64Ptr(2),
 			Properties: Schemas{
 				"numberProperty": NewFloat64Schema().NewRef(),
@@ -884,10 +918,10 @@ var schemaExamples = []schemaExample{
 	},
 	{
 		Schema: &Schema{
-			Type: "object",
+			Type: &Types{"object"},
 			AdditionalProperties: AdditionalProperties{Schema: &SchemaRef{
 				Value: &Schema{
-					Type: "number",
+					Type: &Types{"number"},
 				},
 			}},
 		},
@@ -912,7 +946,7 @@ var schemaExamples = []schemaExample{
 	},
 	{
 		Schema: &Schema{
-			Type:                 "object",
+			Type:                 &Types{"object"},
 			AdditionalProperties: AdditionalProperties{Has: BoolPtr(true)},
 		},
 		Serialization: map[string]interface{}{
@@ -1399,7 +1433,7 @@ components:
 func TestValidationFailsOnInvalidPattern(t *testing.T) {
 	schema := Schema{
 		Pattern: "[",
-		Type:    "string",
+		Type:    &Types{"string"},
 	}
 
 	err := schema.Validate(context.Background())
@@ -1450,7 +1484,7 @@ enum:
 
 func TestIssue751(t *testing.T) {
 	schema := &Schema{
-		Type:        "array",
+		Type:        &Types{"array"},
 		UniqueItems: true,
 		Items:       NewStringSchema().NewRef(),
 	}
diff --git a/openapi3/unique_items_checker_test.go b/openapi3/unique_items_checker_test.go
index e359aef..3dd7230 100644
--- a/openapi3/unique_items_checker_test.go
+++ b/openapi3/unique_items_checker_test.go
@@ -11,7 +11,7 @@ import (
 func TestRegisterArrayUniqueItemsChecker(t *testing.T) {
 	var (
 		schema = openapi3.Schema{
-			Type:        "array",
+			Type:        &openapi3.Types{"array"},
 			UniqueItems: true,
 			Items:       openapi3.NewStringSchema().NewRef(),
 		}
diff --git a/openapi3filter/req_resp_decoder_test.go b/openapi3filter/req_resp_decoder_test.go
index 334af84..7e5f813 100644
--- a/openapi3filter/req_resp_decoder_test.go
+++ b/openapi3filter/req_resp_decoder_test.go
@@ -25,10 +25,10 @@ func TestDecodeParameter(t *testing.T) {
 		explode   = openapi3.BoolPtr(true)
 		noExplode = openapi3.BoolPtr(false)
 		arrayOf   = func(items *openapi3.SchemaRef) *openapi3.SchemaRef {
-			return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "array", Items: items}}
+			return &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"array"}, Items: items}}
 		}
 		objectOf = func(args ...interface{}) *openapi3.SchemaRef {
-			s := &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "object", Properties: make(map[string]*openapi3.SchemaRef)}}
+			s := &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, Properties: make(map[string]*openapi3.SchemaRef)}}
 			if len(args)%2 != 0 {
 				panic("invalid arguments. must be an even number of arguments")
 			}
@@ -40,12 +40,12 @@ func TestDecodeParameter(t *testing.T) {
 			return s
 		}
 
-		integerSchema                          = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "integer"}}
-		numberSchema                           = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "number"}}
-		booleanSchema                          = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "boolean"}}
-		stringSchema                           = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "string"}}
-		additionalPropertiesObjectStringSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "object", AdditionalProperties: openapi3.AdditionalProperties{Schema: stringSchema}}}
-		additionalPropertiesObjectBoolSchema   = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: "object", AdditionalProperties: openapi3.AdditionalProperties{Schema: booleanSchema}}}
+		integerSchema                          = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"integer"}}}
+		numberSchema                           = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"number"}}}
+		booleanSchema                          = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"boolean"}}}
+		stringSchema                           = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}
+		additionalPropertiesObjectStringSchema = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, AdditionalProperties: openapi3.AdditionalProperties{Schema: stringSchema}}}
+		additionalPropertiesObjectBoolSchema   = &openapi3.SchemaRef{Value: &openapi3.Schema{Type: &openapi3.Types{"object"}, AdditionalProperties: openapi3.AdditionalProperties{Schema: booleanSchema}}}
 		allofSchema                            = &openapi3.SchemaRef{
 			Value: &openapi3.Schema{
 				AllOf: []*openapi3.SchemaRef{
diff --git a/openapi3gen/openapi3gen_test.go b/openapi3gen/openapi3gen_test.go
index 553affc..daea9ff 100644
--- a/openapi3gen/openapi3gen_test.go
+++ b/openapi3gen/openapi3gen_test.go
@@ -216,11 +216,11 @@ func TestExportedNonTagged(t *testing.T) {
 	schemaRef, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields())
 	require.NoError(t, err)
 	require.Equal(t, &openapi3.SchemaRef{Value: &openapi3.Schema{
-		Type: "object",
+		Type: &openapi3.Types{"object"},
 		Properties: map[string]*openapi3.SchemaRef{
-			"A":           {Value: &openapi3.Schema{Type: "string"}},
-			"another":     {Value: &openapi3.Schema{Type: "string"}},
-			"even_a_yaml": {Value: &openapi3.Schema{Type: "string"}},
+			"A":           {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
+			"another":     {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
+			"even_a_yaml": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
 		}}}, schemaRef)
 }
 
@@ -383,11 +383,11 @@ func TestCyclicReferences(t *testing.T) {
 	require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["FieldCycle"].Ref)
 
 	require.NotNil(t, schemaRef.Value.Properties["SliceCycle"])
-	require.Equal(t, "array", schemaRef.Value.Properties["SliceCycle"].Value.Type)
+	require.Equal(t, &openapi3.Types{"array"}, schemaRef.Value.Properties["SliceCycle"].Value.Type)
 	require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["SliceCycle"].Value.Items.Ref)
 
 	require.NotNil(t, schemaRef.Value.Properties["MapCycle"])
-	require.Equal(t, "object", schemaRef.Value.Properties["MapCycle"].Value.Type)
+	require.Equal(t, &openapi3.Types{"object"}, schemaRef.Value.Properties["MapCycle"].Value.Type)
 	require.Equal(t, "#/components/schemas/ObjectDiff", schemaRef.Value.Properties["MapCycle"].Value.AdditionalProperties.Schema.Ref)
 }
 
@@ -510,9 +510,9 @@ func TestSchemaCustomizerExcludeSchema(t *testing.T) {
 	schema, err := openapi3gen.NewSchemaRefForValue(&Bla{}, nil, openapi3gen.UseAllExportedFields(), customizer)
 	require.NoError(t, err)
 	require.Equal(t, &openapi3.SchemaRef{Value: &openapi3.Schema{
-		Type: "object",
+		Type: &openapi3.Types{"object"},
 		Properties: map[string]*openapi3.SchemaRef{
-			"Str": {Value: &openapi3.Schema{Type: "string"}},
+			"Str": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
 		}}}, schema)
 
 	customizer = openapi3gen.SchemaCustomizer(func(name string, ft reflect.Type, tag reflect.StructTag, schema *openapi3.Schema) error {
diff --git a/routers/legacy/router_test.go b/routers/legacy/router_test.go
index 99704b1..2bc30ea 100644
--- a/routers/legacy/router_test.go
+++ b/routers/legacy/router_test.go
@@ -195,7 +195,7 @@ func TestRouter(t *testing.T) {
 	}
 
 	schema := &openapi3.Schema{
-		Type:    "string",
+		Type:    &openapi3.Types{"string"},
 		Example: 3,
 	}
 	content := openapi3.NewContentWithJSONSchema(schema)