Skip to content
This repository has been archived by the owner on Feb 2, 2024. It is now read-only.

Commit

Permalink
Merge pull request #30 from engvik/feature/custom-unmarshal
Browse files Browse the repository at this point in the history
feature: custom unmarshal
  • Loading branch information
engvik authored Apr 4, 2022
2 parents 855f421 + f2bb503 commit 5ecad53
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
32 changes: 32 additions & 0 deletions internal/unmarshal/unmarshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package unmarshal

import (
"fmt"
)

// ErrUnexpectedType is returned when a field contains an unexpected type.
type ErrUnexpectedType struct {
t interface{}
}

// Error implements the error interface.
func (e *ErrUnexpectedType) Error() string {
return fmt.Sprintf("Unexpected field type: %T", e.t)
}

// GetIntStringField uses type asserstion to attempt to determine if the
// field argument is a string or a int. Returns a string or an int or an error
// if neither.
func GetIntStringField(field interface{}) (string, int, error) {
stringField, ok := field.(string)
if ok {
return stringField, -1, nil
}

floatField, ok := field.(float64)
if !ok {
return "", -1, &ErrUnexpectedType{t: field}
}

return "", int(floatField), nil
}
78 changes: 78 additions & 0 deletions mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ package sbanken
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"

"github.com/engvik/sbanken-go/internal/transport"
"github.com/engvik/sbanken-go/internal/unmarshal"
)

var (
ErrMailboxMessageUnmarshal = errors.New("error unmarshaling json")
)

// MailboxMessage represents a mailbox message.
Expand All @@ -27,6 +33,78 @@ type MailboxMessage struct {
AttachmentID int `json:"attachmentId"`
}

// UnmarshalJSON is implemented due to a bug in sbankens API. Category and
// Status should contain strings, but contain ints. The custom unmarshaler
// supports both string and ints for these fields.
func (m *MailboxMessage) UnmarshalJSON(data []byte) error {
// Temporary internal structs that supports both ints and strings for
// Category and Status fields.
tmpMailboxMessage := struct {
Subject string `json:"subject"`
Source string `json:"source"`
LinkName string `json:"linkName"`
LinkURL string `json:"linkUrl"`
ReceivedDate string `json:"receivedDate"`
ID int `json:"id"`
Flag int `json:"flag"`
AttachmentID int `json:"attachmentId"`
Category interface{} `json:"category"`
Status interface{} `json:"status"`
}{}

if err := json.Unmarshal(data, &tmpMailboxMessage); err != nil {
return err
}

// Get and set proper category value.
categoryString, categoryInt, err := unmarshal.GetIntStringField(tmpMailboxMessage.Category)
if err != nil {
return err
}

if categoryString != "" {
m.Category = categoryString
} else {
if categoryInt == 0 {
m.Category = "General"
} else if categoryInt == 1 {
m.Category = "News"
} else {
return ErrMailboxMessageUnmarshal
}
}

// Get and set proper status value.
statusString, statusInt, err := unmarshal.GetIntStringField(tmpMailboxMessage.Status)
if err != nil {
return err
}

if statusString != "" {
m.Status = statusString
} else {
if statusInt == 0 {
m.Status = "Unread"
} else if statusInt == 1 {
m.Status = "Read"
} else {
return ErrMailboxMessageUnmarshal
}
}

// Set fields of correct type
m.Subject = tmpMailboxMessage.Subject
m.Source = tmpMailboxMessage.Source
m.LinkName = tmpMailboxMessage.LinkName
m.LinkURL = tmpMailboxMessage.LinkURL
m.ReceivedDate = tmpMailboxMessage.ReceivedDate
m.ID = tmpMailboxMessage.ID
m.Flag = tmpMailboxMessage.Flag
m.AttachmentID = tmpMailboxMessage.AttachmentID

return nil
}

// MailboxQuery represents query parameteres for querying the mailbox.
type MailboxListQuery struct {
StartDate time.Time
Expand Down
1 change: 1 addition & 0 deletions mailbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var testMailboxMessage = MailboxMessage{
Subject: "Test Subject",
Category: "category",
Source: "source",
Status: "status",
LinkName: "link-name",
LinkURL: "https://example.com",
ReceivedDate: time.Now().String(),
Expand Down
75 changes: 75 additions & 0 deletions transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package sbanken
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"time"

"github.com/engvik/sbanken-go/internal/transport"
"github.com/engvik/sbanken-go/internal/unmarshal"
)

var (
ErrTransactionUnmarshal = errors.New("error unmarshaling json")
)

// Transaction represents a transaction.
Expand All @@ -33,6 +39,75 @@ type Transaction struct {
TransactionDetailSpecified bool `json:"transactionDetailSpecified"`
}

// UnmarshalJSON is implemented due to a bug in sbankens API. The Source field
// should contain a string, but contain an int. The custom unmarshaler supports
// both string and ints for this fields.
func (t *Transaction) UnmarshalJSON(data []byte) error {
// Temporary internal structs that supports both ints and strings for
// the Source field.
tmpTransaction := struct {
CardDetails CardDetails `json:"cardDetails"`
TransactionDetails TransactionDetails `json:"transactionDetails"`
AccountingDate string `json:"accountingDate"`
InterestDate string `json:"interestDate"`
OtherAccountNumber string `json:"otherAccountNumber"`
Text string `json:"text"`
TransactionType string `json:"transactionType"`
TransactionTypeText string `json:"transactionTypeText"`
ReservationType string `json:"reservationType"`
TransactionID string `json:"transactionId"`
Amount float32 `json:"amount"`
TransactionTypeCode int `json:"transactionTypeCode"`
IsReservation bool `json:"isReservation"`
CardDetailsSpecified bool `json:"cardDetailsSpecified"`
OtherAccountNumberSpecified bool `json:"otherAccountNumberSpecified"`
TransactionDetailSpecified bool `json:"transactionDetailSpecified"`
Source interface{} `json:"source"`
}{}

if err := json.Unmarshal(data, &tmpTransaction); err != nil {
return err
}

// Get and set proper source value.
sourceString, sourceInt, err := unmarshal.GetIntStringField(tmpTransaction.Source)
if err != nil {
return err
}

if sourceString != "" {
t.Source = sourceString
} else {
if sourceInt == 0 {
t.Source = "AccountStatement"
} else if sourceInt == 1 {
t.Source = "Archive"
} else {
return ErrTransactionUnmarshal
}
}

// Set fields of correct type
t.CardDetails = tmpTransaction.CardDetails
t.TransactionDetails = tmpTransaction.TransactionDetails
t.AccountingDate = tmpTransaction.AccountingDate
t.InterestDate = tmpTransaction.InterestDate
t.OtherAccountNumber = tmpTransaction.OtherAccountNumber
t.Text = tmpTransaction.Text
t.TransactionType = tmpTransaction.TransactionType
t.TransactionTypeText = tmpTransaction.TransactionTypeText
t.ReservationType = tmpTransaction.ReservationType
t.TransactionID = tmpTransaction.TransactionID
t.Amount = tmpTransaction.Amount
t.TransactionTypeCode = tmpTransaction.TransactionTypeCode
t.IsReservation = tmpTransaction.IsReservation
t.CardDetailsSpecified = tmpTransaction.CardDetailsSpecified
t.OtherAccountNumberSpecified = tmpTransaction.OtherAccountNumberSpecified
t.TransactionDetailSpecified = tmpTransaction.TransactionDetailSpecified

return nil
}

// CardDetails contains card details about the card used.
type CardDetails struct {
CardNumber string `json:"cardNumber"`
Expand Down

0 comments on commit 5ecad53

Please sign in to comment.