diff --git a/go.mod b/go.mod
index b3a3fad..107e4ab 100644
--- a/go.mod
+++ b/go.mod
@@ -1,36 +1,55 @@
 module github.com/smocker-dev/smocker
 
-go 1.15
+go 1.21
 
 require (
 	github.com/Masterminds/sprig/v3 v3.2.2
+	github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
+	github.com/kinbiko/jsonassert v1.1.1
+	github.com/labstack/echo/v4 v4.7.0
+	github.com/layeh/gopher-json v0.0.0-20190114024228-97fed8db8427
+	github.com/namsral/flag v1.7.4-pre
+	github.com/sirupsen/logrus v1.4.2
+	github.com/smarty/assertions v1.15.1
+	github.com/stretchr/objx v0.2.0
+	github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
+	github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7
+	github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
+	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
+	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
+	layeh.com/gopher-luar v1.0.7
+)
+
+require (
+	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.1.1 // indirect
 	github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
 	github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
 	github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
-	github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
 	github.com/facebookgo/httpdown v0.0.0-20180706035922-5979d39b15c2 // indirect
 	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 	github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4 // indirect
 	github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
+	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
+	github.com/google/uuid v1.1.1 // indirect
+	github.com/huandu/xstrings v1.3.1 // indirect
+	github.com/imdario/mergo v0.3.11 // indirect
 	github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
 	github.com/kr/pretty v0.1.0 // indirect
-	github.com/labstack/echo/v4 v4.7.0
-	github.com/layeh/gopher-json v0.0.0-20190114024228-97fed8db8427
+	github.com/labstack/gommon v0.3.1 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mitchellh/copystructure v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.1.2 // indirect
 	github.com/mitchellh/reflectwalk v1.0.1 // indirect
-	github.com/namsral/flag v1.7.4-pre
-	github.com/sirupsen/logrus v1.4.2
-	github.com/smartystreets/assertions v1.0.1
-	github.com/stretchr/objx v0.2.0
-	github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
-	github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7
-	github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
+	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/valyala/fasttemplate v1.2.1 // indirect
 	golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
 	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
-	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
 	golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 // indirect
+	golang.org/x/text v0.3.7 // indirect
+	golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
 	gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
-	layeh.com/gopher-luar v1.0.7
 )
diff --git a/go.sum b/go.sum
index b5550c2..360c4ab 100644
--- a/go.sum
+++ b/go.sum
@@ -34,6 +34,8 @@ github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs
 github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJISkSE=
+github.com/kinbiko/jsonassert v1.1.1/go.mod h1:NO4lzrogohtIdNUNzx8sdzB55M4R4Q1bsrWVdqQ7C+A=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -68,8 +70,8 @@ github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXY
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
+github.com/smarty/assertions v1.15.1 h1:812oFiXI+G55vxsFf+8bIZ1ux30qtkdqzKbEFwyX3Tk=
+github.com/smarty/assertions v1.15.1/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -94,13 +96,9 @@ github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox
 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU=
 golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
@@ -109,25 +107,16 @@ golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
 golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/server/admin_server.go b/server/admin_server.go
index 8789195..62221bf 100644
--- a/server/admin_server.go
+++ b/server/admin_server.go
@@ -22,7 +22,7 @@ type TemplateRenderer struct {
 }
 
 // Render renders a template document
-func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+func (t *TemplateRenderer) Render(w io.Writer, name string, data any, c echo.Context) error {
 	return t.ExecuteTemplate(w, name, data)
 }
 
diff --git a/server/handlers/mocks.go b/server/handlers/mocks.go
index b8f1b0f..2d9c58e 100644
--- a/server/handlers/mocks.go
+++ b/server/handlers/mocks.go
@@ -131,9 +131,9 @@ func (m *Mocks) GenericHandler(c echo.Context) error {
 	// Delay
 	var delay time.Duration
 	if response.Delay.Min != response.Delay.Max {
-		rand.Seed(time.Now().Unix())
+		random := rand.New(rand.NewSource(time.Now().Unix()))
 		var n int64 = int64(response.Delay.Max - response.Delay.Min)
-		delay = time.Duration(rand.Int63n(n) + int64(response.Delay.Min))
+		delay = time.Duration(random.Int63n(n) + int64(response.Delay.Min))
 	} else {
 		delay = response.Delay.Min
 	}
diff --git a/server/handlers/utils.go b/server/handlers/utils.go
index 568a369..d796d72 100644
--- a/server/handlers/utils.go
+++ b/server/handlers/utils.go
@@ -11,7 +11,7 @@ import (
 
 const MIMEApplicationXYaml = "application/x-yaml"
 
-func bindAccordingAccept(c echo.Context, res interface{}) error {
+func bindAccordingAccept(c echo.Context, res any) error {
 	if err := c.Bind(res); err != nil {
 		if err != echo.ErrUnsupportedMediaType {
 			log.WithError(err).Error("Failed to parse payload")
@@ -32,7 +32,7 @@ func bindAccordingAccept(c echo.Context, res interface{}) error {
 	return nil
 }
 
-func respondAccordingAccept(c echo.Context, body interface{}) error {
+func respondAccordingAccept(c echo.Context, body any) error {
 	accept := c.Request().Header.Get(echo.HeaderAccept)
 	if strings.Contains(strings.ToLower(accept), MIMEApplicationXYaml) {
 		c.Response().Header().Set(echo.HeaderContentType, MIMEApplicationXYaml)
diff --git a/server/middlewares.go b/server/middlewares.go
index 0675b95..a08da96 100644
--- a/server/middlewares.go
+++ b/server/middlewares.go
@@ -73,7 +73,7 @@ func HistoryMiddleware(s services.Mocks) echo.MiddlewareFunc {
 				}
 			}
 
-			var body interface{}
+			var body any
 			if err := json.Unmarshal(responseBytes, &body); err != nil {
 				body = string(responseBytes)
 			}
diff --git a/server/templates/go_template.go b/server/templates/go_template.go
index f680743..40f0754 100644
--- a/server/templates/go_template.go
+++ b/server/templates/go_template.go
@@ -26,7 +26,7 @@ func (*goTemplateYamlEngine) Execute(request types.Request, script string) (*typ
 	}
 
 	buffer := new(bytes.Buffer)
-	if err = tmpl.Execute(buffer, map[string]interface{}{"Request": request}); err != nil {
+	if err = tmpl.Execute(buffer, map[string]any{"Request": request}); err != nil {
 		log.WithError(err).Error("Failed to execute dynamic template")
 		return nil, fmt.Errorf("failed to execute dynamic template: %w", err)
 	}
@@ -53,12 +53,12 @@ func (*goTemplateJsonEngine) Execute(request types.Request, script string) (*typ
 	}
 
 	buffer := new(bytes.Buffer)
-	if err = tmpl.Execute(buffer, map[string]interface{}{"Request": request}); err != nil {
+	if err = tmpl.Execute(buffer, map[string]any{"Request": request}); err != nil {
 		log.WithError(err).Error("Failed to execute dynamic template")
 		return nil, fmt.Errorf("failed to execute dynamic template: %w", err)
 	}
 
-	var tmplResult map[string]interface{}
+	var tmplResult map[string]any
 	if err = json.Unmarshal(buffer.Bytes(), &tmplResult); err != nil {
 		log.WithError(err).Error("Failed to unmarshal response from dynamic template")
 		return nil, fmt.Errorf("failed to unmarshal response from dynamic template: %w", err)
diff --git a/server/templates/utils.go b/server/templates/utils.go
index bfe9fbc..0e84eee 100644
--- a/server/templates/utils.go
+++ b/server/templates/utils.go
@@ -2,12 +2,12 @@ package templates
 
 import "encoding/json"
 
-func StructToMSI(s interface{}) (map[string]interface{}, error) {
+func StructToMSI(s any) (map[string]any, error) {
 	bytes, err := json.Marshal(s)
 	if err != nil {
 		return nil, err
 	}
-	msi := map[string]interface{}{}
+	msi := map[string]any{}
 	err = json.Unmarshal(bytes, &msi)
 	if err != nil {
 		return nil, err
diff --git a/server/types/encoding.go b/server/types/encoding.go
index a342549..8c4b393 100644
--- a/server/types/encoding.go
+++ b/server/types/encoding.go
@@ -26,7 +26,7 @@ func (ss *StringSlice) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-func (ss *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (ss *StringSlice) UnmarshalYAML(unmarshal func(any) error) error {
 	var str string
 	if err := unmarshal(&str); err == nil {
 		*ss = append(*ss, str)
diff --git a/server/types/history.go b/server/types/history.go
index f526bf4..8e3e3a2 100644
--- a/server/types/history.go
+++ b/server/types/history.go
@@ -38,7 +38,7 @@ type Request struct {
 	Method      string      `json:"method"`
 	Origin      string      `json:"origin"`
 	BodyString  string      `json:"body_string" yaml:"body_string"`
-	Body        interface{} `json:"body,omitempty" yaml:"body,omitempty"`
+	Body        any         `json:"body,omitempty" yaml:"body,omitempty"`
 	QueryParams url.Values  `json:"query_params,omitempty" yaml:"query_params,omitempty"`
 	Headers     http.Header `json:"headers,omitempty" yaml:"headers,omitempty"`
 	Date        time.Time   `json:"date" yaml:"date"`
@@ -46,7 +46,7 @@ type Request struct {
 
 type Response struct {
 	Status  int         `json:"status"`
-	Body    interface{} `json:"body,omitempty" yaml:"body,omitempty"`
+	Body    any         `json:"body,omitempty" yaml:"body,omitempty"`
 	Headers http.Header `json:"headers,omitempty" yaml:"headers,omitempty"`
 	Date    time.Time   `json:"date" yaml:"date"`
 }
@@ -61,8 +61,8 @@ func HTTPRequestToRequest(req *http.Request) Request {
 		}
 	}
 	req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
-	var body interface{}
-	var tmp map[string]interface{}
+	var body any
+	var tmp map[string]any
 	if err := json.Unmarshal(bodyBytes, &tmp); err != nil {
 		body = string(bodyBytes)
 	} else {
diff --git a/server/types/matchers.go b/server/types/matchers.go
index 620b714..8cbae55 100644
--- a/server/types/matchers.go
+++ b/server/types/matchers.go
@@ -1,45 +1,83 @@
 package types
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"net/http"
 	"net/url"
 	"regexp"
 
+	"github.com/kinbiko/jsonassert"
 	log "github.com/sirupsen/logrus"
-	"github.com/smartystreets/assertions"
+	"github.com/smarty/assertions"
 	"github.com/stretchr/objx"
 )
 
+type buffer struct {
+	bb bytes.Buffer
+}
+
+func (b *buffer) Errorf(msg string, args ...interface{}) {
+	b.bb.WriteString(fmt.Sprintf(msg, args...))
+}
+
+func (b *buffer) String() string {
+	return b.bb.String()
+}
+
 const (
 	DefaultMatcher = "ShouldEqual"
 )
 
-type Assertion func(actual interface{}, expected ...interface{}) string
+type Assertion func(actual any, expected ...any) string
 
 var asserts = map[string]Assertion{
-	"ShouldResemble":         assertions.ShouldResemble,
-	"ShouldAlmostEqual":      assertions.ShouldAlmostEqual,
-	"ShouldContainSubstring": assertions.ShouldContainSubstring,
-	"ShouldEndWith":          assertions.ShouldEndWith,
-	"ShouldEqual":            assertions.ShouldEqual,
-	"ShouldEqualJSON":        assertions.ShouldEqualJSON,
-	"ShouldStartWith":        assertions.ShouldStartWith,
-	"ShouldBeEmpty":          ShouldBeEmpty,
-	"ShouldMatch":            ShouldMatch,
-
-	"ShouldNotResemble":         assertions.ShouldNotResemble,
-	"ShouldNotAlmostEqual":      assertions.ShouldNotAlmostEqual,
-	"ShouldNotContainSubstring": assertions.ShouldNotContainSubstring,
-	"ShouldNotEndWith":          assertions.ShouldNotEndWith,
-	"ShouldNotEqual":            assertions.ShouldNotEqual,
-	"ShouldNotStartWith":        assertions.ShouldNotStartWith,
-	"ShouldNotBeEmpty":          ShouldNotBeEmpty,
-	"ShouldNotMatch":            ShouldNotMatch,
+	"ShouldResemble":           assertions.ShouldResemble,
+	"ShouldAlmostEqual":        assertions.ShouldAlmostEqual,
+	"ShouldContainSubstring":   assertions.ShouldContainSubstring,
+	"ShouldEndWith":            assertions.ShouldEndWith,
+	"ShouldEqual":              assertions.ShouldEqual,
+	"ShouldEqualJSON":          assertions.ShouldEqualJSON,
+	"ShouldEqualUnorderedJSON": ShouldEqualUnorderedJSON,
+	"ShouldStartWith":          assertions.ShouldStartWith,
+	"ShouldBeEmpty":            ShouldBeEmpty,
+	"ShouldMatch":              ShouldMatch,
+
+	"ShouldNotResemble":           assertions.ShouldNotResemble,
+	"ShouldNotAlmostEqual":        assertions.ShouldNotAlmostEqual,
+	"ShouldNotContainSubstring":   assertions.ShouldNotContainSubstring,
+	"ShouldNotEndWith":            assertions.ShouldNotEndWith,
+	"ShouldNotEqual":              assertions.ShouldNotEqual,
+	"ShouldNotEqualJSON":          ShouldNotEqualJSON,
+	"ShouldNotEqualUnorderedJSON": ShouldNotEqualUnorderedJSON,
+	"ShouldNotStartWith":          assertions.ShouldNotStartWith,
+	"ShouldNotBeEmpty":            ShouldNotBeEmpty,
+	"ShouldNotMatch":              ShouldNotMatch,
 }
 
-func ShouldMatch(value interface{}, patterns ...interface{}) string {
+func prepareUnorderedJSON(value any) any {
+	switch v := value.(type) {
+	case map[string]any:
+		for key, val := range v {
+			v[key] = prepareUnorderedJSON(val)
+		}
+		return v
+	case []any:
+		if len(v) < 2 {
+			return v
+		}
+		res := make([]any, 0, len(v)+1)
+		res = append(res, "<<UNORDERED>>") // Key element used by jsonassert to allow unordered lists
+		for _, val := range v {
+			res = append(res, prepareUnorderedJSON(val))
+		}
+		return res
+	}
+	return value
+}
+
+func ShouldMatch(value any, patterns ...any) string {
 	valueString, ok := value.(string)
 	if !ok {
 		return "ShouldMatch works only with strings"
@@ -59,15 +97,44 @@ func ShouldMatch(value interface{}, patterns ...interface{}) string {
 	return ""
 }
 
-func ShouldBeEmpty(value interface{}, patterns ...interface{}) string {
+func ShouldBeEmpty(value any, patterns ...any) string {
 	return assertions.ShouldBeEmpty(value)
 }
 
-func ShouldNotBeEmpty(value interface{}, patterns ...interface{}) string {
+func ShouldEqualUnorderedJSON(value any, expected ...any) string {
+	valueString, ok := value.(string)
+	if !ok {
+		return "ShouldEqualUnorderedJSON works only with strings"
+	}
+	if len(expected) != 1 {
+		return "ShouldEqualUnorderedJSON requires exactly one comparison value"
+	}
+	expectedString, ok := expected[0].(string)
+	if !ok {
+		return "ShouldEqualUnorderedJSON works only with strings"
+	}
+	var v any
+	if err := json.Unmarshal([]byte(valueString), &v); err != nil {
+		return "Received value is not valid JSON"
+	}
+	if err := json.Unmarshal([]byte(expectedString), &v); err != nil {
+		return "Expected value is not valid JSON"
+	}
+	b, err := json.Marshal(prepareUnorderedJSON(v))
+	if err != nil {
+		return "ShouldEqualUnorderedJSON failed to prepare expected JSON"
+	}
+	buf := buffer{}
+	ja := jsonassert.New(&buf)
+	ja.Assertf(valueString, string(b))
+	return buf.String()
+}
+
+func ShouldNotBeEmpty(value any, patterns ...any) string {
 	return assertions.ShouldNotBeEmpty(value)
 }
 
-func ShouldNotMatch(value interface{}, patterns ...interface{}) string {
+func ShouldNotMatch(value any, patterns ...any) string {
 	valueString, ok := value.(string)
 	if !ok {
 		return "ShouldNotMatch works only with strings"
@@ -87,6 +154,64 @@ func ShouldNotMatch(value interface{}, patterns ...interface{}) string {
 	return ""
 }
 
+func ShouldNotEqualJSON(value any, expected ...any) string {
+	valueString, ok := value.(string)
+	if !ok {
+		return "ShouldNotEqualJSON works only with strings"
+	}
+	if len(expected) != 1 {
+		return "ShouldNotEqualJSON requires exactly one comparison value"
+	}
+	expectedString, ok := expected[0].(string)
+	if !ok {
+		return "ShouldNotEqualJSON works only with strings"
+	}
+	var v any
+	if err := json.Unmarshal([]byte(valueString), &v); err != nil {
+		return "Received value is not valid JSON"
+	}
+	if err := json.Unmarshal([]byte(expectedString), &v); err != nil {
+		return "Expected value is not valid JSON"
+	}
+
+	if res := assertions.ShouldEqualJSON(value, expected...); res == "" {
+		return fmt.Sprintf("Expected %q to not be equal to %q (but it did)!", valueString, expectedString)
+	}
+	return ""
+}
+
+func ShouldNotEqualUnorderedJSON(value any, expected ...any) string {
+	valueString, ok := value.(string)
+	if !ok {
+		return "ShouldNotEqualUnorderedJSON works only with strings"
+	}
+	if len(expected) != 1 {
+		return "ShouldNotEqualUnorderedJSON requires exactly one comparison value"
+	}
+	expectedString, ok := expected[0].(string)
+	if !ok {
+		return "ShouldNotEqualUnorderedJSON works only with strings"
+	}
+	var v any
+	if err := json.Unmarshal([]byte(valueString), &v); err != nil {
+		return "Received value is not valid JSON"
+	}
+	if err := json.Unmarshal([]byte(expectedString), &v); err != nil {
+		return "Expected value is not valid JSON"
+	}
+	b, err := json.Marshal(prepareUnorderedJSON(v))
+	if err != nil {
+		return "ShouldNotEqualUnorderedJSON failed to prepare expected JSON"
+	}
+	buf := buffer{}
+	ja := jsonassert.New(&buf)
+	ja.Assertf(valueString, string(b))
+	if buf.String() == "" {
+		return fmt.Sprintf("Expected JSON value %q to not be equivalent to JSON value %q regardless of list order (but it did)!", valueString, expectedString)
+	}
+	return ""
+}
+
 type StringMatcher struct {
 	Matcher string `json:"matcher" yaml:"matcher,flow"`
 	Value   string `json:"value" yaml:"value,flow"`
@@ -143,7 +268,7 @@ func (sm *StringMatcher) UnmarshalJSON(data []byte) error {
 	return sm.Validate()
 }
 
-func (sm *StringMatcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (sm *StringMatcher) UnmarshalYAML(unmarshal func(any) error) error {
 	var s string
 	if err := unmarshal(&s); err == nil {
 		sm.Matcher = DefaultMatcher
@@ -210,7 +335,7 @@ func (sms *StringMatcherSlice) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-func (sms *StringMatcherSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (sms *StringMatcherSlice) UnmarshalYAML(unmarshal func(any) error) error {
 	var s string
 	if err := unmarshal(&s); err == nil {
 		*sms = []StringMatcher{{
@@ -310,14 +435,14 @@ func (bm *BodyMatcher) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
-func (bm BodyMatcher) MarshalYAML() (interface{}, error) {
+func (bm BodyMatcher) MarshalYAML() (any, error) {
 	if bm.bodyString != nil {
 		return bm.bodyString, nil
 	}
 	return bm.bodyJson, nil
 }
 
-func (bm *BodyMatcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (bm *BodyMatcher) UnmarshalYAML(unmarshal func(any) error) error {
 	var s StringMatcher
 	if err := unmarshal(&s); err == nil {
 		if _, ok := asserts[s.Matcher]; ok {
diff --git a/server/types/mock.go b/server/types/mock.go
index 83e62a8..657752a 100644
--- a/server/types/mock.go
+++ b/server/types/mock.go
@@ -5,7 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"strings"
@@ -181,7 +181,7 @@ func (d *Delay) UnmarshalJSON(data []byte) error {
 	return d.validate()
 }
 
-func (d *Delay) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (d *Delay) UnmarshalYAML(unmarshal func(any) error) error {
 	var s time.Duration
 	if err := unmarshal(&s); err == nil {
 		d.Min = s
@@ -250,7 +250,7 @@ func (mp MockProxy) Redirect(req Request) (*MockResponse, error) {
 		return nil, err
 	}
 	defer resp.Body.Close()
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tests/data/matcher_mock_list.yml b/tests/data/matcher_mock_list.yml
index 86f1c03..4892b2e 100644
--- a/tests/data/matcher_mock_list.yml
+++ b/tests/data/matcher_mock_list.yml
@@ -95,3 +95,28 @@
   response:
     body: >
       {"message": "test13"}
+- request:
+    path: /test14
+    body:
+      matcher: ShouldNotEqualJSON
+      value: >
+        {"id": 1}
+  response:
+    body: >
+      {"message": "test15"}
+- request:
+    path: /test16
+    body:
+      matcher: ShouldEqualUnorderedJSON
+      value: '[{"messages": ["message4", "message3"]}, {"messages": ["message2", "message1"]}]'
+  response:
+    body: >
+      {"message": "test17"}
+- request:
+    path: /test18
+    body:
+      matcher: ShouldNotEqualUnorderedJSON
+      value: '[{"messages": ["message4", "message3"]}, {"messages": ["message2", "message1"]}]'
+  response:
+    body: >
+      {"message": "test19"}
diff --git a/tests/features/set_mocks.yml b/tests/features/set_mocks.yml
index 0ae1818..9f4da71 100644
--- a/tests/features/set_mocks.yml
+++ b/tests/features/set_mocks.yml
@@ -105,20 +105,23 @@ testcases:
         url: http://localhost:8081/mocks
         assertions:
           - result.statuscode ShouldEqual 200
-          - result.bodyjson.__len__ ShouldEqual 9
-          - result.bodyjson.bodyjson8.request.path.matcher ShouldEqual "ShouldMatch"
-          - result.bodyjson.bodyjson8.request.path.value ShouldEqual "/.*"
-          - result.bodyjson.bodyjson7.request.method.matcher ShouldEqual "ShouldContainSubstring"
-          - result.bodyjson.bodyjson7.request.method.value ShouldEqual "PO"
-          - result.bodyjson.bodyjson6.request.body.matcher ShouldEqual "ShouldEqualJSON"
-          - result.bodyjson.bodyjson6.request.body.value ShouldContainSubstring id
-          - result.bodyjson.bodyjson5.request.headers.content-type.content-type0.matcher ShouldEqual "ShouldMatch"
-          - result.bodyjson.bodyjson5.request.headers.content-type.content-type0.value ShouldEqual application/.*
-          - result.bodyjson.bodyjson4.request.query_params.test.test0.value ShouldEqual true
-          - result.bodyjson.bodyjson3.request.body.matcher ShouldEqual "ShouldNotBeEmpty"
-          - result.bodyjson.bodyjson2.request.query_params.test.test0.value ShouldEqual true
-          - result.bodyjson.bodyjson1.request.body.key1.value ShouldEqual test
-          - result.bodyjson.bodyjson0.request.body.key1[0].value ShouldEqual test
+          - result.bodyjson.__len__ ShouldEqual 12
+          - result.bodyjson.bodyjson0.request.body.matcher ShouldEqual ShouldNotEqualUnorderedJSON
+          - result.bodyjson.bodyjson1.request.body.matcher ShouldEqual ShouldEqualUnorderedJSON
+          - result.bodyjson.bodyjson2.request.body.matcher ShouldEqual ShouldNotEqualJSON
+          - result.bodyjson.bodyjson3.request.body.key1[0].value ShouldEqual test
+          - result.bodyjson.bodyjson4.request.body.key1.value ShouldEqual test
+          - result.bodyjson.bodyjson5.request.query_params.test.test0.value ShouldEqual true
+          - result.bodyjson.bodyjson6.request.body.matcher ShouldEqual "ShouldNotBeEmpty"
+          - result.bodyjson.bodyjson7.request.query_params.test.test0.value ShouldEqual true
+          - result.bodyjson.bodyjson8.request.headers.content-type.content-type0.matcher ShouldEqual "ShouldMatch"
+          - result.bodyjson.bodyjson8.request.headers.content-type.content-type0.value ShouldEqual application/.*
+          - result.bodyjson.bodyjson9.request.body.matcher ShouldEqual "ShouldEqualJSON"
+          - result.bodyjson.bodyjson9.request.body.value ShouldContainSubstring id
+          - result.bodyjson.bodyjson10.request.method.matcher ShouldEqual "ShouldContainSubstring"
+          - result.bodyjson.bodyjson10.request.method.value ShouldEqual "PO"
+          - result.bodyjson.bodyjson11.request.path.matcher ShouldEqual "ShouldMatch"
+          - result.bodyjson.bodyjson11.request.path.value ShouldEqual "/.*"
 
   - name: Add dynamic mocks
     steps:
diff --git a/tests/features/use_mocks.yml b/tests/features/use_mocks.yml
index 8b4c236..9c55623 100644
--- a/tests/features/use_mocks.yml
+++ b/tests/features/use_mocks.yml
@@ -157,7 +157,6 @@ testcases:
         assertions:
           - result.statuscode ShouldEqual 200
           - result.bodyjson.message ShouldEqual test11
-
       - type: http
         method: POST
         url: http://localhost:8080/test10
@@ -175,7 +174,6 @@ testcases:
         assertions:
           - result.statuscode ShouldEqual 200
           - result.bodyjson.message ShouldEqual test13
-
       - type: http
         method: POST
         url: http://localhost:8080/test12
@@ -185,6 +183,73 @@ testcases:
         assertions:
           - result.statuscode ShouldEqual 666
 
+      - type: http
+        method: POST
+        url: http://localhost:8080/test14
+        body: '{"id": 2}'
+        assertions:
+          - result.statuscode ShouldEqual 200
+          - result.bodyjson.message ShouldEqual test15
+      - type: http
+        method: POST
+        url: http://localhost:8080/test14
+        body: '{"id": 1}'
+        assertions:
+          - result.statuscode ShouldEqual 666
+      - type: http
+        method: POST
+        url: http://localhost:8080/test14
+        body: <test>test</test> # The ShouldNotEqualJSON matcher should not work with invalid JSON
+        assertions:
+          - result.statuscode ShouldEqual 666
+
+      - type: http
+        method: POST
+        url: http://localhost:8080/test16
+        body: '[{"messages": ["message1", "message2"]}, {"messages": ["message3", "message4"]}]'
+        assertions:
+          - result.statuscode ShouldEqual 200
+          - result.bodyjson.message ShouldEqual test17
+      - type: http
+        method: POST
+        url: http://localhost:8080/test16
+        body: '[{"messages": ["message4", "message3"]}, {"messages": ["message2", "message1"]}]'
+        assertions:
+          - result.statuscode ShouldEqual 200
+          - result.bodyjson.message ShouldEqual test17
+      - type: http
+        method: POST
+        url: http://localhost:8080/test16
+        body: '{"id": 1}'
+        assertions:
+          - result.statuscode ShouldEqual 666
+
+      - type: http
+        method: POST
+        url: http://localhost:8080/test18
+        body: '{"id": 1}'
+        assertions:
+          - result.statuscode ShouldEqual 200
+          - result.bodyjson.message ShouldEqual test19
+      - type: http
+        method: POST
+        url: http://localhost:8080/test18
+        body: '[{"messages": ["message1", "message2"]}, {"messages": ["message3", "message4"]}]'
+        assertions:
+          - result.statuscode ShouldEqual 666
+      - type: http
+        method: POST
+        url: http://localhost:8080/test18
+        body: '[{"messages": ["message4", "message3"]}, {"messages": ["message2", "message1"]}]'
+        assertions:
+          - result.statuscode ShouldEqual 666
+      - type: http
+        method: POST
+        url: http://localhost:8080/test18
+        body: <test>test</test> # The ShouldNotEqualUnorderedJSON matcher should not work with invalid JSON
+        assertions:
+          - result.statuscode ShouldEqual 666
+
   - name: Use dynamic mock list
     steps:
       - type: http