Repository: rclone/rclone (language: go)

Issue:
Could not remove MinAge filter when using 'rclone rc'
<!--

Welcome :-) We understand you are having a problem with rclone; we want to help you with that!

If you've just got a question or aren't sure if you've found a bug then please use the rclone forum:

    https://forum.rclone.org/

instead of filing an issue for a quick response.

If you think you might have found a bug, please can you try to replicate it with the latest beta?

    https://beta.rclone.org/
    
If you can still replicate it with the latest beta, then please fill in the info below which makes our lives much easier.  A log with -vv will make our day :-)

Thank you

The Rclone Developers

-->

#### Problem statement:
I am trying to set and reset the MinAge through the options/set command as follow:
```rclone rc options/set --json '{"filter":{"MinAge":<age in nano sec>}}' --rc-addr=<ip:port> --rc-user=<user> --rc-pass=<pass>```
No problem to set the MinAge. But could not remove such filter and ensure there is no MinAge limit. I mean when you run rclone rcd there is no filter regarding the min age. And the MinAge is 9223372036854776000 by default.

#### rclone version
rclone v1.49.1
- os/arch: windows/amd64
- go version: go1.12.3

#### OS
Multiple OS and versions

####  Storage
Swift

#### The command  trying to run
`rclone rc options/set --json '{"filter":{"MinAge": 9223372036854775807}}"`

#### log

ERROR : rc: "options/set": error: failed to write options from block "filter": Reshape failed to Unmarshal: json: cannot unmarshal number 9223372036854776000 into Go struct field Opt.MinAge of type fs.Duration





Discussion / hints:
Sorry for the delay responding!

I think the problem is that the JSON decoding is doing a round trip through floating point numbers and 2^^63-1 is larger than the largest integer a float64 can hold.  The number you entered gets rounded to a number slightly bigger than an `int64` and there is no way of entering the exact number 2^^63-1.

This could be solved by adding a JSON Marshal and Unmarshal to the fs.Duration type.

This could also take other values like "off" (as a string).

What do you think?  Do you want to have a go at implementing it?
I can take a stab at this one.
@KDreynolds great - thank you :-) Let me know if you need help.

Fix patch:
diff --git a/fs/parseduration.go b/fs/parseduration.go
index 3838d811c1e0b..7f8ba99d377f4 100644
--- a/fs/parseduration.go
+++ b/fs/parseduration.go
@@ -1,6 +1,7 @@
 package fs
 
 import (
+	"encoding/json"
 	"fmt"
 	"math"
 	"strconv"
@@ -231,10 +232,26 @@ func (d Duration) Type() string {
 
 // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
 func (d *Duration) UnmarshalJSON(in []byte) error {
-	return UnmarshalJSONFlag(in, d, func(i int64) error {
-		*d = Duration(i)
+	// Check if the input is a string value.
+	if len(in) >= 2 && in[0] == '"' && in[len(in)-1] == '"' {
+		strVal := string(in[1 : len(in)-1]) // Remove the quotes
+
+		// Attempt to parse the string as a duration.
+		parsedDuration, err := ParseDuration(strVal)
+		if err != nil {
+			return err
+		}
+		*d = Duration(parsedDuration)
 		return nil
-	})
+	}
+	// Handle numeric values.
+	var i int64
+	err := json.Unmarshal(in, &i)
+	if err != nil {
+		return err
+	}
+	*d = Duration(i)
+	return nil
 }
 
 // Scan implements the fmt.Scanner interface


Test patch:
diff --git a/fs/parseduration_test.go b/fs/parseduration_test.go
index b95e7da8ddf75..93340026c5db5 100644
--- a/fs/parseduration_test.go
+++ b/fs/parseduration_test.go
@@ -214,3 +214,32 @@ func TestParseUnmarshalJSON(t *testing.T) {
 		assert.Equal(t, Duration(test.want), duration, test.in)
 	}
 }
+
+func TestUnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		name    string
+		input   string
+		want    Duration
+		wantErr bool
+	}{
+		{"off string", `"off"`, DurationOff, false},
+		{"max int64", `9223372036854775807`, DurationOff, false},
+		{"duration string", `"1h"`, Duration(time.Hour), false},
+		{"invalid string", `"invalid"`, 0, true},
+		{"negative int", `-1`, Duration(-1), false},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var d Duration
+			err := json.Unmarshal([]byte(tt.input), &d)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if d != tt.want {
+				t.Errorf("UnmarshalJSON() got = %v, want %v", d, tt.want)
+			}
+		})
+	}
+}