diff --git a/struct_utils/map_params.go b/struct_utils/map_params.go
new file mode 100644
index 0000000000000000000000000000000000000000..a7830fd4936ecc262b572fb3b40d3999cfc2e098
--- /dev/null
+++ b/struct_utils/map_params.go
@@ -0,0 +1,55 @@
+package struct_utils
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+//convert fields in a struct to a map of parameters, as if defined in a URL
+//we use this mainly for legacy functions that expect params to be defined in a map[string]string
+//to convert the new params struct into such a map
+func MapParams(data interface{}) map[string]string {
+	params := map[string]string{}
+	addStructParams(params, reflect.ValueOf(data))
+	return params
+}
+
+//recursive function
+func addStructParams(params map[string]string, structValue reflect.Value) {
+	t := structValue.Type()
+	if t.Kind() != reflect.Struct {
+		return
+	}
+
+	for i := 0; i < t.NumField(); i++ {
+		tf := t.Field(i)
+		//recurse for embedded structs
+		if tf.Anonymous {
+			addStructParams(params, structValue.Field(i))
+		} else {
+			jsonTags := strings.Split(t.Field(i).Tag.Get("json"), ",")
+			skip := false
+			if jsonTags[0] == "-" || jsonTags[0] == "" {
+				skip = true
+			} else {
+				for _, option := range jsonTags[1:] {
+					if option == "omitempty" && structValue.Field(i).IsZero() { // ignore the field if omitempty is applicable
+						skip = true
+					}
+				}
+			}
+			if !skip {
+				//lists must be written as JSON lists so they can be unmarshalled
+				//jsut because that is how the legacy code did it
+				if t.Field(i).Type.Kind() == reflect.Slice {
+					jsonValue, _ := json.Marshal(structValue.Field(i).Interface())
+					params[jsonTags[0]] = string(jsonValue)
+				} else {
+					params[jsonTags[0]] = fmt.Sprintf("%v", structValue.Field(i).Interface())
+				}
+			}
+		}
+	}
+}
diff --git a/struct_utils/map_params_test.go b/struct_utils/map_params_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..369264e48eb00adaf249236e35c5b22eb0e0b469
--- /dev/null
+++ b/struct_utils/map_params_test.go
@@ -0,0 +1,61 @@
+package struct_utils_test
+
+import (
+	"testing"
+
+	"gitlab.com/uafrica/go-utils/struct_utils"
+)
+
+func TestParams(t *testing.T) {
+	type s struct {
+		NameWithoutTag  string //will not be encoded into params!
+		NameWithDashTag string `json:"-"`               //will not be encoded into params!
+		Name            string `json:"name"`            //encoded always
+		NameOmitempty   string `json:"name2,omitempty"` //encoded when not empty
+	}
+	ps := s{"a", "b", "c", "d"}
+	pm := struct_utils.MapParams(ps)
+	if len(pm) != 2 || pm["name"] != "c" || pm["name2"] != "d" { //name2 is encoded when not empty
+		t.Fatalf("wrong params: %+v != %+v", ps, pm)
+	}
+	t.Logf("ps=%+v -> pm=%+v", ps, pm)
+
+	ps = s{}
+	pm = struct_utils.MapParams(ps)
+	if len(pm) != 1 || pm["name"] != "" { //name is always encoded because it has json tag and does not specify omitempty
+		t.Fatalf("wrong params: %+v != %+v", ps, pm)
+	}
+	t.Logf("ps=%+v -> pm=%+v", ps, pm)
+}
+
+func TestAnonymous(t *testing.T) {
+	type page struct {
+		Limit  int64 `json:"limit,omitempty"`
+		Offset int64 `json:"offset,omitempty"`
+	}
+	type get struct {
+		ID int64 `json:"id,omitempty"`
+		page
+	}
+
+	ps := get{ID: 123}
+	pm := struct_utils.MapParams(ps)
+	if len(pm) != 1 || pm["id"] != "123" {
+		t.Fatalf("wrong params: %+v != %+v", ps, pm)
+	}
+	t.Logf("ps=%+v -> pm=%+v", ps, pm)
+
+	ps = get{page: page{Limit: 444, Offset: 555}}
+	pm = struct_utils.MapParams(ps)
+	if len(pm) != 2 || pm["limit"] != "444" || pm["offset"] != "555" {
+		t.Fatalf("wrong params: %+v != %+v", ps, pm)
+	}
+	t.Logf("ps=%+v -> pm=%+v", ps, pm)
+
+	ps = get{page: page{Limit: 444, Offset: 555}, ID: 111}
+	pm = struct_utils.MapParams(ps)
+	if len(pm) != 3 || pm["limit"] != "444" || pm["offset"] != "555" || pm["id"] != "111" {
+		t.Fatalf("wrong params: %+v != %+v", ps, pm)
+	}
+	t.Logf("ps=%+v -> pm=%+v", ps, pm)
+}