英文:
JSON on Golang - Unmarshal Graphite Data
问题
我正在玩弄Golang和JSON,尝试使用从Graphite API提取的数据进行一些计算。
为了简单起见,Graphite发送的数据片段如下所示:
[
{
"target": "server1.loadavg.1min",
"datapoints": [
[
0.16,
1422770850
],
[
0.16,
1422770880
],
[
null,
1422771120
]
]
},
{
"target": "server2.loadavg.1min",
"datapoints": [
[
0.19,
1422770850
],
[
null,
1422771390
],
[
0.14,
1422771420
]
]
}
]
我一直在阅读关于如何使用通用接口{}处理JSON任意数据的go json教程,但我在这个过程中遇到了一些困难。
我尝试定义一个将保存这些数据的结构,读取文件内容并将其解组到这个结构中:
type Graphite struct {
Metric struct {
Target string json:"target"
Datapoints [][]float64 json:"datapoints"
}
}
var results []Graphite
err = json.Unmarshal(d, &r)
if err != nil {
panic(err)
}
fmt.Printf("%v\n", r)
但结果是:
[{{ []}} {{ []}}]
当然,我可以使用通用接口{}来实现,但我想知道我在这里漏掉了什么。
你能帮助我吗?
谢谢!
英文:
I am playing around with Golang and JSON trying to do some calculations with data extracted from Graphite API.
For simplicity, a snippet of the data sent by Graphite is:
[
{
"target": "server1.loadavg.1min",
"datapoints": [
[
0.16,
1422770850
],
[
0.16,
1422770880
],
[
null,
1422771120
]
]
},
{
"target": "server2.loadavg.1min",
"datapoints": [
[
0.19,
1422770850
],
[
null,
1422771390
],
[
0.14,
1422771420
]
]
}
]
I've been reading through the go json tutorial about how to use a generic interface{} for JSON arbitrary data, but I'm struggling with some aspects of the process.
I've tried to define a structure that will hold this data, read the file contents and unmarshal it to this structure:
type Graphite struct {
Metric struct {
Target string `json:"target"`
Datapoints [][]float64 `json:"datapoints"`
}
}
var results []Graphite
err = json.Unmarshal(d, &r)
if err != nil {
panic(err)
}
fmt.Printf("%v\n", r)
But the result is:
[{{ []}} {{ []}}]
I could do it of course with a generic interface{} but I would like to know what am I missing here.
Could you please help me?
Thank you!
答案1
得分: 5
我喜欢从最简单的类型开始,然后逐步扩展。首先,你需要表示你的数据点。
type DataPoint []float64
然后,一个指标(metric)只是一个目标和一系列数据点。
type Metric struct {
Target string `json:"target"`
Points []DataPoint `json:"datapoints"`
}
你不需要Graphite
结构体。你的JSON只是一个Metric
的JSON数组。
var results []Metric
err := json.Unmarshal([]byte(data), &results)
这里是一个完整示例的playground链接。
英文:
I like to start at the simplest type and work my way out. First you need to represent your data point.
type DataPoint []float64
Then a metric is just a target and a series of data points.
type Metric struct {
Target string `json:"target"`
Points []DataPoint `json:"datapoints"`
}
There is no need for your Graphite
struct. Your JSON is just a JSON array of Metric
s.
var results []Metric
err := json.Unmarshal([]byte(data), &results)
Here's a playground link with a complete example.
答案2
得分: 0
上述答案的问题在于它将空值转换为值为0的点,这是不正确的。空值表示“未知”。有人建议使用指向浮点数的指针,因此nil指针表示“没有值”,但这会带来显著的开销(例如,在大多数64位平台上为8字节,更不用说内存解引用开销了)。
更好的方法是使用Go语言的math NaN支持来标记空值,这不需要额外的数据,因为它已经内置在浮点表示中。
您可以使用自定义的Unmarshal函数来实现这一点:
type Metric struct {
Target string
Datapoints []Point
}
type Point struct {
Val float64
Ts uint32
}
var errInvalidFormat = errors.New("invalid format")
func (p *Point) UnmarshalJSON(data []byte) error {
if len(data) < 2 {
return errInvalidFormat
}
// 找到第一个数字或表示“null”的'n'
for (data[0] < 48 || data[0] > 57) && data[0] != 110 {
if len(data) == 1 {
return errInvalidFormat
}
data = data[1:]
}
// 找到逗号
var i int
for i = 0; i < len(data); i++ {
if data[i] == 44 {
break
}
}
if i == 0 {
return errInvalidFormat
}
if bytes.HasPrefix(data[:i], []byte("null")) {
p.Val = math.NaN()
} else {
fl, err := strconv.ParseFloat(string(data[:i]), 64)
if err != nil {
return err
}
p.Val = fl
}
data = data[i:]
if len(data) < 2 {
return errInvalidFormat
}
// 找到第一个数字
for (data[0] < 48 || data[0] > 57) && data[0] != 110 {
if len(data) == 1 {
return errInvalidFormat
}
data = data[1:]
}
// 找到最后一个数字
for i = 0; data[i] >= 48 && data[i] <= 57 && i < len(data); i++ {
}
if i == 0 {
return errInvalidFormat
}
ts, err := strconv.ParseUint(string(data[:i]), 10, 32)
if err != nil {
return err
}
p.Ts = uint32(ts)
return nil
}
完整的示例程序:在playground上
英文:
The problem with the above answer is it turns null points into points with value 0, which is not correct. Null means "not known". Some people suggest using pointers to floats so nil pointer means "no value", but that has a significant overhead (e.g. 8bytes on most 64bit platforms, not to mention the memory dereferencing overhead).
Better to use golang's math NaN support to mark nulls, which requires no additional data as it's built into the float representation.
You can do this with a custom Unmarshal function like so:
type Metric struct {
Target string
Datapoints []Point
}
type Point struct {
Val float64
Ts uint32
}
var errInvalidFormat = errors.New("invalid format")
func (p *Point) UnmarshalJSON(data []byte) error {
if len(data) < 2 {
return errInvalidFormat
}
// find first digit or 'n' for "null"
for (data[0] < 48 || data[0] > 57) && data[0] != 110 {
if len(data) == 1 {
return errInvalidFormat
}
data = data[1:]
}
// find comma
var i int
for i = 0; i < len(data); i++ {
if data[i] == 44 {
break
}
}
if i == 0 {
return errInvalidFormat
}
if bytes.HasPrefix(data[:i], []byte("null")) {
p.Val = math.NaN()
} else {
fl, err := strconv.ParseFloat(string(data[:i]), 64)
if err != nil {
return err
}
p.Val = fl
}
data = data[i:]
if len(data) < 2 {
return errInvalidFormat
}
// find first digit
for (data[0] < 48 || data[0] > 57) && data[0] != 110 {
if len(data) == 1 {
return errInvalidFormat
}
data = data[1:]
}
// find last digit
for i = 0; data[i] >= 48 && data[i] <= 57 && i < len(data); i++ {
}
if i == 0 {
return errInvalidFormat
}
ts, err := strconv.ParseUint(string(data[:i]), 10, 32)
if err != nil {
return err
}
p.Ts = uint32(ts)
return nil
}
Full example program: on playground
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论