Golang에서 커스텀 방식으로 JSON을 언마샬링하는 방법

28941 단어 go
처음으로 gocoinex library 작업을 할 때 JSON 언마샬링 프로세스에 대한 사용자 지정 구성이 필요했습니다. 여기에서 제가 배운 내용을 여러분과 공유하고 싶지만 그 전에 언마샬링 방법에 대해 요약해 보겠습니다. golang의 JSON.

These are real world examples. (coinex exchange API)



먼저 다음과 같이 요청합니다.

raw_response, _ := http.Get("https://api.coinex.com/v1/market/list")


그리고 우리는 다음과 같은 json 객체를 얻을 것입니다:

{
    "code": 0,
    "data": [
        "LTCBCH",
        "ETHBCH",
        "ZECBCH",
        "DASHBCH"
    ],
    "message": "Ok"
}


하지만 우리는 그것을 파싱해야 합니다.
파싱하는 방법은 다양하지만,
json 패키지의 NewDecoder 또는 Unmarshal 기능을 사용할 수 있습니다.struct 또는 map[string]interface{}로 디코딩할 수 있습니다.

선호도에 따라 다르지만 이 경우 NewDecoder와 구조체 조합을 선호합니다.

따라서 다음과 같은 구조체를 만들어야 합니다.

type AllMarketList struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    []string
}


또한 포함된 구조체를 가질 수 있습니다. 예를 들어 마지막 구조체를 두 개로 나눕니다.

type GeneralResponse struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

type AllMarketList struct {
    GeneralResponse
    Data    []string
}


그리고 차이가 없습니다.

마지막으로 NewDecoder를 사용하여 raw_response를 AllMarketList 구조체로 디코딩합니다.

var allMarketList AllMarketList
json.NewDecoder(raw_response.Body).Decode(&allMarketList)


완성된 코드




package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type AllMarketList struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Data    []string
}

func main() {
    raw_response, _ := http.Get("https://api.coinex.com/v1/market/list")

    var allMarketList AllMarketList
    if err := json.NewDecoder(raw_response.Body).Decode(&allMarketList); err != nil {
        fmt.Println(err)
    }
    defer raw_response.Body.Close()
    fmt.Printf("%+v\n", allMarketList)
}


예 2



다음과 같은 json이 있다고 생각하십시오.

{
  "code": 0,
  "data": {
    "date": 1513865441609, # server time when returning
    "ticker": {
        "open": "10", # highest price
        "last": "10.00", # latest price 
        "vol": "110" # 24H volume
    }
  },
  "message" : "Ok"
}


디코딩 프로세스에서 몇 가지 사항을 개선할 예정입니다.
  • 이 경우 Unix 타임스탬프를 구문 분석할 수 없으므로 방법을 제공해야 합니다.
  • "데이터"에서 직접 "티커"키와 "열기", "마지막", "볼륨"에 대한 액세스를 제거하려고 합니다.
  • "마지막"을 "닫기"필드로 내보내야 합니다! ("vol"및 "Volume"도 마찬가지입니다.
  • "open", "last", "vol"은 문자열이 아닌 float이어야 하지만 다음 예를 위해 그대로 두겠습니다.

  • 문제 1과 2는 디코딩하려는 구조체에 UnmarshalJSON 메서드를 구현하여 해결할 수 있습니다.
    문제 3은 json 태그로 쉽게 해결됩니다. (아래 코드에서 언급했습니다)

    최종 구조체는 다음과 같아야 합니다.

    // Final struct
    type SingleMarketStatistics struct {
        Code    int    `json:"code"`
        Message string `json:"message"`
        Data    TickerData
    }
    
    // Inner struct that we should implement to solve problem 2
    type TickerData struct {
        ServerTime CTime   `json:"date"` // CTime is short for CustomTime
        Open       float64 `json:"open"`
        Close      float64 `json:"last"` // Different Attribute name and tag name
        Volume     float64 `json:"vol"` // Different Attribute name and tag name
    }
    
    // Custome time
    // Inner struct that we should implement to solve problem 1
    type CTime struct {
        time.Time
    }
    


    맞춤형 시간 구현




    func (t *CTime) UnmarshalJSON(data []byte) error {
        // Ignore null, like in the main JSON package.
        if string(data) == "null" || string(data) == `""` {
            return nil
        }
        // Fractional seconds are handled implicitly by Parse.
        i, err := strconv.ParseInt(string(data), 10, 64)
        update := time.UnixMilli(i)
        *t = CTime{update}
        return err
    }
    


    그리고 더 이상 오류가 발생하지 않습니다!
    이 메서드는 시간을 CTime으로 디코딩할 때마다 자동으로 사용됩니다(인터페이스 덕분에!)!

    맞춤 데이터 구현



    func (t *TickerData) UnmarshalJSON(data []byte) error {
        if string(data) == "null" || string(data) == `""` {
            return nil
        }
    
        // This is how this json really looks like.
        var realTicker struct {
            ServerTime CTime `json:"date"`
            Ticker     struct {
                // tags also can be omitted when we're using UnmarshalJSON.
                Open   string `json:"open"`
                Close  string `json:"last"`
                Volume string `json:"vol"`
            } `json:"ticker"`
        }
    
        // Unmarshal the json into the realTicker struct.
        if err := json.Unmarshal(data, &realTicker); err != nil {
            return err
        }
    
        // Set the fields to the new struct,
        // with any shape it has,
        // or anyhow you want.
        *t = TickerData{
            ServerTime: realTicker.ServerTime,
            Open:       realTicker.Ticker.Open,
            Close:      realTicker.Ticker.Close,
            Volume:     realTicker.Ticker.Volume,
        }
    
        return nil
    }
    

    이제 이전처럼 NewDecoder를 사용하면 됩니다. 변경할 필요가 없습니다.

    var singleMarketStatistics SingleMarketStatistics 
    json.NewDecoder(raw_response.Body).Decode(&allMarketList)
    


    예 3



    다음과 같은 JSON을 상상해보십시오.

    {
      "asks": [ // This is a array of asks
        [ // This is a array of ONE ask
          "10.00", // Price of ONE ask
          "0.999", // Amount of ONE ask
        ]
      ],
      "bids": [ // Same structure as asks
        [
          "10.00",
          "1.000",
        ]
      ]
    }
    


    명확하게 알 수 있듯이 비전문적인 방식으로 "매수"를 [][]string로 디코딩하고 첫 번째 매도 가격asks[0][0] 및 금액asks[0][1]에 액세스해야 합니다.
    0이 가격이고 1이 금액이라는 것을 누가 기억합니까? 어느 것이 무엇입니까? 😄
    따라서 UnmarshalJSON 메서드에서 관리하겠습니다.
    또한 여기에도 존재하는 이전 예제의 문제 4를 해결할 것입니다.

    type BidAsk struct {
        // Tags are not needed.
        Price  float64 `json:"price"`  // Bid or Ask price
        Amount float64 `json:"amount"` // Bid or Ask amount
    }
    
    func (t *BidAsk) UnmarshalJSON(data []byte) error {
        // Ignore null, like in the main JSON package.
        if string(data) == "null" || string(data) == `""` {
            return nil
        }
    
        // Unmarshal to real type.
        var bisask []string
        if err := json.Unmarshal(data, &bisask); err != nil {
            return err
        }
    
        // Change value type from string to float64.
        price, err := strconv.ParseFloat(bisask[0], 64)
        if err != nil {
            return err
        }
        amount, err := strconv.ParseFloat(bisask[1], 64)
        if err != nil {
            return err
        }
    
        // Map old structure to new structure.
        *t = BidAsk{
            Price:  price,
            Amount: amount,
        }
        return err
    }
    
    type MarketDepth struct {
        Asks   []BidAsk `json:"asks"` // Ask depth
        Bids   []BidAsk `json:"bids"` // Bid depth
    }
    


    다시, 우리는 단순히 다음을 사용합니다.

    var marketDepth MarketDepth 
    json.NewDecoder(raw_response.Body).Decode(&marketDepth)
    


    결과의 아름다움을 즐기십시오.

    for i, ask := range data.Data.Asks {
        fmt.Printf("Ask %v\n", i)
        fmt.Printf("  Price: %v\n", ask.Price) // here is the beauty
        fmt.Printf("  Amount: %v\n", ask.Amount) // here is the beauty
        fmt.Println()
    }
    for i, bid := range data.Data.Bids {
        fmt.Printf("Bid %v\n", i)
        fmt.Printf("  Price: %v\n", bid.Price) // here is the beauty
        fmt.Printf("  Amount: %v\n", bid.Amount) // here is the beauty
        fmt.Println()
    }
    

    좋은 웹페이지 즐겨찾기