Elm - 使用动态密钥解码 Json
Elm - Decode Json with dynamic keys
我想解码如下所示的 Json 文件:
{ 'result': [
{'id': 1, 'model': 'online', 'app_label': 'some_app_users'},
{'id': 2, 'model': 'rank', 'app_label': 'some_app_users'},
]}
或者像这样:
{ 'result': [
{'id': 1, 'name': 'Tom', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
{'id': 1, 'name': 'Bob', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
]}
基本上,result
下的内容是具有相同键的字典列表 - 但我事先不知道这些键,我也不知道知道它们的值类型(int、string、dict 等)。
目标是显示数据库表的内容; Json 包含 SQL 查询的结果。
我的解码器看起来像这样(未编译):
tableContentDecoder : Decode.Decoder (List dict)
tableContentDecoder =
Decode.at [ "result" ] (Decode.list Decode.dict)
我是这样使用的:
Http.send GotTableContent (Http.get url tableContentDecoder)
我遇到了这个错误:
Function list
is expecting the argument to be:
Decode.Decoder (Dict.Dict String a)
But it is:
Decode.Decoder a -> Decode.Decoder (Dict.Dict String a)
使用字典解码器的正确语法是什么?那行得通吗?我找不到任何通用的 Elm 解码器...
Decode.list
是一个接受类型 Decoder a
值的函数,returns 接受类型 Decoder (List a)
的值。 Decode.dict
也是一个函数,它采用 Decoder a
类型的值,returns 是 Decoder (Dict String a)
的解码器。这告诉我们两件事:
- 我们需要先将解码器值传递给
Decode.dict
,然后再传递给 Decoder.list
- Dict 可能不适合您的用例,因为 Dict 只能在两个固定类型之间映射,并且不支持嵌套值,例如
'skills': {'key': 'value', ...}
Elm 不提供通用解码器。这样做的动机与 Elm 对 "no runtime errors" 的保证有关。在与外部世界打交道时,Elm 需要保护其运行时免受外部故障、错误等可能性的影响。 Elm 这样做的主要机制是类型。 Elm 只允许正确描述的数据,这样做可以消除通用解码器引入错误的可能性。
由于您的主要目标是显示内容,因此 Dict String String
之类的方法可能有效,但这取决于您的数据嵌套的深度。您可以通过对代码进行少量修改来实现此目的:Decode.at [ "result" ] <| Decode.list (Decode.dict Decode.string)
.
另一种可能性是使用 Decode.value
和 Decode.andThen
来测试指示我们正在读取的 table 的值。
我们的解码器具有单一一致的类型很重要,这意味着我们需要将可能的结果表示为求和类型。
-- represents the different possible tables
type TableEntry
= ModelTableEntry ModelTableFields
| UserTableEntry UserTableFields
| ...
-- we will use this alias as a constructor with `Decode.map3`
type alias ModelTableFields =
{ id : Int
, model : String
, appLabel : String
}
type alias UserTableFields =
{ id : Int
, ...
}
tableContentDecoder : Decoder (List TableEntry)
tableContentDecoder =
Decode.value
|> Decode.andThen
\value ->
let
tryAt field =
Decode.decodeValue
(Decode.at ["result"] <|
Decode.list <|
Decode.at [field] Decode.string)
value
in
-- check the results of various attempts and use
-- the appropriate decoder based on results
case ( tryAt "model", tryAt "name", ... ) of
( Ok _, _, ... ) ->
decodeModelTable
( _, Ok _, ... ) ->
decodeUserTable
...
(_, _, ..., _ ) ->
Decode.fail "I don't know what that was!"
-- example decoder for ModelTableEntry
-- Others can be constructed in a similar manner but, you might
-- want to use NoRedInk/Json.Decode.Pipline for more complex data
decodeModel : Decoder (List TableEntry)
decodeModel =
Decode.list <|
Decode.map3
(ModelTableEntry << ModelTableFields)
(Decode.field "id" Decode.int)
(Decode.field "model" Decode.string)
(Decode.field "app_label" Decode.string)
decodeUser : Decoder (List TableEntry)
decodeUser =
...
可以公平地说,这比大多数其他语言让您解析的工作要多得多 JSON。但是,这样做的好处是能够使用外部数据而不必担心异常。
一种思考方式是,Elm 让您预先完成所有工作。其他语言可能会让您起床和 运行 更快,但是,做更少的事情来帮助您实现 stable。
我不知道如何让 Decode.dict
工作,所以我更改了 Json 并拆分了列和结果:
data={
'columns': [column.name for column in cursor.description],
'results': [[str(column) for column in record] for record in cursor.fetchall()]
}
为了简单起见,我还必须将所有结果转换为字符串。 Json 例如 'id': "1"
。
这样 Json 完成后,Elm 代码就非常简单了:
type alias QueryResult =
{ columns : List String, results : List (List String) }
tableContentDecoder : Decode.Decoder QueryResult
tableContentDecoder =
Decode.map2
QueryResult
(Decode.field "columns" (Decode.list Decode.string))
(Decode.field "results" (Decode.list (Decode.list Decode.string)))
我想解码如下所示的 Json 文件:
{ 'result': [
{'id': 1, 'model': 'online', 'app_label': 'some_app_users'},
{'id': 2, 'model': 'rank', 'app_label': 'some_app_users'},
]}
或者像这样:
{ 'result': [
{'id': 1, 'name': 'Tom', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
{'id': 1, 'name': 'Bob', 'skills': {'key': 'value', ...}, {'key': 'value', ...}},
]}
基本上,result
下的内容是具有相同键的字典列表 - 但我事先不知道这些键,我也不知道知道它们的值类型(int、string、dict 等)。
目标是显示数据库表的内容; Json 包含 SQL 查询的结果。
我的解码器看起来像这样(未编译):
tableContentDecoder : Decode.Decoder (List dict)
tableContentDecoder =
Decode.at [ "result" ] (Decode.list Decode.dict)
我是这样使用的:
Http.send GotTableContent (Http.get url tableContentDecoder)
我遇到了这个错误:
Function
list
is expecting the argument to be: Decode.Decoder (Dict.Dict String a)But it is: Decode.Decoder a -> Decode.Decoder (Dict.Dict String a)
使用字典解码器的正确语法是什么?那行得通吗?我找不到任何通用的 Elm 解码器...
Decode.list
是一个接受类型 Decoder a
值的函数,returns 接受类型 Decoder (List a)
的值。 Decode.dict
也是一个函数,它采用 Decoder a
类型的值,returns 是 Decoder (Dict String a)
的解码器。这告诉我们两件事:
- 我们需要先将解码器值传递给
Decode.dict
,然后再传递给Decoder.list
- Dict 可能不适合您的用例,因为 Dict 只能在两个固定类型之间映射,并且不支持嵌套值,例如
'skills': {'key': 'value', ...}
Elm 不提供通用解码器。这样做的动机与 Elm 对 "no runtime errors" 的保证有关。在与外部世界打交道时,Elm 需要保护其运行时免受外部故障、错误等可能性的影响。 Elm 这样做的主要机制是类型。 Elm 只允许正确描述的数据,这样做可以消除通用解码器引入错误的可能性。
由于您的主要目标是显示内容,因此 Dict String String
之类的方法可能有效,但这取决于您的数据嵌套的深度。您可以通过对代码进行少量修改来实现此目的:Decode.at [ "result" ] <| Decode.list (Decode.dict Decode.string)
.
另一种可能性是使用 Decode.value
和 Decode.andThen
来测试指示我们正在读取的 table 的值。
我们的解码器具有单一一致的类型很重要,这意味着我们需要将可能的结果表示为求和类型。
-- represents the different possible tables
type TableEntry
= ModelTableEntry ModelTableFields
| UserTableEntry UserTableFields
| ...
-- we will use this alias as a constructor with `Decode.map3`
type alias ModelTableFields =
{ id : Int
, model : String
, appLabel : String
}
type alias UserTableFields =
{ id : Int
, ...
}
tableContentDecoder : Decoder (List TableEntry)
tableContentDecoder =
Decode.value
|> Decode.andThen
\value ->
let
tryAt field =
Decode.decodeValue
(Decode.at ["result"] <|
Decode.list <|
Decode.at [field] Decode.string)
value
in
-- check the results of various attempts and use
-- the appropriate decoder based on results
case ( tryAt "model", tryAt "name", ... ) of
( Ok _, _, ... ) ->
decodeModelTable
( _, Ok _, ... ) ->
decodeUserTable
...
(_, _, ..., _ ) ->
Decode.fail "I don't know what that was!"
-- example decoder for ModelTableEntry
-- Others can be constructed in a similar manner but, you might
-- want to use NoRedInk/Json.Decode.Pipline for more complex data
decodeModel : Decoder (List TableEntry)
decodeModel =
Decode.list <|
Decode.map3
(ModelTableEntry << ModelTableFields)
(Decode.field "id" Decode.int)
(Decode.field "model" Decode.string)
(Decode.field "app_label" Decode.string)
decodeUser : Decoder (List TableEntry)
decodeUser =
...
可以公平地说,这比大多数其他语言让您解析的工作要多得多 JSON。但是,这样做的好处是能够使用外部数据而不必担心异常。
一种思考方式是,Elm 让您预先完成所有工作。其他语言可能会让您起床和 运行 更快,但是,做更少的事情来帮助您实现 stable。
我不知道如何让 Decode.dict
工作,所以我更改了 Json 并拆分了列和结果:
data={
'columns': [column.name for column in cursor.description],
'results': [[str(column) for column in record] for record in cursor.fetchall()]
}
为了简单起见,我还必须将所有结果转换为字符串。 Json 例如 'id': "1"
。
这样 Json 完成后,Elm 代码就非常简单了:
type alias QueryResult =
{ columns : List String, results : List (List String) }
tableContentDecoder : Decode.Decoder QueryResult
tableContentDecoder =
Decode.map2
QueryResult
(Decode.field "columns" (Decode.list Decode.string))
(Decode.field "results" (Decode.list (Decode.list Decode.string)))