Когда мы имеем дело с JSON, всегда есть риск сломать его, особенно если он редактируется людьми.
С другой стороны, Elm со своими кодировщиками/декодерами очень строг к этому.
Давайте посмотрим в этом очень простом руководстве, как Elm может поддерживать создание редактора JSON.
Сначала мы определяем, какую структуру данных мы хотим хранить в формате JSON.
type alias Data = { text : String , logo : Logo , toggle : Bool }
text
и toggle
являются прямым представлением типа JSON. logo
вместо этого является типом Elm, определенным как
type Logo = Elm | Strawberry | Watermelon
Теперь нам нужно кодировать, один для перехода из JSON в Elm и один для перехода из Elm в JSON.
jsonEncoder : Data -> Json.Encode.Value jsonEncoder data = Json.Encode.object [ ( "text", Json.Encode.string <| data.text ) , ( "logo", Json.Encode.string <| toString data.logo ) , ( "toggle", Json.Encode.bool <| data.toggle ) ] jsonDecoder : Json.Decode.Decoder Data jsonDecoder = Json.Decode.Pipeline.decode Data |> Json.Decode.Pipeline.required "text" Json.Decode.string |> Json.Decode.Pipeline.required "logo" (Json.Decode.string |> Json.Decode.andThen logoDecoder) |> Json.Decode.Pipeline.required "toggle" Json.Decode.bool
Декодер для логотипа более сложен, потому что его нужно перевести в шрифт Elm.
В энкодере это преобразование делается просто с помощью toString
.
Для декодера мы используем Json.Decode.andThen
, которые создают декодеры, зависящие от предыдущих результатов. Таким образом, в этом случае он позволяет сначала декодировать значение как строку. В случае успеха результирующая строка декодируется с помощью logoDecoder
.
Это logoDecoder
:
logoDecoder : String -> Json.Decode.Decoder Logo logoDecoder logoString = case logoString of "Elm" -> Json.Decode.succeed Elm "Strawberry" -> Json.Decode.succeed Strawberry "Watermelon" -> Json.Decode.succeed Watermelon _ -> Json.Decode.fail <| "I don't know a logo named " ++ logoString
Затем нам нужно преобразовать строку в JSON и JSON в строку, что-то похожее на JSON.stringify()
и JSON.parse()
в Javascript.
Из данных в строку функция проста:
dataToString : Data -> String dataToString data = Json.Encode.encode 4 <| jsonEncoder data
4
— это уровень отступа в возвращаемой строке.
Обратный путь немного сложнее. Сделаем это в два шага.
Сначала идем от String
к Result
:
stringToResult : String -> Result String Data stringToResult string = Json.Decode.decodeString jsonDecoder string
Затем мы переходим от String
к Data
, но в этот момент нам также требуется последняя версия Data
, чтобы в случае сбоя декодирования мы возвращали самые последние действительные данные. Таким образом, данные всегда будут в правильном формате.
stringToData : String -> Data -> ( Maybe String, Data ) stringToData string oldData = case stringToResult string of Ok newData -> ( Nothing, newData ) Err err -> ( Just err, oldData )
Мы также возвращаем описание ошибки, если при декодировании произошла ошибка.
Чтобы увидеть поведение этих декодеров/кодировщиков в действии, достаточно создать текстовое поле с текстом JSON и попробовать отредактировать: http://guupa.com/elm-unbreakable-json/
Если наше нажатие клавиши приводит JSON в невозможное состояние, приложение восстановит предыдущую версию. (Кроме того, курсор будет нажат в конце текста, это известная проблема, которая на данный момент не имеет простого решения.)
Спасибо за чтение.