Go:从Neo4j事务中解组结果

huangapple go评论79阅读模式
英文:

Go: Unmarshaling results from Neo4j transaction

问题

我正在尝试找出从Neo4j事务获取的JSON结果的最佳反序列化方法。数据以一组列和数据的形式返回,我正在努力将其适应到Go结构体中。

这是我需要结果存储的结构体:

type Person struct {
  Id   string `json:"id"`
  Name string `json:"name"`
}

type Persons []*Person

这是我从事务中获取的JSON数据:

{
  "results":
  [
    {"columns":[],"data":[]},  
    {"columns":[],"data":[]},
    {"columns":[],"data":[]},
    {"columns":["r"],"data":   // 我只关心这个有数据的结果
      [
        {"row":[{"id":"1","name":"test1"}]},  // 实际结果
        {"row":[{"id":"2","name":"test2"}]},
        {"row":[{"id":"3","name":"test3"}]}
      ]
    }
  ],
  "errors":[]
}

这只是一个特定的示例。其他事务将具有可变数量的results(我只关心最后一个),并且需要反序列化为不同的结构体。这是另一个示例:

这是另一个不同的结构体:

type Phone struct {
  Id     string `json:"id"`
  Number string `json:"number"`
}

type Phones []*Phone

对应的JSON数据:

{
  "results":
  [
    {"columns":[],"data":[]},  
    {"columns":["r"],"data":   // 我只关心这个有数据的结果
      [
        {"row":[{"id":"4","number":"555-1234"}]},  // 实际结果
        {"row":[{"id":"5","number":"555-1232"}]},
        {"row":[{"id":"6","number":"555-1235"}]}
      ]
    }
  ],
  "errors":[]
}

目前,我只是读取整个响应,使用字符串替换修复格式,然后正常进行反序列化,但我想知道是否有更好的方法。

这是我目前希望改进的实现。

// 用于反序列化Neo4j事务结果的结构体。
type transactionResponse struct {
	Results *json.RawMessage   `json:"results"`
	Errors  []transactionError `json:"errors"`
}

// req是我发送给Neo4j的POST请求
resp, err := db.client.Do(req)
defer resp.Body.Close()
if err != nil {
    return fmt.Errorf("Error posting transaction: %s", err)
}

body, err := ioutil.ReadAll(resp.Body)

var txResponse transactionResponse
if err = json.Unmarshal(body, &txResponse); err == nil {
    json.Unmarshal(formatTransactionResponse(*txResponse.Results), &Phones{})
}    

func formatTransactionResponse(data []byte) []byte {
    start := bytes.Index(data, []byte("[\"r\"]")) + 13
    data = data[start:]
    data = bytes.Replace(data, []byte("{\"row\":["), nil, -1)
    data = bytes.Replace(data, []byte("}]},{"), []byte("},{"), -1)
    data = bytes.TrimSuffix(data, []byte("}]}"))

    // 如果没有返回结果的特殊情况
    if len(data) == 4 {
        data = bytes.TrimSuffix(data, []byte("}]"))
    }

    return data
}

希望这可以帮助到你!如果你有任何其他问题,请随时问我。

英文:

I'm trying to figure out the best way to unmarshal the JSON results that you get from a Neo4j transaction. The data comes back as a set of columns and data which I'm trying to shoehorn into a Go struct.

Here is my struct that I need the results to end up in:

type Person struct {
  Id   string `json:id`
  Name string `json:name`
}

type Persons []*Person

And here is the JSON that I'm getting back from my transaction:

{
  "results":
  [
    {"columns":[],"data":[]},  
    {"columns":[],"data":[]},
    {"columns":[],"data":[]},
    {"columns":["r"],"data":   // I only care this result which has data
      [
        {"row":[{"id":"1","name":"test1"}]},  // actual results
        {"row":[{"id":"2","name":"test2"}]},
        {"row":[{"id":"3","name":"test3"}]}
      ]
    }
  ],
  "errors":[]
}

This is just one particular example. Other transactions will have variable numbers of results (only the last of which I ever care about) and will need to be unmarshalled into different structs. I don't want to create have to create a unique result struct for every model struct. Here is another example:

Here is a different struct:

type Phone struct {
  Id     string `json:id`
  Number string `json:number`
}

type Phones []*Phone

And the corresponding JSON:

{
  "results":
  [
    {"columns":[],"data":[]},  
    {"columns":["r"],"data":   // I only care this result which has data
      [
        {"row":[{"id":"4","number":"555-1234"}]},  // actual results
        {"row":[{"id":"5","number":"555-1232"}]},
        {"row":[{"id":"6","number":"555-1235"}]}
      ]
    }
  ],
  "errors":[]
}

Currently I just read out the entire response, fix up the format using string replaces and then unmarshal normally but I'm wondering if there is a better way.

Here is the current implementation that I'm looking to improve.

// Structures to unmarshal Neo4j transaction results into.
type transactionResponse struct {
	Results *json.RawMessage   `json:"results"`
	Errors  []transactionError `json:"errors"`
}

// req is my POST to Neo4j 
resp, err := db.client.Do(req)
defer resp.Body.Close()
if err != nil {
    return fmt.Errorf("Error posting transaction: %s", err)
}

body, err := ioutil.ReadAll(resp.Body)

var txResponse transactionResponse
if err = json.Unmarshal(body, &txResponse); err == nil {
    json.Unmarshal(formatTransactionResponse(*txResponse.Results), &Phones{});
}    

func formatTransactionResponse(data []byte) []byte {
    start := bytes.Index(data, []byte("[\"r\"]")) + 13
    data = data[start:]
    data = bytes.Replace(data, []byte("{\"row\":["), nil, -1)
    data = bytes.Replace(data, []byte("}]},{"), []byte("},{"), -1)
    data = bytes.TrimSuffix(data, []byte("}]}]"))

    //special case if no results are returned
    if len(data) == 4 {
        data = bytes.TrimSuffix(data, []byte("}]"))
    }

    return data
}

答案1

得分: 3

由于Neo4j输出本身就是一个格式良好的JSON结构,你可以将其解组为自己的结构体:

type Phone struct {
    Id     string `json:"id"`
    Number string `json:"number"`
}

type Output struct {
    Results []struct {
        Columns []string `json:"columns"`
        Data    []struct {
            Row []Phone `json:"row"`
        } `json:"data"`
    } `json:"results"`
    Errors []string `json:"errors"`
}

o := Output{}
err := json.Unmarshal(data, &o)

然后你可以对该结构进行任何操作。

for _, result := range o.Results {
    if len(result.Data) > 0 {
        // 这里是你的电话号码。
        for _, d := range result.Data {
            fmt.Println(d.Row)
        }
    }
}

http://play.golang.org/p/tWc677HX1V

不过,如果你需要更快的速度,你的那个有点丑陋的函数可能会更快。

英文:

Since the Neo4j output is itself a well-formed JSON structure, you could unmarhsal it to its own struct:

type Phone struct {
	Id     string `json:"id"`
	Number string `json:"number"`
}

type Output struct {
	Results []struct {
		Columns []string `json:"columns"`
		Data    []struct {
			Row []Phone `json:"row"`
		} `json:"data"`
	} `json:"results"`
	Errors []string `json:"errors"`
}

o := Output{}
err := json.Unmarshal(data, &o)

And then you do whatever you want with that structure.

for _, result := range o.Results {
	if len(result.Data) > 0 {
		// Here are your phones.
		for _, d := range result.Data {
			fmt.Println(d.Row)
		}
	}
}

http://play.golang.org/p/tWc677HX1V

Your ugly-ish function, though, will probably be faster, if you need that.

答案2

得分: 2

请看以下文章:http://blog.golang.org/json-and-go,"Decoding arbitrary data" 部分。

你可以将 JSON 解码为一个具有字符串键的映射,然后使用该映射来访问所需的数据。

英文:

Have a look at the following article: http://blog.golang.org/json-and-go, "Decoding arbitrary data" section.

You can decode a json into a map with string keys and then use this map to access data you need.

答案3

得分: 0

处理这样冗长的协议确实没有好的方法。但最好的解决方案可能是你的方法和Toni的答案的结合,类似于这样:

type transactionResponse struct {
    Results []struct {
        Columns []string
        Data    []struct {
            Row []json.RawMessage
        }
    }
    Errors []string
}

这样可以消除对JSON的字符串处理,同时只需要定义一个新类型。你可以编写一个函数来高效地从这个结构中提取所需的数据并进行解组。

英文:

There is really no good way to deal with such a verbose protocol. But the best solution is probably a cross between yours and the one in Toni's answer, something like this:

type transactionResponse struct {
	Results []struct {
		Columns []string
		Data    []struct {
			Row []json.RawMessage
		}
	}
	Errors []string
}

This would eliminate the string processing of the JSON, while still only requiring one new type to be defined. You could probably write a function to efficiently extract the data you need from this structure and unmarshal it.

答案4

得分: 0

我不会手动处理这个解组过程,只需使用标准的Go数据库API和cq驱动程序即可:

https://github.com/wfreeman/cq

然后你可以使用sqlx,它可以自动将数据解组到结构体中:

https://github.com/jmoiron/sqlx

这样你就可以同时享受sqlx的结构映射和cq的批量请求优化,以及两个库的未来改进。

代码示例:

import (
    _ "github.com/wfreeman/cq"
    "database/sql"
    "github.com/jmoiron/sqlx"
    "log"
)

type Person struct {
    Name  string `db:"first_name"`
    Email string
}

func main() {
    db, err := sqlx.Connect("neo4j-cypher", "http://localhost:7474")
    if err != nil {
        log.Fatalln(err)
    }
    
    people := []Person{}
    db.Select(&people, "MATCH (n:Person) RETURN n.name AS first_name, n.email AS email")
}
英文:

I wouldn't deal with unmarshalling this manually, just use the standard go database APIs and the cq driver:

https://github.com/wfreeman/cq

Then you can use sqlx which does automatic unmarshalling into structs for you:

https://github.com/jmoiron/sqlx

This allows you to benefit from the optimizations in both sqlx for struct mapping and in cq for batching requests, as well as all future improvements from both libraries.

Something like this:

import (
    _ "github.com/wfreeman/cq"
    "database/sql"
    "github.com/jmoiron/sqlx"
    "log"
)

type Person struct {
    Name string `db:"first_name"`
    Email     string
}

func main() {
    db, err := sqlx.Connect("neo4j-cypher", "http://localhost:7474")
    if err != nil {
        log.Fatalln(err)
    }
    
    people := []Person{}
    db.Select(&people, "MATCH (n:Person) RETURN n.name AS first_name, n.email AS email")
}

huangapple
  • 本文由 发表于 2014年3月26日 03:45:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/22644756.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定