代码。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。我想这比纯文本要好 🤷♂️