Fragment Description:



The 'Marshaller/Unmarshaller JSON interfaces':
a standard library mechanisms, implicitly, at your service.
The Go encoding/json documentation about Marshal indicates:
'Marshal returns the JSON encoding of v.
Marshal traverses the value v recursively.
If an encountered value implements the Marshaller interface and is not a nil pointer, Marshal calls its MarshalJSON method to produce JSON.
The nil pointer exception is not strictly necessary but mimics a similar, necessary exception in the behavior of UnmarshalJSON'.
In particular, if a JSON value implements the Marshaler interface, its 'MarshalJSON' method is called (idem with its 'UnmarshalJSON' method).
The following example, inspired by Roger Peppe (2016), demonstrates this.
To pinpoint this mechanisms the example uses the value 'health' and 'healthNOT', implementing or not the marshaller/unmarshaller interfaces.


valueImplementingJSONMarshaller

Go Playground

Last update, on 2016, Wed 20 Jan, 19:32:41

/* ... <== see fragment description ... */

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    deps := map[string]Status{
        "rabbit":    Status{"OK"},
        "zookeeper": Status{"DOWN"},
    }
    health := Health{Status: "OK", Dependencies: deps}
    // fmt.Printf("health is: %#v\n", health)
    healthNOT := HealthNOT{Status: "OK", Dependencies: deps}
    // fmt.Printf("health is: %#v\n", health)
    // Marshal returns the JSON encoding of v.
    jsonData, err := json.MarshalIndent(&health, "", "   ")
    if err != nil {
        fmt.Printf("boom: %v\n", err)
        return
    }
    jsonDataNOT, err := json.MarshalIndent(&healthNOT, "", "   ")
    if err != nil {
        fmt.Printf("boom: %v\n", err)
        return
    }
    layout := "\n--- with 'health' implementing" +
        "JSON Marshaller/Unmarshaller ---\n%s\n====\n"
    layoutNOT := "\n--- with 'health'NOT implementing" +
        "JSON Marshaller/Unmarshaller ---\n%s\n====\n"
    fmt.Printf(layout, string(jsonData))
    fmt.Printf(layoutNOT, string(jsonDataNOT))
    var health1 Health
    var health1NOT HealthNOT
    err = json.Unmarshal(jsonData, &health1)
    if err != nil {
        fmt.Printf("cannot unmarshal: %v", err)
    }
    err = json.Unmarshal(jsonDataNOT, &health1NOT)
    if err != nil {
        fmt.Printf("cannot unmarshal: %v", err)
    }
}

// this object doesn't implements the JSON marshaller/unmarshaller
type HealthNOT struct {
    Status       string
    Dependencies map[string]Status
}

// this Object with specific marshal/unmarshal methods, dealing with
// Dependencies
type Health struct {
    Status       string
    Dependencies map[string]Status
}

// the magic occurs with the 2 following methods
func (h *Health) MarshalJSON() ([]byte, error) {
    m := make(map[string]interface{})
    for k, v := range h.Dependencies {
        m[k] = v
    }
    m["status"] = h.Status
    return json.Marshal(m)
}
func (h *Health) UnmarshalJSON(data []byte) error {
    var m map[string]json.RawMessage
    if err := json.Unmarshal(data, &m); err != nil {
        return err
    }
    if m["status"] == nil {
        return fmt.Errorf("no status found in object")
    }
    if err := json.Unmarshal(m["status"], &h.Status); err != nil {
        return fmt.Errorf("cannot unmarshal status: %v", err)
    }
    h.Dependencies = make(map[string]Status)
    delete(m, "status")
    for k, v := range m {
        var status Status
        if err := json.Unmarshal(v, &status); err != nil {
            return fmt.Errorf("cannot unmarshal dependency status %q: %v", k, err)
        }
        h.Dependencies[k] = status
    }
    return nil
}

type Status struct {
    Value string `json:"status"`
}

/* Expected output (with the 2 methods:
--- with 'health' implementingJSON Marshaller/Unmarshaller ---
{
   "rabbit": {
      "status": "OK"
   },
   "status": "OK",
   "zookeeper": {
      "status": "DOWN"
   }
}
====
--- with 'health'NOT implementingJSON Marshaller/Unmarshaller ---
{
   "Status": "OK",
   "Dependencies": {
      "rabbit": {
         "status": "OK"
      },
      "zookeeper": {
         "status": "DOWN"
      }
   }
}
====
*/



Comments