代码之家  ›  专栏  ›  技术社区  ›  cbll

从键值对中“过滤”JSON对象的最有效方法是什么?

  •  2
  • cbll  · 技术社区  · 6 年前

    我在读 .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
    }
    

    运行时-输出错误。它创建的对象与输入的对象一样多——但是,新对象是空的,例如:

    // ...
     [
     {
      "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"
        }
     ]
    
    1 回复  |  直到 6 年前
        1
  •  4
  •   icza    6 年前

    什么时候? 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,跟踪您当前在对象树中的位置,当检测到必须排除的对象时,不要处理/添加到切片中。有关如何编写的详细信息和想法,请查看以下内容: Go - Decode JSON as it is still streaming in via net/http