{
  "slug": "Implementing-a-Custom-JSON-Unmarshaller-in-Go-to-Prevent-Nil-Exceptions-87c414fbf835",
  "title": "Implementing a Custom JSON Unmarshaller in Go to Prevent Nil Exceptions",
  "subtitle": "Consider a situation where a third-party endpoint returns two different data structures at the same time. One possible solution to this…",
  "excerpt": "Consider a situation where a third-party endpoint returns two different data structures at the same time. One possible solution to this…",
  "date": "2025-08-15",
  "tags": [
    "Go"
  ],
  "readingTime": "4 min",
  "url": "https://medium.com/@mobinshaterian/implementing-a-custom-json-unmarshaller-in-go-to-prevent-nil-exceptions-87c414fbf835",
  "hero": "https://cdn-images-1.medium.com/max/800/1*UsTl8rq0xAfoTEJcWcgGzw.jpeg",
  "content": [
    {
      "type": "paragraph",
      "html": "Consider a situation where a third-party endpoint returns two different data structures at the same time. One possible solution to this issue is to use an interface; however, Golang also supports custom JSON Unmarshallers, which can effectively handle this problem."
    },
    {
      "type": "paragraph",
      "html": "For example, imagine an API that returns hotel data. The <code>galleries</code> field can appear in two ways:"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*UsTl8rq0xAfoTEJcWcgGzw.jpeg",
      "alt": "Implementing a Custom JSON Unmarshaller in Go to Prevent Nil Exceptions",
      "caption": "",
      "width": 1120,
      "height": 1120
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Case 1: Flat Array 😎"
    },
    {
      "type": "code",
      "lang": "json",
      "code": "{\n  \"galleries\": [\n    {\n      \"id\": 1,\n      \"media\": \"/path/1.jpg\"\n    },\n    {\n      \"id\": 2,\n      \"media\": \"/path/2.jpg\"\n    }\n  ]\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Case 2: Object with Language Keys 👀"
    },
    {
      "type": "code",
      "lang": "json",
      "code": "{\n  \"galleries\": {\n    \"type-one\": [\n      {\n        \"id\": 1,\n        \"media\": \"/path/en1.jpg\"\n      }\n    ],\n    \"type-two\": [\n      {\n        \"id\": 2,\n        \"media\": \"/path/fa1.jpg\"\n      }\n    ]\n  }\n}"
    },
    {
      "type": "paragraph",
      "html": "<strong>If we use a simple struct in Go, unmarshalling will fail for one of the two cases: </strong>😍"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type MyData struct {\n    Galleries []GalleryItem `json:\"galleries\"` // works only for flat arrays\n}"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type MyData struct {\n    Galleries Galleries `json:\"galleries\"` // works only for object form\n}"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type Galleries struct {\n    Flat []GalleryItem // when galleries is a flat array\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "First Attempt: Using an interface{} 🥶"
    },
    {
      "type": "paragraph",
      "html": "A common solution is to declare the field as <code>interface{}</code> and inspect its type at runtime:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type MyData struct {\n    Galleries interface{\n    }\n    `json:\"galleries\"`\n}"
    },
    {
      "type": "paragraph",
      "html": "You could then use type assertions or <code>map[string]interface{}</code> checks to distinguish between the array and the object."
    },
    {
      "type": "paragraph",
      "html": "While this works, it <strong>pushes complexity downstream</strong>: every place where you consume <code>Galleries</code> must handle both cases, increasing the risk of <code>nil</code> checks and runtime errors."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "A Better Way: Custom JSON Unmarshaller 🕵️‍♂️"
    },
    {
      "type": "paragraph",
      "html": "Go allows us to attach custom parsing logic to our structs by implementing the <code>UnmarshalJSON</code> method. This way, we can centralize the logic and keep the rest of the codebase clean."
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type MyData struct {\n    Galleries Galleries `json:\"galleries\"`\n}\ntype Galleries struct {\n    Flat []GalleryItem // when galleries is a flat array\n    Map map[string][]GalleryItem // when galleries is an object with arbitrary keys\n}\ntype GalleryItem struct {\n    ID int\n    `json:\"id\"`\n    Media string `json:\"media\"`\n}\nfunc (hg *MyData) UnmarshalJSON(data []byte) error {\n    // Try flat slice\n    var flat []GalleryItem\n    if err := json.Unmarshal(data, &flat);\n    err == nil {\n        hg.Flat = flat return nil\n    }\n    // Try map[string][]GalleryItem\n    var multi map[string][]GalleryItem\n    if err := json.Unmarshal(data, &multi);\n    err == nil {\n        hg.Map = multi return nil\n    }\n    return fmt.Errorf(\"invalid galleries format\")\n}"
    },
    {
      "type": "embed",
      "provider": "youtube",
      "url": "https://www.youtube.com/embed/IL4tT0Yhoo4?feature=oembed"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Usage Example"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "import (\n\"encoding/json\"\n\"testing\"\n)\nfunc TestMyData_FlatGalleries(t *testing.T) {\n    jsonData :=\n    `{      \"galleries\": [        { \"id\": 1, \"media\": \"/path/1.jpg\"},        { \"id\": 2, \"media\": \"/path/2.jpg\"}      ]    }`\n    var data MyData\n    if err := json.Unmarshal([]byte(jsonData), &data);\n    err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n    if len(data.Galleries.Flat) != 2 {\n        t.Errorf(\"expected 2 flat galleries, got %d\", len(data.Galleries.Flat))\n    }\n    if data.Galleries.Map != nil {\n        t.Errorf(\"expected Map to be nil, got %+v\", data.Galleries.Map)\n    }\n}\nfunc TestMyData_MapGalleries(t *testing.T) {\n    jsonData :=\n    `{      \"galleries\": {        \"type-one\": [          { \"id\": 1, \"media\": \"/path/en1.jpg\"}        ],        \"type-two\": [          { \"id\": 2, \"media\": \"/path/fa1.jpg\"}        ]      }    }`\n    var data MyData\n    if err := json.Unmarshal([]byte(jsonData), &data);\n    err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n    if data.Galleries.Flat != nil {\n        t.Errorf(\"expected Flat to be nil, got %+v\", data.Galleries.Flat)\n    }\n    if len(data.Galleries.Map) != 2 {\n        t.Errorf(\"expected 2 keys in Map, got %d\", len(data.Galleries.Map))\n    }\n    if data.Galleries.Map[\"type-one\"][0].Media != \"/path/en1.jpg\" {\n        t.Errorf(\"unexpected type-one media: %+v\", data.Galleries.Map[\"type-one\"])\n    }\n    if data.Galleries.Map[\"type-two\"][0].Media != \"/path/fa1.jpg\" {\n        t.Errorf(\"unexpected type-two media: %+v\", data.Galleries.Map[\"type-two\"])\n    }\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Conclusion"
    },
    {
      "type": "paragraph",
      "html": "Dealing with inconsistent or variable JSON structures from third-party APIs is a common challenge in Go applications. When faced with a field like <code>galleries</code> that can appear as either a flat array or a nested object, relying on simple structs or <code>interface{}</code> leads to brittle code riddled with type assertions and nil checks—increasing complexity and the risk of runtime panics."
    },
    {
      "type": "paragraph",
      "html": "By implementing a <strong>custom </strong><code><strong>UnmarshalJSON</strong></code><strong> method</strong>, we can elegantly handle multiple JSON formats in a single, reusable way. This approach encapsulates the parsing logic within the type itself, ensuring that the rest of the application receives a consistent and safe data structure—free from unexpected <code>nil</code> values or type mismatches."
    },
    {
      "type": "paragraph",
      "html": "In this article, we demonstrated how to define a <code>Galleries</code> type capable of unmarshaling both array and object forms, using type-specific decoding logic that automatically adapts to the incoming data. With proper unit testing, this solution proves to be robust, maintainable, and idiomatic in Go."
    },
    {
      "type": "paragraph",
      "html": "Ultimately, custom unmarshalling empowers developers to write resilient code that gracefully handles real-world API inconsistencies — turning a potential source of bugs into a clean, predictable interface. When dealing with unpredictable JSON, <code>UnmarshalJSON</code> isn’t just a workaround; it’s a powerful tool for building reliable systems."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "A message from our Founder"
    },
    {
      "type": "paragraph",
      "html": "<strong>Hey, </strong><a href=\"https://linkedin.com/in/sunilsandhu\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Sunil</strong></a><strong> here.</strong> I wanted to take a moment to thank you for reading until the end and for being a part of this community."
    },
    {
      "type": "paragraph",
      "html": "Did you know that our team run these publications as a volunteer effort to over 3.5m monthly readers? <strong>We don’t receive any funding, we do this to support the community. ❤️</strong>"
    },
    {
      "type": "paragraph",
      "html": "If you want to show some love, please take a moment to <strong>follow me on </strong><a href=\"https://linkedin.com/in/sunilsandhu\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>LinkedIn</strong></a><strong>, </strong><a href=\"https://tiktok.com/@messyfounder\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>TikTok</strong></a>, <a href=\"https://instagram.com/sunilsandhu\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Instagram</strong></a>. You can also subscribe to our <a href=\"https://newsletter.plainenglish.io/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>weekly newsletter</strong></a>."
    },
    {
      "type": "paragraph",
      "html": "And before you go, don’t forget to <strong>clap</strong> and <strong>follow</strong> the writer️!"
    }
  ]
}
