Repository: danielgtaylor/huma (Go)

Issue:
json.RawMessage support
I would like to have some GeoJSON included as part of my request body that is later on deserialized by another library. I would like to use json.RawMessage as a field type. But the validator of Huma is not allowing me that because it states that the input should be a string. Can support for json.RawMessage be included in this library or can I at least disable the validation on a field if I would like to?

As a work arround I could specify the field type as an interface{} marshal it back to bytes and then unmarshal it with my desired library, that feels a bit inefficient and cumbersome.

```
`type CreateRouteBody struct {
	Name        string           `json:"name" example:"Rondje Kralingseplas" doc:"Name to identify a route with" maxLength:"255"`
	RouteType   string           `json:"route_type" enum:"route_points,line_string" doc:"Indication for the type of route"`
	RoutePoints *json.RawMessage `json:"route_points,omitempty" doc:"Feature Collection that contains all the points."`
	LineString  *string          `json:"line_string,omitempty" doc:""`
}` 
```

```
curl --location 'http://localhost:8888/routes/route' \
--header 'Authorization: <token>' \
--header 'Content-Type: application/json' \
--data '{
    "name": "Kralingseplas232",
    "route_type": "route_points",
    "route_points": {
        "type": "FeatureCollection",
        "features": [{
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": [125.6, 10.1]
            },
            "properties": {
                "name": "Dinagat Islands"
            }
        }]
    }
}'
```

```
{
    "$schema": "http://localhost:8888/schemas/ErrorModel.json",
    "title": "Unprocessable Entity",
    "status": 422,
    "detail": "validation failed",
    "errors": [
        {
            "message": "expected string",
            "location": "body.route_points",
            "value": {
                "features": [
                    {
                        "geometry": {
                            "coordinates": [
                                125.6,
                                10.1
                            ],
                            "type": "Point"
                        },
                        "properties": {
                            "name": "Dinagat Islands"
                        },
                        "type": "Feature"
                    }
                ],
                "type": "FeatureCollection"
            }
        }
    ]
}
```





Discussion:
@sven4all good catch, we should definitely support `json.RawMessage`.
@sven4all take a look at #350 and let me know what you think. In the meantime you can also modify the schema that is generated while you wait for an updated release next week, something like:

https://go.dev/play/p/kMYf5zgmAIK



Patch:
diff --git a/docs/docs/features/request-inputs.md b/docs/docs/features/request-inputs.md
index bd4ee720..e66b1b0d 100644
--- a/docs/docs/features/request-inputs.md
+++ b/docs/docs/features/request-inputs.md
@@ -56,6 +56,19 @@ The special struct field `Body` will be treated as the input request body and ca
 
 `RawBody []byte` can also be used alongside `Body` to provide access to the `[]byte` used to validate & parse `Body`.
 
+### Special Types
+
+The following special types are supported out of the box:
+
+| Type              | Schema                                      | Example                       |
+| ----------------- | ------------------------------------------- | ----------------------------- |
+| `time.Time`       | `{"type": "string", "format": "date-time"}` | `"2020-01-01T12:00:00Z"`      |
+| `url.URL`         | `{"type": "string", "format": "uri"}`       | `"https://example.com"`       |
+| `net.IP`          | `{"type": "string", "format": "ipv4"}`      | `"127.0.0.1"`                 |
+| `json.RawMessage` | `{}`                                        | `["whatever", "you", "want"]` |
+
+You can override this default behavior if needed as described in [Schema Customization](./schema-customization.md) and [Request Validation](./request-validation.md), e.g. setting a custom `format` tag for IPv6.
+
 ### Other Body Types
 
 Sometimes, you want to bypass the normal body parsing and instead read the raw body contents directly. This is useful for unstructured data, file uploads, or other binary data. You can use `RawBody []byte` **without** a `Body` field to access the raw body bytes without any parsing/validation being applied. For example, to accept some `text/plain` input:
@@ -78,9 +91,9 @@ This enables you to also do your own parsing of the input, if needed.
 
 ### Multipart Form Data
 
-Multipart form data is supported by using a `RawBody` with a type of  
+Multipart form data is supported by using a `RawBody` with a type of
 `multipart.Form` type in the input struct. This will parse the request using
-Go standard library multipart processing implementation. 
+Go standard library multipart processing implementation.
 
 For example:
 
diff --git a/schema.go b/schema.go
index 47577f72..98fcc5a8 100644
--- a/schema.go
+++ b/schema.go
@@ -30,9 +30,10 @@ const (
 
 // Special JSON Schema formats.
 var (
-	timeType = reflect.TypeOf(time.Time{})
-	ipType   = reflect.TypeOf(net.IP{})
-	urlType  = reflect.TypeOf(url.URL{})
+	timeType       = reflect.TypeOf(time.Time{})
+	ipType         = reflect.TypeOf(net.IP{})
+	urlType        = reflect.TypeOf(url.URL{})
+	rawMessageType = reflect.TypeOf(json.RawMessage{})
 )
 
 func deref(t reflect.Type) reflect.Type {
@@ -597,6 +598,8 @@ func SchemaFromType(r Registry, t reflect.Type) *Schema {
 		return &Schema{Type: TypeString, Format: "uri"}
 	case ipType:
 		return &Schema{Type: TypeString, Format: "ipv4"}
+	case rawMessageType:
+		return &Schema{}
 	}
 
 	minZero := 0.0


Test patch:
diff --git a/schema_test.go b/schema_test.go
index 76c61226..1087c845 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -139,6 +139,11 @@ func TestSchema(t *testing.T) {
 			input:    net.IPv4(127, 0, 0, 1),
 			expected: `{"type": "string", "format": "ipv4"}`,
 		},
+		{
+			name:     "json.RawMessage",
+			input:    &json.RawMessage{},
+			expected: `{}`,
+		},
 		{
 			name:     "bytes",
 			input:    []byte("test"),
diff --git a/validate_test.go b/validate_test.go
index 4a26261b..49c6eb4e 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -811,6 +811,13 @@ var validateTests = []struct {
 		},
 		errs: []string{"expected object with at most 1 properties"},
 	},
+	{
+		name: "json.RawMessage success",
+		typ: reflect.TypeOf(struct {
+			Value json.RawMessage `json:"value"`
+		}{}),
+		input: map[string]any{"value": map[string]any{"some": []any{"thing"}}},
+	},
 	{
 		name:  "object struct success",
 		typ:   reflect.TypeOf(struct{}{}),