세계시 UnmarshalJSON 구현
소개
서로 다른 날짜 시간 형식에 대한 파서를 작성하여 두 가지 복잡한 단계를 수행했습니다.
다음이자 마지막 단계는 실제 및 실제 문제를 해결하는 실제 함수를 작성하는 것입니다.
데이터 유형이 다른 다양한 형식의 JSON 필드를 구문 분석해야 합니다.
실제 코드를 작성하기 전에 몇 가지 조사 단계를 거치겠습니다.
I'll use Golang for all code examples, but you can use any other languages base on the provided algorithm.
작업 개요
현재 작업은 JSON 구문 분석 파이프라인에서 사용할 수 있는 Golang 유형을 작성하는 것입니다.
이 필드는 이전 기사에서 이미 검토한 몇 가지 사용 사례를 지원해야 합니다.
다음과 같은 JSON 필드를 받았습니다.
기대
위의 사용 사례를 기반으로 다음과 같은 JSON 필드 목록이 표시될 것으로 예상됩니다.
{
"num_seconds": 1651808102,
"num_milliseconds": 1651808102363,
"num_microseconds": 1651808102363368,
"num_nanoseconds": 1651808102363368423,
"hex_seconds": "0x62749766",
"str_seconds": "1651808102",
"str_milliseconds": "1651808102363",
"str_microseconds": "1651808102363368",
"str_nanoseconds": "1651808102363368423",
"str_rfc3339": "2022-05-06T03:35:02Z",
"str_rfc3339_nano": "2022-05-06T03:35:02.363368423Z",
"str_rfc1123": "Fri, 06 May 2022 03:35:02 UTC",
"str_rfc850": "Friday, 06-May-22 03:35:02 UTC",
"str_rfc822": "06 May 22 03:35 UTC"
}
분명히 전체 유형 목록은 아니지만 이전에 포괄적인 사례 목록을 테스트했으므로 지금 바로 단순화할 수 있습니다.
strconv.ParseInt
함수의 마법 때문에 작동하는지 확인하기 위해 16진수 초 필드를 추가했습니다.UnmarshalJSON 인터페이스 개요
구현하기 전에 사용자 정의 JSON 데이터 유형 구현의 Golang 방식을 고려해야 합니다.
Golang에는 하나의 메서드
UnmarshalJSON([]byte) error
가 있는 인터페이스Unmarshaler가 포함되어 있습니다.이는 JSON 필드를 구문 분석하기 위해 하나의 메서드만 구현해야 함을 의미합니다.
JSON 필드에서 밑줄
_
로 구분된 값이 있는 문자열을 2개의 변수로 구분하기만 하면 되는 간단한 인터페이스 구현의 예가 있습니다.package main
import (
"encoding/json"
"errors"
"strings"
)
type unmarshaled struct {
part1 string
part2 string
}
func (u *unmarshaled) UnmarshalJSON(text []byte) error {
str := string(text)
parts := strings.Split(str, "_")
if len(parts) != 2 {
return errors.New("invalid format")
}
u.part1 = parts[0]
u.part2 = parts[1]
return nil
}
type jsonType struct {
T unmarshaled `json:"t"`
}
func main() {
var jt jsonType
json1 := `{"t": "one_two"}`
err := json.Unmarshal([]byte(json1), &jt)
if err != nil {
panic(err)
}
println(jt.T.part1, jt.T.part2)
json2 := `{"t": "onetwo"}`
err = json.Unmarshal([]byte(json2), &jt)
if err != nil {
panic(err)
}
println(jt.T.part1, jt.T.part2)
}
산출:
"one two"
panic: invalid format
goroutine 1 [running]:
main.main()
/Users/aohorodnyk/projects/anton.ohorodnyk.name/main.go:47 +0x13c
exit status 2
첫 번째 입력은 올바르게 구문 분석되었지만 두 번째 입력은 실패했습니다.
구현
최종 구현에는 다음 단계가 포함됩니다.
strconv.ParseInt
함수를 사용하여 구문 분석합니다. strconv.ParseInt
함수는 문자열의 접두사를 기반으로 유형을 구문 분석합니다. 접두사가 0x
인 경우 16진수로 구문 분석됩니다. 0b는 이진수 등입니다. 기본적으로 십진수로 구문 분석됩니다. 다음 코드 예제는 위에서 설명한 알고리즘을 구현합니다.
package main
import (
"encoding/json"
"errors"
"fmt"
"math"
"strconv"
"strings"
"time"
)
// List of supported time layouts.
var formats = []string{
time.RFC3339Nano,
time.RFC3339,
time.RFC1123Z,
time.RFC1123,
time.RFC850,
time.RFC822Z,
time.RFC822,
time.Layout,
time.RubyDate,
time.UnixDate,
time.ANSIC,
time.StampNano,
time.StampMicro,
time.StampMilli,
time.Stamp,
time.Kitchen,
}
const (
maxNanoseconds = int64(math.MaxInt64)
maxMicroseconds = int64(maxNanoseconds / 1000)
maxMilliseconds = int64(maxMicroseconds / 1000)
maxSeconds = int64(maxMilliseconds / 1000)
minNanoseconds = int64(math.MinInt64)
minMicroseconds = int64(minNanoseconds / 1000)
minMilliseconds = int64(minMicroseconds / 1000)
minSeconds = int64(minMilliseconds / 1000)
)
type InternalTime struct {
time.Time
}
func (it *InternalTime) UnmarshalJSON(data []byte) error {
// Make sure that the input is not empty
if len(data) == 0 {
return errors.New("empty value is not supported")
}
// If the input is not a string, try to parse it as a number, otherwise return an error.
if data[0] != '"' {
timeInt64, err := strconv.ParseInt(string(data), 0, 64)
if err != nil {
return err
}
it.Time = parseTimestamp(timeInt64)
}
// If the input is a string, trim quotes.
str := strings.Trim(string(data), `"`)
// Parse the string as a time using the supported layouts.
parsed, err := parseTime(formats, str)
if err == nil {
it.Time = parsed
return nil
}
// As the final attempt, try to parse the string as a timestamp.
timeInt64, err := strconv.ParseInt(str, 0, 64)
if err == nil {
it.Time = parseTimestamp(timeInt64)
return nil
}
return errors.New("Unsupported time format")
}
type jsonType struct {
NumSeconds InternalTime `json:"num_seconds"`
NumMilliseconds InternalTime `json:"num_milliseconds"`
NumMicroseconds InternalTime `json:"num_microseconds"`
NumNanoseconds InternalTime `json:"num_nanoseconds"`
HexSeconds InternalTime `json:"hex_seconds"`
StrSeconds InternalTime `json:"str_seconds"`
StrMilliseconds InternalTime `json:"str_milliseconds"`
StrMicroseconds InternalTime `json:"str_microseconds"`
StrNanoseconds InternalTime `json:"str_nanoseconds"`
StrRFC3339 InternalTime `json:"str_rfc3339"`
StrRFC3339Nano InternalTime `json:"str_rfc3339_nano"`
StrRFC1123 InternalTime `json:"str_rfc1123"`
StrRFC850 InternalTime `json:"str_rfc850"`
StrRFC822 InternalTime `json:"str_rfc822"`
}
func main() {
var jt jsonType
json1 := `{
"num_seconds": 1651808102,
"num_milliseconds": 1651808102363,
"num_microseconds": 1651808102363368,
"num_nanoseconds": 1651808102363368423,
"hex_seconds": "0x62749766",
"str_seconds": "1651808102",
"str_milliseconds": "1651808102363",
"str_microseconds": "1651808102363368",
"str_nanoseconds": "1651808102363368423",
"str_rfc3339": "2022-05-06T03:35:02Z",
"str_rfc3339_nano": "2022-05-06T03:35:02.363368423Z",
"str_rfc1123": "Fri, 06 May 2022 03:35:02 UTC",
"str_rfc850": "Friday, 06-May-22 03:35:02 UTC",
"str_rfc822": "06 May 22 03:35 UTC"
}`
err := json.Unmarshal([]byte(json1), &jt)
if err != nil {
panic(err)
}
fmt.Println(jt) // {2022-05-05 20:35:02 -0700 PDT 2022-05-05 20:35:02.363 -0700 PDT 2022-05-05 20:35:02.363368 -0700 PDT 2022-05-05 20:35:02.363368423 -0700 PDT 2022-05-05 20:35:02 -0700 PDT 2022-05-05 20:35:02 -0700 PDT 2022-05-05 20:35:02.363 -0700 PDT 2022-05-05 20:35:02.363368 -0700 PDT 2022-05-05 20:35:02.363368423 -0700 PDT 2022-05-06 03:35:02 +0000 UTC 2022-05-06 03:35:02.363368423 +0000 UTC 2022-05-06 03:35:02 +0000 UTC 2022-05-06 03:35:02 +0000 UTC 2022-05-06 03:35:00 +0000 UTC}
}
func parseTimestamp(timestamp int64) time.Time {
switch {
case timestamp < minMicroseconds:
return time.Unix(0, timestamp) // Before 1970 in nanoseconds.
case timestamp < minMilliseconds:
return time.Unix(0, timestamp*int64(time.Microsecond)) // Before 1970 in microseconds.
case timestamp < minSeconds:
return time.Unix(0, timestamp*int64(time.Millisecond)) // Before 1970 in milliseconds.
case timestamp < 0:
return time.Unix(timestamp, 0) // Before 1970 in seconds.
case timestamp < maxSeconds:
return time.Unix(timestamp, 0) // After 1970 in seconds.
case timestamp < maxMilliseconds:
return time.Unix(0, timestamp*int64(time.Millisecond)) // After 1970 in milliseconds.
case timestamp < maxMicroseconds:
return time.Unix(0, timestamp*int64(time.Microsecond)) // After 1970 in microseconds.
}
return time.Unix(0, timestamp) // After 1970 in nanoseconds.
}
func parseTime(formats []string, dt string) (time.Time, error) {
for _, format := range formats {
parsedTime, err := time.Parse(format, dt)
if err == nil {
return parsedTime, nil
}
}
return time.Time{}, fmt.Errorf("could not parse time: %s", dt)
}
위의 코드는 긍정적인 테스트 사례를 다루며 알고리즘이 문제 없이 작동하고 구현이 올바르다는 것을 증명합니다.
결론
최근 3개의 기사에서 우리는 클라이언트의 개발자 경험을 개선하는 데 사용할 수 있는 범용 시간 파서를 구현했습니다.
생산 버그 및 오류의 양을 줄이는 데 도움이 될 수 있습니다.
이 기사가 귀하와 귀하의 팀에 도움이 되기를 바랍니다.
당신이 가질 수 있는 질문을 자유롭게 작성하십시오.
Reference
이 문제에 관하여(세계시 UnmarshalJSON 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/aohorodnyk/universal-time-unmarshaljson-implementation-3okm텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)