如何使用MarshalJSON将结构转换为具有自定义字段类型的公共结构

时间:2017-08-23 09:37:13

标签: json go interface marshalling

我有一个Type" Book"我从一个返回json的不同界面读取。在阅读json并处理数据后,我必须将书籍转换为公共书籍类型以隐藏字段并更改输出格式。

我的问题是,来自同一字段(ISBN)的输入类型有时是字符串,有时是int。我认为最简单的解决方案是使用json.Number来解组数据。这工作 - 但我需要在不同领域的传出json上的字符串......

这就是我需要帮助的地方。我会有一个自定义类型,我可以在字段的公共结构中设置,我想将output-json-field设置为字符串。我将自定义类型命名为#34; mytype"在示例中。 (实际数据是嵌套的,我有更多的字段,我在输出中设置为字符串 - 公共结构中的id字段只是一个测试)

我的意思是,它应该看起来像那样 - 或者不是吗?

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

以下是我的示例代码: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)
        }
    }
}

修改 我有一个适合我的解决方案。 https://play.golang.org/p/Vr4eELsHs1

关键点是,我必须采用" fmt.Sprint(* isbn)来返回封送器中的字符串。我创建了一个新类型,使用json.Number函数将输入转换为int64,并将其与json自定义封送器转换为字符串。

2 个答案:

答案 0 :(得分:1)

最简单的解决方案是拥有一个代表ISBN号的自定义类型。然后,您可以实现自定义JSON解码功能,以便您可以解析字符串和放大器。数字输入。例如

type isbn string

func (s *isbn) UnmarshalJSON(buf []byte) error {
    // Read numeric characters only from raw JSON input. This will handle strings, numbers or null etc and strip any
    // optional separators.
    out := make([]byte, 0, len(buf))
    for _, b := range buf {
        if b >= '0' && b <= '9' {
            out = append(out, b)
        }
    }

    // Validate ISBN (assuming not using old 10 digit ISBN)
    l := len(out)
    if l != 13 {
        return errors.New("Invalid ISBN length")
    }
    // Calculate check digit and ensure valid.

    // Create formatted output. This assumes 13 characters for simplicity
    *s = isbn(fmt.Sprintf("%s-%s-%s-%s-%s", out[:3], out[3:4], out[4:9], out[9:12], out[12:]))
    return nil
}

以上只是以适合输出的格式存储ISBN。但是,如果需要,您可以以任何格式存储并具有单独的json.Marshaler实现来格式化输出。

然后,您可以像往常一样在Book中创建一个字段:

type Book struct {
    Id       json.Number `json:"id"`
    Revision int         `json:"revision"`
    ISBN     isbn        `json:"isbn"`
    Title    string      `json:"title"`
}

上面的ISBN解码示例仅供参考。您应该创建一个经过单元测试的完整实现,以确保它正确处理所有预期的输入,并在空/格式错误的输入上引发相应的错误。如果这是一个问题,也可以提高性能。

修改

您无法使用相同的变量在json.Marshal实施中调用json.Marshaler。这将导致无限递归循环,例如

json.Marshal(e) - &gt; e.MarshalJSON - &gt; json.Marshal(e) - &gt; e.MarshalJSON ...

json.Number类型是数字的字符串表示形式。如果您只是想将所有数字输出为字符串,则根本不需要任何自定义类型。只需在代码中使用相关的字符串值即可。例如:

type PublicBook struct {
    Id          string          `json:"id"`
    // all other fields...
}

// Creates the public book representation from a book
func Public(b *Book) *PublicBook {
  return &PublicBook{
     Id: string(b.Id),
  }
}

这将始终输出一个字符串,因为您使用的是string类型,而不是具有自定义JSON编组/解组实现的json.Number类型。

答案 1 :(得分:0)

我有一个适合我的解决方案。 https://play.golang.org/p/Vr4eELsHs1

关键点是,我必须使用fmt.Sprint(*isbn)返回封送器中的字符串。我创建了一个新类型,使用json.Number函数将输入转换为int64,并将其与json自定义封送程序转换为字符串。

感谢您的帮助!