代码。Gleam。从 JSON 中提取字段
你好!
有时我们只需要从 JSON 字符串中获取几个字段,而无需任何复杂的操作,例如编写解码器或使用 JSON 模式。
我们将使用 `result.then()` 方便管道传输:如果传递给 `then()` 的结果是 `Ok`,那么我们继续执行;如果结果是 `Error` - 我们在那里停止并从管道返回此错误。因此,在整个管道中保持一致的 `Error` 类型非常重要。
因此,步骤如下。
# Erlang version <= OTP26 gleam add gleam_json@1 # Erlang version >= OTP27 gleam add gleam_json@2
pub type HttpError { // ... JsonParseError(error: JsonParseErrorType, field: String, json_string: String) } pub type JsonParseErrorType { InvalidJsonForParsing ObjectFieldNotFound IntegerFieldNotFound FloatFieldNotFound StringFieldNotFound SeveralFieldsNotFound }
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。我想这比纯文本要好 🤷♂️