【问题标题】:What is the most efficient way of "filtering" out JSON objects from a key-value pair?从键值对中“过滤”出 JSON 对象的最有效方法是什么?
【发布时间】:2018-09-25 06:22:00
【问题描述】:

我正在阅读.json 文件。它是一个有效 JSON 格式的对象数组,例如:

    [
        {
                "Id": 13,
                "Location": "Australia",
                "Content": "Another string"
        },
        {
                "Id": 145,
                "Location": "England",
                "Content": "SomeString"
        },
        {
                "Id": 12,
                "Location": "England",
                "Content": "SomeString"
        },
        {
                "Id": 12331,
                "Location": "Sweden",
                "Content": "SomeString"
        },
        {
                "Id": 213123,
                "Location": "England",
                "Content": "SomeString"
        }
     ]

我想过滤掉这些对象——比如说,删除"Location"不等于"England"的任何东西。

到目前为止,我尝试的是创建一个自定义 UnmarshalJSON 函数。它确实解组它,但它产生的对象是空的 - 和输入一样多。

示例代码:

type languageStruct struct {
    ID                  int     `json:"Id"`
    Location            string  `json:"Location"` 
    Content             string  `json:"Content"`
}

func filterJSON(file []byte) ([]byte, error) {
    var x []*languageStruct

    err := json.Unmarshal(file, &x)
    check(err)

    return json.MarshalIndent(x, "", " ")
}


func (s *languageStruct) UnmarshalJSON(p []byte) error {

    var result struct {
        ID              int     `json:"Id"`
        Location        string  `json:"Location"` 
        Content         string  `json:"Content"`
    }

    err := json.Unmarshal(p, &result)
    check(err)

    // slice of locations we'd like to filter the objects on
    locations := []string{"England"} // Can be more 

    if sliceContains(s.Location, locations) {
        s.ID = result.ID
        s.Location= result.Location
        s.Content = result.Content
    }

    return nil
}

// helper func to check if a given string, f.e. a value of a key-value pair in a json object, is in a provided list
func sliceContains(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            fmt.Println("it's a match!")
            return true
        }
    }
    return false
}

运行时 - 输出错误。它会创建尽可能多的对象 - 但是,新对象是空的,f.e.:

// ...
 [
 {
  "Id": 0,
  "Location": "",
  "Content": ""
 },
 {
  "Id": 0,
  "Location": "",
  "Content": ""
 }
 ]
//...

而我想要的输出,从第一个给定的输入,将是:

[
    {
            "Id": 145,
            "Location": "England",
            "Content": "SomeString"
    },
    {
            "Id": 12,
            "Location": "England",
            "Content": "SomeString"
    },
    {
            "Id": 213123,
            "Location": "England",
            "Content": "SomeString"
    }
 ]

【问题讨论】:

    标签: json go


    【解决方案1】:

    languageStruct.UnmarshalJSON()被调用时,已经有一个languageStruct准备好了,不管你是否填充它的内容(字段),它都会被附加到切片中。

    我建议的最简单的解决方案是正常解组,然后对切片进行后处理:根据您的要求删除元素。这会产生干净的代码,您可以在将来轻松调整/更改。虽然它可以在自定义切片类型[]languageStruct 上实现为自定义编组逻辑,但我仍然不会为此创建自定义编组逻辑,而是将其实现为单独的过滤逻辑。

    这是一个简单的代码解组、过滤和再次编组(注意:没有为此定义/使用自定义编组):

    var x []*languageStruct
    
    err := json.Unmarshal(file, &x)
    if err != nil {
        panic(err)
    }
    
    var x2 []*languageStruct
    for _, v := range x {
        if v.Location == "England" {
            x2 = append(x2, v)
        }
    }
    
    data, err := json.MarshalIndent(x2, "", " ")
    fmt.Println(string(data), err)
    

    这将产生您想要的输出。在Go Playground 上试试吧。

    最快和最复杂的解决方案是使用事件驱动解析和构建状态机,但复杂性会大大增加。这个想法是通过标记处理 JSON,跟踪您当前在对象树中的位置,并且当检测到必须排除的对象时,不要处理/将其添加到您的切片中。有关如何编写的详细信息和想法,请查看此 anwser:Go - Decode JSON as it is still streaming in via net/http

    【讨论】:

    • 感谢您的回复。我想现在,我会坚持第一个,更简单的选择。您能否详细说明“并对切片进行后处理:根据您的要求删除元素”。 - 也许添加一个小样本?我想我在 UnmarshalJSON 函数中做了一些这样的事情,但当然它应该是一个单独的函数。
    • @cbll 添加了一个工作示例(链接到 Go Playground)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    相关资源
    最近更新 更多