Search code examples
jsongointerfacemarshalling

How to convert a structure to a public structure with custom field types with MarshalJSON


I have a Type "Book" that i read from a different interface which returns json. After reading the json and processing data, i have to convert the book to a public book type to hide fields and change output format.

My problem is, that the input type from the same field (ISBN) is sometimes string and sometimes int. I thought that the easiest solution is to use json.Number to unmarshal the data. That works - but i need string on the outgoing json on different fields...

That is the point where i need help. I would have a custom type which i can set in the public structure at the fields, where i want to set the output-json-field to string. I named the custom type "mytype" in the example. (The real data are nested and i have more fields that i set to string in the output - the id field in the public structure is only a test)

I mean, it should look something like that - or not?

func (m *mytype) MarshalJSON() ([]byte, error) {
    ...
}

Here is my example code: https://play.golang.org/p/rS9HddzDMp

package main 

import (  
    "encoding/json"
    "fmt"
    "bytes"
)

/* ----------------------------------------------------------------------------------------------------
Definition of the internal Book object (read from input)
-----------------------------------------------------------------------------------------------------*/
type Book struct {
    Id                      json.Number         `json:"id"`
    Revision                int                 `json:"revision"`
    ISBN                    json.Number         `json:"isbn"`
    Title                   string              `json:"title"`
}

/* ----------------------------------------------------------------------------------------------------
Definition of the public Book object
-----------------------------------------------------------------------------------------------------*/
type AliasBook Book
type omit           *struct{}
type mytype         string

type PublicBook struct {
    Id          string          `json:"id"`
    Revision    omit            `json:"revision,omitempty"`
    ISBN        mytype          `json:"isbn"`
    *AliasBook
}

/* ----------------------------------------------------------------------------------------------------
Rendering functions
-----------------------------------------------------------------------------------------------------*/
func (bb *Book) MarshalJSON() ([]byte, error) {
    fmt.Println("---------------MarschalJSON---------------")
    aux := PublicBook{
        Id:         bb.Id.String(),
        AliasBook:  (*AliasBook)(bb),
    }

    return json.Marshal(&aux)
}

func main() {
    var jsonStreams[2][]byte
    // Input ISBN as string
    jsonStreams[0] = []byte(`{"id":"123","revision":1234,"isbn":"978-3-86680-192-9","title":"Go for dummies"}`)
    // Input ISBN as int
    jsonStreams[1] = []byte(`{"id":123,"revision":1234,"isbn":9783866801929,"title":"Go for dummies"}`)

    // For each stream
    for i := range jsonStreams {
        fmt.Print("stream: ")
        fmt.Println(string(jsonStreams[i]))

        // Read Input
        b := Book{}
        err := json.Unmarshal(jsonStreams[i], &b)
        if err == nil {
            fmt.Printf("%+v\n", b)
        } else {
            fmt.Println(err)
            fmt.Printf("%+v\n", b)
        }

        // Output as JSON
        response := new(bytes.Buffer)
        enc := json.NewEncoder(response)
        enc.SetEscapeHTML(false)
        enc.SetIndent("", "    ")
        err = enc.Encode(&b)
        if err == nil {
            fmt.Printf("%+v\n", response)
        } else {
            fmt.Println(err)
            fmt.Printf("%+v\n", response)
        }
    }
}

Edit I have a solution which works for me. https://play.golang.org/p/Vr4eELsHs1

The keypoint was, that i have to take "fmt.Sprint(*isbn) to return the string in the marshaler. I created a new type, convert the input to int64 with the json.Number function and convert it with the json custom marshaler to string.


Solution

  • I have a solution which works for me. https://play.golang.org/p/Vr4eELsHs1

    The keypoint was, that i have to take fmt.Sprint(*isbn) to return the string in the marshaler. I created a new type, convert the input to int64 with the json.Number function and convert it with the json custom marshaler to string.

    Thank you for your help!