Repository: gin-gonic/gin
Pull request #4048 (2024-09-03 03:52:19)

feat(form): Support default values for collections in form binding
- With pull requests:
  - Open your pull request against `master`
  - Your pull request should have no more than two commits, if not you should squash them.
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
  - You should add/modify tests to cover your proposed code changes.
  - If your pull request contains a new feature, please document it on the README.

---
This PR adds support for default values for the "multi" and "csv" collection_format binding types introduced in PR #3986. 
- To circumvent the issue of parsing commas or needing to use regex, I set semicolon as the delimiter for "multi" and "csv"
- By merging this PR, we can also close PR #2744 from 2021 that was adding the same functionality to an older version of Gin

closes #2744 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/gin-gonic/gin/pull/4048?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 98.86%. Comparing base [(`3dc1cd6`)](https://app.codecov.io/gh/gin-gonic/gin/commit/3dc1cd6572b4e3a0cd170a15debe546c2c72294f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) to head [(`b9ca046`)](https://app.codecov.io/gh/gin-gonic/gin/commit/b9ca04660f82006bd6c1def32ee63cfbd02c2ca8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).
> Report is 69 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4048      +/-   ##
==========================================
- Coverage   99.21%   98.86%   -0.36%     
==========================================
  Files          42       44       +2     
  Lines        3182     2811     -371     
==========================================
- Hits         3157     2779     -378     
- Misses         17       19       +2     
- Partials        8       13       +5     
```

| [Flag](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | Coverage Δ | |
|---|---|---|
| [](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [-tags "sonic avx"](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.85% <100.00%> (?)` | |
| [-tags go_json](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.85% <100.00%> (?)` | |
| [-tags nomsgpack](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.84% <100.00%> (?)` | |
| [go-1.18](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [go-1.19](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [go-1.20](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `?` | |
| [go-1.21](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.86% <100.00%> (-0.36%)` | :arrow_down: |
| [go-1.22](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.86% <100.00%> (?)` | |
| [macos-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.86% <100.00%> (-0.36%)` | :arrow_down: |
| [ubuntu-latest](https://app.codecov.io/gh/gin-gonic/gin/pull/4048/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic) | `98.86% <100.00%> (-0.36%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/gin-gonic/gin/pull/4048?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gin-gonic).


Patch:
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index a84536f..f5f6f3a 100644
--- a/binding/form_mapping.go
+++ b/binding/form_mapping.go
@@ -159,6 +159,14 @@ func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter
 		if k, v := head(opt, "="); k == "default" {
 			setOpt.isDefaultExists = true
 			setOpt.defaultValue = v
+
+			// convert semicolon-separated default values to csv-separated values for processing in setByForm
+			if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
+				cfTag := field.Tag.Get("collection_format")
+				if cfTag == "" || cfTag == "multi" || cfTag == "csv" {
+					setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
+				}
+			}
 		}
 	}
 
@@ -224,6 +232,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 	case reflect.Slice:
 		if !ok {
 			vs = []string{opt.defaultValue}
+
+			// pre-process the default value for multi if present
+			cfTag := field.Tag.Get("collection_format")
+			if cfTag == "" || cfTag == "multi" {
+				vs = strings.Split(opt.defaultValue, ",")
+			}
 		}
 
 		if ok, err = trySetCustom(vs[0], value); ok {
@@ -238,6 +252,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 	case reflect.Array:
 		if !ok {
 			vs = []string{opt.defaultValue}
+
+			// pre-process the default value for multi if present
+			cfTag := field.Tag.Get("collection_format")
+			if cfTag == "" || cfTag == "multi" {
+				vs = strings.Split(opt.defaultValue, ",")
+			}
 		}
 
 		if ok, err = trySetCustom(vs[0], value); ok {
diff --git a/context.go b/context.go
index baa4b0f..1f35651 100644
--- a/context.go
+++ b/context.go
@@ -315,7 +315,31 @@ func (c *Context) GetInt(key string) (i int) {
 	return
 }
 
-// GetInt64 returns the value associated with the key as an integer.
+// GetInt8 returns the value associated with the key as an integer 8.
+func (c *Context) GetInt8(key string) (i8 int8) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i8, _ = val.(int8)
+	}
+	return
+}
+
+// GetInt16 returns the value associated with the key as an integer 16.
+func (c *Context) GetInt16(key string) (i16 int16) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i16, _ = val.(int16)
+	}
+	return
+}
+
+// GetInt32 returns the value associated with the key as an integer 32.
+func (c *Context) GetInt32(key string) (i32 int32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i32, _ = val.(int32)
+	}
+	return
+}
+
+// GetInt64 returns the value associated with the key as an integer 64.
 func (c *Context) GetInt64(key string) (i64 int64) {
 	if val, ok := c.Get(key); ok && val != nil {
 		i64, _ = val.(int64)
@@ -331,7 +355,31 @@ func (c *Context) GetUint(key string) (ui uint) {
 	return
 }
 
-// GetUint64 returns the value associated with the key as an unsigned integer.
+// GetUint8 returns the value associated with the key as an unsigned integer 8.
+func (c *Context) GetUint8(key string) (ui8 uint8) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui8, _ = val.(uint8)
+	}
+	return
+}
+
+// GetUint16 returns the value associated with the key as an unsigned integer 16.
+func (c *Context) GetUint16(key string) (ui16 uint16) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui16, _ = val.(uint16)
+	}
+	return
+}
+
+// GetUint32 returns the value associated with the key as an unsigned integer 32.
+func (c *Context) GetUint32(key string) (ui32 uint32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui32, _ = val.(uint32)
+	}
+	return
+}
+
+// GetUint64 returns the value associated with the key as an unsigned integer 64.
 func (c *Context) GetUint64(key string) (ui64 uint64) {
 	if val, ok := c.Get(key); ok && val != nil {
 		ui64, _ = val.(uint64)
@@ -339,6 +387,14 @@ func (c *Context) GetUint64(key string) (ui64 uint64) {
 	return
 }
 
+// GetFloat32 returns the value associated with the key as a float32.
+func (c *Context) GetFloat32(key string) (f32 float32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		f32, _ = val.(float32)
+	}
+	return
+}
+
 // GetFloat64 returns the value associated with the key as a float64.
 func (c *Context) GetFloat64(key string) (f64 float64) {
 	if val, ok := c.Get(key); ok && val != nil {
@@ -363,6 +419,90 @@ func (c *Context) GetDuration(key string) (d time.Duration) {
 	return
 }
 
+func (c *Context) GetIntSlice(key string) (is []int) {
+	if val, ok := c.Get(key); ok && val != nil {
+		is, _ = val.([]int)
+	}
+	return
+}
+
+func (c *Context) GetInt8Slice(key string) (i8s []int8) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i8s, _ = val.([]int8)
+	}
+	return
+}
+
+func (c *Context) GetInt16Slice(key string) (i16s []int16) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i16s, _ = val.([]int16)
+	}
+	return
+}
+
+func (c *Context) GetInt32Slice(key string) (i32s []int32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i32s, _ = val.([]int32)
+	}
+	return
+}
+
+func (c *Context) GetInt64Slice(key string) (i64s []int64) {
+	if val, ok := c.Get(key); ok && val != nil {
+		i64s, _ = val.([]int64)
+	}
+	return
+}
+
+func (c *Context) GetUintSlice(key string) (uis []uint) {
+	if val, ok := c.Get(key); ok && val != nil {
+		uis, _ = val.([]uint)
+	}
+	return
+}
+
+func (c *Context) GetUint8Slice(key string) (ui8s []uint8) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui8s, _ = val.([]uint8)
+	}
+	return
+}
+
+func (c *Context) GetUint16Slice(key string) (ui16s []uint16) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui16s, _ = val.([]uint16)
+	}
+	return
+}
+
+func (c *Context) GetUint32Slice(key string) (ui32s []uint32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui32s, _ = val.([]uint32)
+	}
+	return
+}
+
+func (c *Context) GetUint64Slice(key string) (ui64s []uint64) {
+	if val, ok := c.Get(key); ok && val != nil {
+		ui64s, _ = val.([]uint64)
+	}
+	return
+}
+
+func (c *Context) GetFloat32Slice(key string) (f32s []float32) {
+	if val, ok := c.Get(key); ok && val != nil {
+		f32s, _ = val.([]float32)
+	}
+	return
+}
+
+func (c *Context) GetFloat64Slice(key string) (f64s []float64) {
+	if val, ok := c.Get(key); ok && val != nil {
+		f64s, _ = val.([]float64)
+	}
+	return
+}
+
 // GetStringSlice returns the value associated with the key as a slice of strings.
 func (c *Context) GetStringSlice(key string) (ss []string) {
 	if val, ok := c.Get(key); ok && val != nil {
diff --git a/docs/doc.md b/docs/doc.md
index b76011f..8cb53cc 100644
--- a/docs/doc.md
+++ b/docs/doc.md
@@ -26,6 +26,7 @@
   - [Custom Validators](#custom-validators)
   - [Only Bind Query String](#only-bind-query-string)
   - [Bind Query String or Post Data](#bind-query-string-or-post-data)
+  - [Bind default value if none provided](#bind-default-value-if-none-provided)
   - [Collection format for arrays](#collection-format-for-arrays)
   - [Bind Uri](#bind-uri)
   - [Bind custom unmarshaler](#bind-custom-unmarshaler)
@@ -339,16 +340,16 @@ func main() {
   router := gin.Default()
 
   // Simple group: v1
-  v1 := router.Group("/v1")
   {
+    v1 := router.Group("/v1")
     v1.POST("/login", loginEndpoint)
     v1.POST("/submit", submitEndpoint)
     v1.POST("/read", readEndpoint)
   }
 
   // Simple group: v2
-  v2 := router.Group("/v2")
   {
+    v2 := router.Group("/v2")
     v2.POST("/login", loginEndpoint)
     v2.POST("/submit", submitEndpoint)
     v2.POST("/read", readEndpoint)
@@ -862,6 +863,53 @@ Test it with:
 curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
 ```
 
+
+### Bind default value if none provided
+
+If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag:
+
+```
+package main
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+type Person struct {
+	Name      string    `form:"name,default=William"`
+	Age       int       `form:"age,default=10"`
+	Friends   []string  `form:"friends,default=Will;Bill"`
+	Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"`
+	LapTimes  []int     `form:"lap_times,default=1;2;3" collection_format:"csv"`
+}
+
+func main() {
+	g := gin.Default()
+	g.POST("/person", func(c *gin.Context) {
+		var req Person
+		if err := c.ShouldBindQuery(&req); err != nil {
+			c.JSON(http.StatusBadRequest, err)
+			return
+		}
+		c.JSON(http.StatusOK, req)
+	})
+	_ = g.Run("localhost:8080")
+}
+```
+
+```
+curl -X POST http://localhost:8080/person
+{"Name":"William","Age":10,"Friends":["Will","Bill"],"Colors":["red","blue"],"LapTimes":[1,2,3]}
+```
+
+NOTE: For default [collection values](#collection-format-for-arrays), the following rules apply:
+- Since commas are used to delimit tag options, they are not supported within a default value and will result in undefined behavior
+- For the collection formats "multi" and "csv", a semicolon should be used in place of a comma to delimited default values
+- Since semicolons are used to delimit default values for "multi" and "csv", they are not supported within a default value for "multi" and "csv"
+
+
 #### Collection format for arrays
 
 | Format          | Description                                               | Example                 |


Test patch:
diff --git a/binding/form_mapping_test.go b/binding/form_mapping_test.go
index 8cf7465..810315b 100644
--- a/binding/form_mapping_test.go
+++ b/binding/form_mapping_test.go
@@ -323,6 +323,72 @@ func TestMappingCollectionFormat(t *testing.T) {
 	assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
 }
 
+func TestMappingCollectionFormatInvalid(t *testing.T) {
+	var s struct {
+		SliceCsv []int `form:"slice_csv" collection_format:"xxx"`
+	}
+	err := mappingByPtr(&s, formSource{
+		"slice_csv": {"1,2"},
+	}, "form")
+	require.Error(t, err)
+
+	var s2 struct {
+		ArrayCsv [2]int `form:"array_csv" collection_format:"xxx"`
+	}
+	err = mappingByPtr(&s2, formSource{
+		"array_csv": {"1,2"},
+	}, "form")
+	require.Error(t, err)
+}
+
+func TestMappingMultipleDefaultWithCollectionFormat(t *testing.T) {
+	var s struct {
+		SliceMulti       []int     `form:",default=1;2;3" collection_format:"multi"`
+		SliceCsv         []int     `form:",default=1;2;3" collection_format:"csv"`
+		SliceSsv         []int     `form:",default=1 2 3" collection_format:"ssv"`
+		SliceTsv         []int     `form:",default=1\t2\t3" collection_format:"tsv"`
+		SlicePipes       []int     `form:",default=1|2|3" collection_format:"pipes"`
+		ArrayMulti       [2]int    `form:",default=1;2" collection_format:"multi"`
+		ArrayCsv         [2]int    `form:",default=1;2" collection_format:"csv"`
+		ArraySsv         [2]int    `form:",default=1 2" collection_format:"ssv"`
+		ArrayTsv         [2]int    `form:",default=1\t2" collection_format:"tsv"`
+		ArrayPipes       [2]int    `form:",default=1|2" collection_format:"pipes"`
+		SliceStringMulti []string  `form:",default=1;2;3" collection_format:"multi"`
+		SliceStringCsv   []string  `form:",default=1;2;3" collection_format:"csv"`
+		SliceStringSsv   []string  `form:",default=1 2 3" collection_format:"ssv"`
+		SliceStringTsv   []string  `form:",default=1\t2\t3" collection_format:"tsv"`
+		SliceStringPipes []string  `form:",default=1|2|3" collection_format:"pipes"`
+		ArrayStringMulti [2]string `form:",default=1;2" collection_format:"multi"`
+		ArrayStringCsv   [2]string `form:",default=1;2" collection_format:"csv"`
+		ArrayStringSsv   [2]string `form:",default=1 2" collection_format:"ssv"`
+		ArrayStringTsv   [2]string `form:",default=1\t2" collection_format:"tsv"`
+		ArrayStringPipes [2]string `form:",default=1|2" collection_format:"pipes"`
+	}
+	err := mappingByPtr(&s, formSource{}, "form")
+	require.NoError(t, err)
+
+	assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
+	assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
+	assert.Equal(t, []int{1, 2, 3}, s.SliceSsv)
+	assert.Equal(t, []int{1, 2, 3}, s.SliceTsv)
+	assert.Equal(t, []int{1, 2, 3}, s.SlicePipes)
+	assert.Equal(t, [2]int{1, 2}, s.ArrayMulti)
+	assert.Equal(t, [2]int{1, 2}, s.ArrayCsv)
+	assert.Equal(t, [2]int{1, 2}, s.ArraySsv)
+	assert.Equal(t, [2]int{1, 2}, s.ArrayTsv)
+	assert.Equal(t, [2]int{1, 2}, s.ArrayPipes)
+	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringMulti)
+	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringCsv)
+	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringSsv)
+	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringTsv)
+	assert.Equal(t, []string{"1", "2", "3"}, s.SliceStringPipes)
+	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringMulti)
+	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringCsv)
+	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringSsv)
+	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringTsv)
+	assert.Equal(t, [2]string{"1", "2"}, s.ArrayStringPipes)
+}
+
 func TestMappingStructField(t *testing.T) {
 	var s struct {
 		J struct {
diff --git a/context_test.go b/context_test.go
index 66190b3..211fbb1 100644
--- a/context_test.go
+++ b/context_test.go
@@ -252,6 +252,30 @@ func TestContextGetInt(t *testing.T) {
 	assert.Equal(t, 1, c.GetInt("int"))
 }
 
+func TestContextGetInt8(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int8"
+	value := int8(0x7F)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt8(key))
+}
+
+func TestContextGetInt16(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int16"
+	value := int16(0x7FFF)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt16(key))
+}
+
+func TestContextGetInt32(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int32"
+	value := int32(0x7FFFFFFF)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt32(key))
+}
+
 func TestContextGetInt64(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
 	c.Set("int64", int64(42424242424242))
@@ -264,12 +288,44 @@ func TestContextGetUint(t *testing.T) {
 	assert.Equal(t, uint(1), c.GetUint("uint"))
 }
 
+func TestContextGetUint8(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint8"
+	value := uint8(0xFF)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint8(key))
+}
+
+func TestContextGetUint16(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint16"
+	value := uint16(0xFFFF)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint16(key))
+}
+
+func TestContextGetUint32(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint32"
+	value := uint32(0xFFFFFFFF)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint32(key))
+}
+
 func TestContextGetUint64(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
 	c.Set("uint64", uint64(18446744073709551615))
 	assert.Equal(t, uint64(18446744073709551615), c.GetUint64("uint64"))
 }
 
+func TestContextGetFloat32(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "float32"
+	value := float32(3.14)
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetFloat32(key))
+}
+
 func TestContextGetFloat64(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
 	c.Set("float64", 4.2)
@@ -289,6 +345,102 @@ func TestContextGetDuration(t *testing.T) {
 	assert.Equal(t, time.Second, c.GetDuration("duration"))
 }
 
+func TestContextGetIntSlice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int-slice"
+	value := []int{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetIntSlice(key))
+}
+
+func TestContextGetInt8Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int8-slice"
+	value := []int8{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt8Slice(key))
+}
+
+func TestContextGetInt16Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int16-slice"
+	value := []int16{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt16Slice(key))
+}
+
+func TestContextGetInt32Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int32-slice"
+	value := []int32{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt32Slice(key))
+}
+
+func TestContextGetInt64Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "int64-slice"
+	value := []int64{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetInt64Slice(key))
+}
+
+func TestContextGetUintSlice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint-slice"
+	value := []uint{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUintSlice(key))
+}
+
+func TestContextGetUint8Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint8-slice"
+	value := []uint8{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint8Slice(key))
+}
+
+func TestContextGetUint16Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint16-slice"
+	value := []uint16{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint16Slice(key))
+}
+
+func TestContextGetUint32Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint32-slice"
+	value := []uint32{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint32Slice(key))
+}
+
+func TestContextGetUint64Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "uint64-slice"
+	value := []uint64{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetUint64Slice(key))
+}
+
+func TestContextGetFloat32Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "float32-slice"
+	value := []float32{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetFloat32Slice(key))
+}
+
+func TestContextGetFloat64Slice(t *testing.T) {
+	c, _ := CreateTestContext(httptest.NewRecorder())
+	key := "float64-slice"
+	value := []float64{1, 2}
+	c.Set(key, value)
+	assert.Equal(t, value, c.GetFloat64Slice(key))
+}
+
 func TestContextGetStringSlice(t *testing.T) {
 	c, _ := CreateTestContext(httptest.NewRecorder())
 	c.Set("slice", []string{"foo"})