3

I'm calling a older api, and its returning objects in the form of.

{ value: 1, time: "/Date(1412321990000)/" }

Using a struct defined with

type Values struct{
  Value int
  Time time.Time
}

Gives me a &time.ParseError. I'm a beginner at Go, is there a way for me to define how this should be serialized/deserialized?. Ultimately I do want it as a time.Time object.

This date format seems to be an older .NET format too. Can't really get the output changed either.

2 Answers 2

5

You need to implement the json Unmarshaler interface on your Values struct.

// UnmarshalJSON implements json's Unmarshaler interface
func (v *Values) UnmarshalJSON(data []byte) error {
    // create tmp struct to unmarshal into
    var tmp struct {
        Value int    `json:"value"`
        Time  string `json:"time"`
    }
    if err := json.Unmarshal(data, &tmp); err != nil {
        return err
    }

    v.Value = tmp.Value

    // trim out the timestamp
    s := strings.TrimSuffix(strings.TrimPrefix(tmp.Time, "/Date("), ")/")

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

    // create and assign time using the timestamp
    v.Time = time.Unix(i/1000, 0)

    return nil
}

Check out this working example.

Sign up to request clarification or add additional context in comments.

2 Comments

I like this because as far as my external definition goes. It's time and people seeing my code will see the standard time. The only downside is I'm sort of defining the struct twice. Once as normal and once in UnmarshalJSON. Is it possible to create the tmp struct from the existing Values struct, but override one field definition?
@DewyBroto good catch, I started with ParseInt(s, 10, 0) got an error and changed it, did not even try ParseInt(s, 10, 64).
1

A different approach is to define a custom type for the time instead of manually creating a temp struct.

Also embedding time.Time makes it easier to access all the functions defined on it, like .String().

type WeirdTime struct{ time.Time }

type Value struct {
    Value int
    Time  WeirdTime
}

func (wt *WeirdTime) UnmarshalJSON(data []byte) error {
    if len(data) < 9 || data[6] != '(' || data[len(data)-3] != ')' {
        return fmt.Errorf("unexpected input %q", data)
    }
    t, err := strconv.ParseInt(string(data[7:len(data)-3]), 10, 64)
    if err != nil {
        return err
    }
    wt.Time = time.Unix(t/1000, 0)
    return nil
}

playground

5 Comments

This also seems like a good solution. Though its a bit strange considering the struct definition. I would need to have this "WeirdTime" struct public, which seems confusing if you come to the code later on. Shame I can only pick one answer
@user1570690 I'm not sure what you mean? embedding time in it makes it usable as time.Time.
It is functionally identical as time. It just has a special name, which could be surprising to someone inspecting the Value Struct. Granted they would then look at the WeirdTime struct, and a comment would help. To be Honest I'm still partly torn between which answer is more correct. They both have merits. I do like this answer because its shorter and leaves unmarshalling the other values to the built in method.
@user1570690 you'd name it as DotNetTime or something similar or just Time.
Here's an instance where it's weird. I want to copy the time out to a different struct. instance.Time= value.Time gives a compiler error about incompatible types. (WeirdTime vs time.Time). You can do values.Time.Time, but you can see how this looks a little strange elsewhere in the code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.