代码。Gleam。从 JSON 中提取字段

你好!

有时我们只需要从 JSON 字符串中获取几个字段,而无需任何复杂的操作,例如编写解码器或使用 JSON 模式。

我们将使用 `result.then()` 方便管道传输:如果传递给 `then()` 的结果是 `Ok`,那么我们继续执行;如果结果是 `Error` - 我们在那里停止并从管道返回此错误。因此,在整个管道中保持一致的 `Error` 类型非常重要。

因此,步骤如下。

  • 安装 gleam_json 包:
  • # Erlang version <= OTP26
    gleam add gleam_json@1
    
    # Erlang version >= OTP27
    gleam add gleam_json@2
  • 在 types.gleam 中写入一些类型:
  • pub type HttpError {
      // ...
      JsonParseError(error: JsonParseErrorType, field: String, json_string: String)
    }
    
    pub type JsonParseErrorType {
      InvalidJsonForParsing
      ObjectFieldNotFound
      IntegerFieldNotFound
      FloatFieldNotFound
      StringFieldNotFound
      SeveralFieldsNotFound
    }
  • 编写一些非常基本的实用程序来缩短 gleam_json 使用语法并简化管道过程。这里我只添加了一些函数来将根 JSON 解析为对象(也可以是数组)并提取整数、浮点数和字符串(自己添加其他函数并不难):
  • import gleam/string
    import gleam/result.{then, replace_error}
    import gleam/json
    import gleam/dynamic
    import gleam/dict
    import types.{
      type HttpError, JsonParseError,
      InvalidJsonForParsing,
      ObjectFieldNotFound,
      IntegerFieldNotFound,
      FloatFieldNotFound,
      StringFieldNotFound,
    }
    
    
    /// Parses JSON string as an object into a dictionary.
    pub fn parse_obj(json_string: String) -> Result(dict.Dict(String, dynamic.Dynamic), HttpError) {
      json_string
      |> json.decode(dynamic.dict(dynamic.string, dynamic.dynamic))
      |> replace_error(JsonParseError(error: InvalidJsonForParsing, field: "", json_string: json_string))
    }
    
    /// Retrieves an object field from the current JSON level.
    pub fn get_obj(
      body: dict.Dict(String, dynamic.Dynamic),
      field: String,
    ) -> Result(dict.Dict(String, dynamic.Dynamic), HttpError) {
      body
      |> dict.get(field)
      |> then(as_dict())
      |> replace_error(JsonParseError(error: ObjectFieldNotFound, field: field, json_string: string.inspect(body)))
    }
    
    /// Retrieves an integer field from the current JSON level.
    pub fn get_int(
      body: dict.Dict(String, dynamic.Dynamic),
      field: String,
    ) -> Result(Int, HttpError) {
      body
      |> dict.get(field)
      |> then(as_int())
      |> replace_error(JsonParseError(error: IntegerFieldNotFound, field: field, json_string: string.inspect(body)))
    }
    
    /// Retrieves a float field from the current JSON level.
    pub fn get_float(
      body: dict.Dict(String, dynamic.Dynamic),
      field: String,
    ) -> Result(Float, HttpError) {
      body
      |> dict.get(field)
      |> then(as_float())
      |> replace_error(JsonParseError(error: FloatFieldNotFound, field: field, json_string: string.inspect(body)))
    }
    
    /// Retrieves a string field from the current JSON level.
    pub fn get_string(
      body: dict.Dict(String, dynamic.Dynamic),
      field: String,
    ) -> Result(String, HttpError) {
      body
      |> dict.get(field)
      |> then(as_string())
      |> replace_error(JsonParseError(error: StringFieldNotFound, field: field, json_string: string.inspect(body)))
    }
    
    /// Replacement for `dynamic.dict(dynamic.string, dynamic.dynamic)` to have a custom `Error` for piping.
    fn as_dict() -> fn(dynamic.Dynamic) -> Result(dict.Dict(String, dynamic.Dynamic), Nil) {
      fn(body: dynamic.Dynamic) {
        body
        |> dynamic.dict(dynamic.string, dynamic.dynamic)
        |> replace_error(Nil)
      }
    }
    
    /// Replacement for `dynamic.int(_)` to have a custom `Error` for piping.
    fn as_int() -> fn(dynamic.Dynamic) -> Result(Int, Nil) {
      fn(body: dynamic.Dynamic) {
        body
        |> dynamic.int()
        |> replace_error(Nil)
      }
    }
    
    /// Replacement for `dynamic.float(_)` to have a custom `Error` for piping.
    fn as_float() -> fn(dynamic.Dynamic) -> Result(Float, Nil) {
      fn(body: dynamic.Dynamic) {
        body
        |> dynamic.float()
        |> replace_error(Nil)
      }
    }
    
    /// Replacement for `dynamic.string(_)` to have a custom `Error` for piping.
    fn as_string() -> fn(dynamic.Dynamic) -> Result(String, Nil) {
      fn(body: dynamic.Dynamic) {
        body
        |> dynamic.string()
        |> replace_error(Nil)
      }
    }
  • 使用你的新实用程序:
  • pub fn main() {
      // {
      //   "name": "Lucy",
      //   "stats": {
      //     "class": "Barbarian",
      //     "power": 6,
      //     "max_hp": 10
      //   },
      //   "pets": {
      //     "Wolfie": {
      //       "type": "dog"
      //     }
      //   }
      // }
      let json_string = "{\"name\": \"Lucy\",\"stats\": {\"class\": \"Barbarian\",\"power\": 6,\"max_hp\": 10},\"pets\": {\"Wolfie\": {\"type\": \"dog\"}}}"
      let json_dict = json_string |> json.parse_obj()
    
      // Get Lucy's name
      json_dict
      |> then(json.get_string(_, "name"))
      |> io.debug()
      // Ok("Lucy")
    
      // Get Wolfie's type
      json_dict
      |> then(json.get_obj(_, "pets"))
      |> then(json.get_obj(_, "Wolfie"))
      |> then(json.get_string(_, "type"))
      |> io.debug()
      // Ok("dog")
    
      // Get something ridiculous
      // Note that we get an error on extracting the `nonsense` field and don't go to `type` out of the box
      json_dict
      |> then(json.get_obj(_, "stats"))
      |> then(json.get_obj(_, "nonsense"))
      |> then(json.get_string(_, "type"))
      |> io.debug()
      // Error(JsonParseError(ObjectFieldNotFound, "nonsense", "dict.from_list([#(\"class\", \"Barbarian\"), #(\"max_hp\", 10), #(\"power\", 6)])"))
    
      // Get something even more ridiculous
      // Now we handle both fields and raising a `SeveralFieldsNotFound` error
      let nonesense =
      json_dict
      |> then(json.get_obj(_, "stats"))
      |> then(json.get_string(_, "nonesense"))
    
      let delirious =
      json_dict
      |> then(json.get_obj(_, "stats"))
      |> then(json.get_int(_, "delirious"))
    
      case nonesense, delirious {
        Ok(nonesense), Ok(delirious) -> Ok(#(nonesense, delirious))
        Error(_), Error(_) -> Error(JsonParseError(error: SeveralFieldsNotFound, field: "stats.{nonesense, delirious}", json_string: json_string))
        Error(err), _ -> Error(err)
        _, Error(err) -> Error(err)
      }
      |> io.debug()
      // Error(JsonParseError(SeveralFieldsNotFound, "stats.{nonesense, delirious}", "{\"name\": \"Lucy\",\"stats\": {\"class\": \"Barbarian\",\"power\": 6,\"max_hp\": 10},\"pets\": {\"Wolfie\": {\"type\": \"dog\"}}}"))
    }

    再见!

    附言

    抱歉语法高亮效果不好。不支持 Gleam,我选择了 Erlang。我想这比纯文本要好 🤷‍♂️