如何使用剩余值作为列表解码异构数组

时间:2018-11-13 07:37:45

标签: json elm

我想像下面那样解码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>
        ) 
   })
}

2 个答案:

答案 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 StringEntryValue 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.combineelm-community/result-extra中的一个函数,它将List中的Result变成Result中的List,在此非常方便。