Мне нужно прочитать из RSS-канала, который содержит элемент ‹enclosure› в Elm.
Хотя я могу удобно использовать YQL, чтобы напрямую получить JSON-версию
фида, они бывают разных форм. :

//case 1: As object
{ ... enclosure: { url: 'http://...' , length:12345, type:'audio/mpeg' } ... }

//case 2: As list, I'll take the first one
{ ... enclosure : [
          { url:'http://...', length:12345, type:'audio/mpeg'} ,
          { url:'http://...', length:12345, type:'audio/mpeg'} ,
          ...
      ]
}

//case 3: enclosure with type other than audio will be filtered out
{ ... enclosure: { url: 'http://...' , length:12345, type:'video/mp4' } ... }

//case 4: no enclosure, this will be filtered out too
{ ... }

Чтобы декодировать его в такой тип:

type alias Model = 
   { list : List Item }

type alias Item =
   { url : String }

Мы можем сделать это один за другим и сочинить вместе. Сначала мы делаем простой случай, просто декодируем URL-адрес и вводим его как кортеж для дальнейшей обработки.

import Json.Decode as Json

decodeSingleEnclosure : Json.Decoder (String, String)
decodeSingleEnclosure =
    Json.object2 (,)
        ("url" := Json.string)
        ("type" := Json.string)

Нам нужно выполнить проверку этого типа корпуса, как в случае 3. Для этого мы будем использовать andThen.

decodeSingleEnclosureUrl : Json.Decoder String
decodeSingleEnclosureUrl =
    decodeSingleEnclosure
    `Json.andThen`
    (\(url, type_) ->
        if Regex.contains (Regex.regex "^audio/") type_ then
            Json.succeed url
        else
            Json.fail "not audio type"
    )

Чтобы декодировать список вложений в случае 2, мы сначала декодируем его как список, а затем берем первый элемент. Обратите внимание, что мы используем may, чтобы разрешить неудачное декодирование в списке и отфильтровать его с помощью filterMap.

decodeEnclosureListUrl : Json.Decoder String
decodeEnclosureListUrl =
    Json.list (Json.maybe decodeSingleEnclosureUrl)
    `Json.andThen`
    (\list ->   -- List (Maybe String)
        let
            head = list
                |> List.filterMap identity
                |> List.head
        in
            case head of
                Just first ->
                    Json.succeed first
                Nothing ->
                    Json.fail "cannot get enclosure"
    )

Теперь мы можем использовать oneOf для решения как случая 1, так и случая 2.

decodeEnclosureUrl : Json.Decoder String
decodeEnclosureUrl =
    Json.oneOf
        [ "enclosure" := decodeSingleEnclosureUrl
        , "enclosure" := decodeEnclosureListUrl
        ]

Наконец, мы разделяем этот URL-адрес на элемент списка. Если URL не найден, элемент пропускается

decodeItem: Json.Decoder (Maybe Item)
decodeItem =
    Json.maybe (Json.object1 Item decodeEnclosureUrl)


decodeModel : Json.Decoder Model
decodeModel =
    Json.map Model
        ( ("items" := Json.list decodeItem)
          `Json.andThen`
          (\list -> list 
                    |> List.filterMap identity
                    |> Json.succeed
          )
        )

Проверьте весь пример в этом суть. Вы можете скопировать его на elm-lang.org/try, чтобы запустить его.

Декодирование сложного JSON в Elm поначалу показалось мне довольно трудным. Мне нужно обернуть голову и ознакомиться со всей концепцией декодера. Опять же, композиция функций является основным ключом к работе со сложным JSON. Постарайтесь сначала прийти к самому простому случаю, а затем постепенно наращивайте его.

Теги: elm, json, программирование

через 網絡暴民 Блог Джеки http://ift.tt/2dHxyO8