我想像下面那样解码json字符串。
[("aaa",[1,2,3,4]),("bbb",[1,2,3])] : List (String, List Int)
并解码到Elm元组列表。
jsdecode=index 0 string
|> andThen xxxxxxx??
如何解码?
processQuestions(questionsCount, difficulty) {
const res = questionsCount.filter(item => item.level === difficulty);
return res.reduce((acc, item) => {
if (acc[item.Id]) {
acc[item.Id] = { ...acc[item.Id], [item.type]: acc[item.Id][item.type]? acc[item.Id][item.type] + 1 : 1};
} else {
acc[item.Id] = { [item.type]: 1};
}
return acc;
}, {})
}
render() {
const { difficulty } = this.props;
const questionsData = this.processQuestions(this.state.questionCount, difficulty);
return Object.entries(questionsData).map(([questionId, data]) => {
return (
<option key={questionId}>{questionId} code: {data.code} non_code: {data['Non-code']}</option>
)
})
}
答案 0 :(得分:2)
这不是一件容易的事,但是在我直接跳去做之前,让我收集一些关于我们要解码的数据的想法:
因此,在我看来,构建正确的解码器的困难反映了处理所有这些极端情况的复杂性。但是,让我们开始定义我们想要的数据:
type alias Record =
( String, List Int )
type alias Model =
List Record
jsonString : String
jsonString =
"[[\"aaa\",1,2,3,4],[\"bbb\",1,2,3]]"
decoder : Decoder Model
decoder =
Decode.list recordDecoder
现在我们需要定义一个表示列表可以包含字符串或整数的类型
type EntryFlags
= EntryId String
| EntryValue Int
type RecordFlags
= List EntryFlags
现在我们的解码器
recordDecoder : Decoder Record
recordDecoder =
Decode.list
(Decode.oneOf
[ Decode.map EntryId Decode.string
, Decode.map EntryValue Decode.int
]
)
|> Decode.andThen buildRecord
因此buildRecord
提取EntryId String
或EntryValue Int
的列表,并建立我们要查找的记录。
buildRecord : List EntryFlags -> Decoder Record
buildRecord list =
case list of
[] ->
Decode.fail "No values were passed"
[ x ] ->
Decode.fail "Only key passed, but no values"
x :: xs ->
case buildRecordFromFlags x xs of
Nothing ->
Decode.fail "Could not build record"
Just value ->
Decode.succeed value
如您所见,我们在解码器中处理许多边缘情况。现在让我们来看看buildRecordFromFlags
:
buildRecordFromFlags : EntryFlags -> List EntryFlags -> Maybe Record
buildRecordFromFlags idEntry valueEntries =
let
maybeId =
case idEntry of
EntryId value ->
Just value
_ ->
Nothing
maybeEntries =
List.map
(\valueEntry ->
case valueEntry of
EntryValue value ->
Just value
_ ->
Nothing
)
valueEntries
|> Maybe.Extra.combine
in
case ( maybeId, maybeEntries ) of
( Just id, Just entries ) ->
Just ( id, entries )
_ ->
Nothing
最后一点,我们使用maybe-extra中的函数来验证初始EntryId
之后的所有值的确是EntryValue
类型的所有值。
您可以在此处查看有效的示例:https://ellie-app.com/3SwvFPjmKYFa1
答案 1 :(得分:1)
这里有两个子问题:1.解码列表,以及2.将其转换为所需的形状。您可以按照@SimonH的建议进行操作,方法是解码为JSON值列表,对它进行后处理,然后 (或在后处理过程中)对内部值进行解码。相反,我宁愿先将其完全解码为自定义类型,然后再完全在Elm类型的领域中进行后处理。
因此,第1步,解码:
type JsonListValue
= String String
| Int Int
decodeListValue : Decode.Decoder JsonListValue
decodeListValue =
Decode.oneOf
[ Decode.string |> Decode.map String
, Decode.int |> Decode.map Int
]
decoder : Decode.Decoder (List (List JsonListValue))
decoder =
Decode.list (Decode.list decodeListValue)
这是可用于解码任何异构数组的基本模式。只需使用oneOf
依次尝试解码器列表,然后将每个解码后的值映射到一个通用类型,通常是一个自定义类型,每种类型的值都有一个简单的构造函数。
然后进入步骤2,进行转换:
extractInts : List JsonListValue -> List Int
extractInts list =
list
|> List.foldr
(\item acc ->
case item of
Int n ->
n :: acc
_ ->
acc
)
[]
postProcess : List JsonListValue -> Result String ( String, List Int )
postProcess list =
case list of
(String first) :: rest ->
Ok ( first, extractInts rest )
_ ->
Err "first item is not a string"
postProcess
将第一项与String
匹配,其余部分运行extractInts
,它们应全部为Int
,然后将它们放到您的元组中想。如果第一项不是String
,它将返回错误。
extractInts
会折叠每一项并将其添加到列表中(如果它是Int
则将其忽略)。请注意,如果某项不是Int
,则它不会返回错误,只是其中不包含错误。
如果值不符合期望(如postProcess
),或者将其“优雅地”处理(如extractInts
),这两个函数都可能会失败。我选择只做其中之一,以说明您可能会同时做这两种情况。
然后,将第3步放在一起:
Decode.decodeString decoder json
|> Result.mapError Decode.errorToString
|> Result.andThen
(List.map postProcess >> Result.Extra.combine)
这里Result.mapError
用于从解码中获取错误,以符合我们从postProcess
得到的错误类型。 Result.Extra.combine
是elm-community/result-extra
中的一个函数,它将List
中的Result
变成Result
中的List
,在此非常方便。