Repository: gin-gonic/gin (language: go)

Issue:
add an option to have the default value as a list with commas as seperator
- [x] Open your pull request against `master`

- [x] Your pull request should have no more than two commits, if not you should squash them.

- [x] It should pass all tests in the available continuous integration systems such as TravisCI.

- [x]  You should add/modify tests to cover your proposed code changes.

- [x]  If your pull request contains a new feature, please document it on the README.

The idea was like mentioned on the title to be able to specify multiple default values that will come back as a slice




Discussion / hints:

Fix patch:
diff --git a/binding/form_mapping.go b/binding/form_mapping.go
index a84536f722..f5f6f3ae93 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/docs/doc.md b/docs/doc.md
index b76011f2ab..0b7b33b668 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)
@@ -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 8cf7465173..810315bb1a 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 {