Custom type for time unmarshaling returns object

Hi everyone,

I’m new to Go, and so far I’m loving it!

I’ve got a real-life work project that I use to learn the language, and within the last 2 days I’ve learned a ton of new stuff. I’m, however, struggling with a specific subject; unmarshaling a custom time attribute from XML files. So far, I’ve implemented the following:

type Program struct {
    ...
	Timestamp     customTime `xml:"timestamp,attr"`
    ...
}

type customTime struct {
	Timestamp time.Time
}

func (c *customTime) UnmarshalXMLAttr(attr xml.Attr) error {
	date := attr.Value + " CET"
	parse, _ := time.Parse("2006-01-02T15:04:05 MST", date)
	*c = customTime{parse}
	return nil
}

This kind of works, however, this saved the Timestamp as an object in MongoDB:

_id: ObjectId(235235235235)
timestamp: Object
  timestamp: 2020-02-04T17:46:37.000+00:00

This all makes sense, but I don’t want that object, I just want the timestamp as the value of the first timestamp. I’ve been reading a lot, but I can’t find the documentation I need to achieve this.

Help would be appreciated!

Kind regards,
Derk

1 Like

Hey Derk!

I’m a beginner in Go too, so I may not be as helpful as I’d like to be, but I think I have an idea that could help.

Instead of making your customTime a struct, if you just made it a time.Time, maybe that would remove the extra wrapping object when you’re storing it in MongoDB.

I’m thinking something like this

type Program struct {
	Timestamp customTime `xml:"timestamp,attr"`
}

type customTime time.Time

func (c *customTime) UnmarshalXMLAttr(attr xml.Attr) error {
	date := attr.Value
	fmt.Println("DATE", date)
	location, _ := time.LoadLocation("MST")
	parse, err := time.ParseInLocation("2006-01-02", date, location)
	if err != nil {
		panic(err)
	}
	fmt.Println("PARSE", parse)
	*c = customTime(parse)
	return nil
}

func main() {
	testXML := []byte(`<Program timestamp="2021-05-15"></Program>`)

	var v Program
	if err := xml.Unmarshal(testXML, &v); err != nil {
		panic(err)
	}

	fmt.Println(v.Timestamp)
}

Thank you for your time and reply!

I’ve tried that as well (after making this post), and I think that puts me on the right track. However, it outputs as (note v.Timestamp):

DATE 2021-05-15
PARSE 2021-05-15 00:00:00 -0700 MST
{0 63756658800 0xc000052240}

I think it’s a pointer thing, but coming from Python and Javascript pointers are new to me (if it’s a pointer thing at all).

1 Like

Hi, Derk,

Your XML unmarshaling looks right. How are you using your customTime type when you’re saving to Mongo?

Hi Sean,

I’m passing the reference of the unmarshalled struct:

package main

import (
	"context"
	"encoding/xml"
	"fmt"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"io/ioutil"
	"log"
	"time"
)

type Program struct {
	Guci    string `xml:"guci"`
	Prid string `xml:"prid"`
	Pridexport    string `xml:"pridexport"`
	Titel    string `xml:"titel"`
	Wsrid    string `xml:"wsrid"`
	Type    string `xml:"type"`
	Platform    string `xml:"platform"`
	Puboptie    string `xml:"puboptie"`
	Videofile    string `xml:"videofile"`
	Timestamp     customTime `xml:"timestamp,attr"`
}

type customTime struct {
	Timestamp time.Time
}

func (c *customTime) UnmarshalXMLAttr(attr xml.Attr) error {
	date := attr.Value + " CET"
	parse, _ := time.Parse("2006-01-02T15:04:05 MST", date)
	*c = customTime{parse}
	return nil
}


func main() {
	client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017"))
	ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
	err = client.Connect(ctx)
	collection := client.Database("scooter").Collection("Programs")

	files, err := ioutil.ReadDir("xmls/")
	if err != nil {log.Fatal(err)}

	for _, file := range files {
		data, err := ioutil.ReadFile("xmls/" + file.Name())
		if err != nil {log.Fatal(err)}

		program :=  &Program{}

		_ = xml.Unmarshal([]byte(data), &program)

		fmt.Println(*program)

		_, err = collection.InsertOne(ctx, *program)
		if err != nil {log.Fatal(err)}
	}
}

This is the entire main.go file I’m using right now

EDIT:

Of course it works to insert it like this, but it feels redundant. I think I should be able to pass-in the whole struct:

		_, err = collection.InsertOne(ctx, bson.M{
			"guci": program.Guci,
			"prid": program.Prid,
			"pridexport": program.Pridexport,
			"titel": program.Titel,
			"wsrid": program.Wsrid,
			"type": program.Type,
			"platform": program.Platform,
			"puboptie": program.Puboptie,
			"videofile": program.Videofile,
			"timestamp": program.Timestamp.Timestamp,
		})