0

My struct:

type User struct {
    FirstName     string `json:"firstname, omitempty" validate:"required"` 
    LastName      string `json:"lastname, omitempty" validate:"required"`
    NumberofDays  int   `json:"numberofdays, string" validate:"min=0,max=100"`
}

Value for NumberofDays is passed as string from the server but I want to check if it is within range and store as int.

Ex: user := &User{"Michael","Msk","3"}

I'm getting 'cannot unmarshal string into Go value of type int'.

I'm not sure how to typecast to int and do the validation

kostix
  • 51,517
  • 14
  • 93
  • 176
  • You've defined NumberofDays as a type of string in the struct tag, you can only use the validate flag with an int type. So change string to int and pass in 3 and not "3" – reticentroot Nov 09 '17 at 16:12
  • See examples https://goplay.space/#l0bNArNn14 – reticentroot Nov 09 '17 at 16:18
  • @reticentroot All values are passed from server as string. I cannot change that. – Yokanand Thirupathi Nov 09 '17 at 16:19
  • 1
    see the example I provided. I also provided an alternative that uses "3". You may not control the values, but you can definitely change your struct – reticentroot Nov 09 '17 at 16:20
  • According to the question they're trying to *un*marshal, not marshal, but maybe OP can clarify. – Adrian Nov 09 '17 at 16:22
  • You will have to do some programming. There is no magical unmarshal-typeconvert-validate-whatnotall syntactic sugar syntax for such things. I know programming is awful, but sometimes necessary. – Volker Nov 09 '17 at 17:32
  • similar: https://stackoverflow.com/questions/9452897/how-to-decode-json-with-type-convert-from-string-to-float64-in-golang – Dave Neeley Dec 28 '20 at 23:20

3 Answers3

4

Remove the space after the comma in the struct tags, e.g. json:"numberofdays, string" should be json:"numberofdays,string". With the space, the json package ignores the string part, hence the error message you're getting. Without the space, the error goes away and behavior is as expected.

Demo comparing the two here: https://play.golang.org/p/AUImnw_PIS

Documentation of json package struct tags: https://golang.org/pkg/encoding/json/#Marshal

Adrian
  • 42,911
  • 6
  • 107
  • 99
  • Define "it's not working"? The error you quoted in the question is fixed by changing the code quoted in the question as indicated in the answer. What is the issue you have after applying that fix? – Adrian Nov 09 '17 at 16:23
  • Error message is : 'cannot unmarshal string into Go value of type int' – Yokanand Thirupathi Nov 09 '17 at 16:28
  • Then double-check your struct tags. With the correct struct tags as indicated in this answer, you should not be getting that error. – Adrian Nov 09 '17 at 16:32
1

You can use a custom type to unmarshal a string to an integer value using whatever set of parsing rules you want this mapping to use:

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
)

type User struct {
    FirstName    string `json:"firstname,omitempty" validate:"required"`
    LastName     string `json:"lastname,omitempty" validate:"required"`
    NumberofDays StrInt `json:"numberofdays" validate:"min=0,max=100"`
}

type StrInt int

func (si *StrInt) UnmarshalJSON(b []byte) error {
    var s string
    err := json.Unmarshal(b, &s)
    if err != nil {
        return err
    }

    n, err := strconv.ParseInt(s, 10, 0)
    if err != nil {
        return err
    }

    *si = StrInt(n)
    return nil
}

const data = `{
    "FirstName": "John",
    "LastName": "Doe",
    "NumberOfDays": "42"
}`

func main() {
    var u User
    err := json.Unmarshal([]byte(data), &u)
    if err != nil {
        panic(err)
    }
    fmt.Println(u)
}

Playground.

The idea is as follows:

  1. Have a custom type which is mostly int but allows defining custom methods on it.
  2. Have that type satisfy the encoding/json.Unmarshaler interface by implementing the UnmarshalJSON() method on a pointer to that type.
  3. In the method, first unmarshal the value as its native type—string—and then parse it as an integer.

This approach might appear to be weird, but if you'll think of it a bit more, there's no single "true" string representation of an integer: there are different bases and different conventions at representing integers even in a particular base.

In your unmarshaling method you implement the policy of the text representation of your integers.

kostix
  • 51,517
  • 14
  • 93
  • 176
  • The struct tags in your example code are invalid. A space after the comma in the struct tag causes everything after the comma to be ignored by the `json` package. A custom type and unmarshaller *is not necessary in this case*. – Adrian Nov 09 '17 at 16:52
  • @Adrian, could you please cite the relevant bit of documentation regarding the mapping of types via the field tags? – kostix Nov 10 '17 at 03:50
  • Already linked from my answer. It's documented on `json.Marshal` and demonstrated in the playground example linked from my answer. – Adrian Nov 10 '17 at 04:07
  • @Adrian, found it now, thanks. Since my answer is still technically correct, I've updated its wording to not be misleading. BTW it's interesting that the doc is silent on what exactly the rules of interpreting those strings are. It's a bit useless IMO w/o strict specification of the behaviour. – kostix Nov 10 '17 at 07:05
  • @Adrian, and yes, it appears, "0xff" is not a valid integer string representation for the unmarshaling rules implemented by that `,string` tag option—[an example](https://play.golang.org/p/bU7-mYZgfd). – kostix Nov 10 '17 at 07:08
  • OK, but that doesn't seem relevant to this question. – Adrian Nov 10 '17 at 14:08
  • @Adrian, I don't quite understand why are you so picky about my answer. You like your solution better? Okay, that's perfectly fine! So what? My solution also addresses the request to perform what the OP called "type casting while unmarshaling". Your solution is simpler, mine is more flexible—when needed. The OP now has access to the full range of tools to attack their problem. – kostix Nov 10 '17 at 15:36
0

The json package defines a custom type json.Number for these cases.

Amnon
  • 334
  • 2
  • 7