From 3621b6643792101ca02c70a861e88b9e4e091358 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 27 May 2026 17:28:54 -0400 Subject: [PATCH] fix: handle MAL API returning numbers instead of strings for zero-value statistics The MyAnimeList API inconsistently returns statistics status fields (watching, completed, on_hold, dropped, plan_to_watch) as quoted strings for non-zero values (e.g. "8217") but as bare numbers for zero values (e.g. 0). This caused JSON unmarshal errors for anime with zero counts in any status field. Introduce a FlexString custom type that implements json.Unmarshaler to accept both JSON strings and JSON numbers, always storing the result as a string. The type definition lives in MALTypes.go and the unmarshal logic in MALFunctions.go to keep static types and behavior separate. --- MALFunctions.go | 15 +++++++++++++++ MALTypes.go | 16 ++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/MALFunctions.go b/MALFunctions.go index 416c45c..cb6a5be 100644 --- a/MALFunctions.go +++ b/MALFunctions.go @@ -11,6 +11,21 @@ import ( "strings" ) +// Unmarshalling accidental numbers received from MAL to strings +func (f *FlexString) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err == nil { + *f = FlexString(s) + return nil + } + var n json.Number + if err := json.Unmarshal(data, &n); err == nil { + *f = FlexString(string(n)) + return nil + } + return fmt.Errorf("FlexString: invalid value") +} + func MALHelper(method string, malUrl string, body url.Values) (json.RawMessage, string, error) { client := &http.Client{} diff --git a/MALTypes.go b/MALTypes.go index c2534ce..c827532 100644 --- a/MALTypes.go +++ b/MALTypes.go @@ -1,6 +1,10 @@ package main -import "time" +import ( + "time" +) + +type FlexString string type MyAnimeListJWT struct { TokenType string `json:"token_type"` @@ -129,11 +133,11 @@ type MALAnime struct { Statistics struct { NumListUsers int `json:"num_list_users" ts_type:"numListUsers"` Status struct { - Watching string `json:"watching" ts_type:"watching"` - Completed string `json:"completed" ts_type:"completed"` - OnHold string `json:"on_hold" ts_type:"onHold"` - Dropped string `json:"dropped" ts_type:"dropped"` - PlanToWatch string `json:"plan_to_watch" ts_type:"planToWatch"` + Watching FlexString `json:"watching" ts_type:"string"` + Completed FlexString `json:"completed" ts_type:"string"` + OnHold FlexString `json:"on_hold" ts_type:"string"` + Dropped FlexString `json:"dropped" ts_type:"string"` + PlanToWatch FlexString `json:"plan_to_watch" ts_type:"string"` } } }