Elm. Confortável e estranho. Json.Encoder e Json.Decoder

Continuamos a falar sobre o olmo 0,18 .


Elm. Confortável e desajeitado
Elm. Confortável e estranho. Composição:
Elm. Confortável e estranho. Http, Tarefa


Este artigo abordará os problemas dos codificadores / decodificadores.


Os decodificadores / codificadores são usados ​​para:


  1. conversão de respostas de recursos de terceiros (Http, WebSocket, etc.);
  2. interações através de portas. Vou falar mais sobre portas e código nativo nos seguintes artigos.

Conforme descrito anteriormente, o Elm exige que transformemos dados externos em tipos de aplicativos internos. O módulo Json.Decode é responsável por esse processo. O processo inverso é Json.Encode .


O tipo que define as regras de decodificação é Json.Decode.Decoder a . Este tipo é parametrizado pelo tipo de usuário e determina como obter o tipo de usuário a do objeto JSON.


Para o codificador, apenas o tipo de resultado é definido - Json.Encode.Value .


Considere exemplos para o tipo UserData.


type alias User = { id: Int , name: String , email: String } 

Decodificador para receber dados do usuário:


 decodeUserData : Json.Decode.Decoder UserData decodeUserData = Json.Decode.map3 UserData (Json.Decode.field “id” Json.Decode.int) (Json.Decode.field “nameJson.Decode.string) (Json.Decode.field “email” Json.Decode.string) encodeUserData : UserData -> Json.Encode.Value encodeUserData userData = Json.Encode.object [ ( “id”, Json.Encode.int userData.id) , ( “name”, Json.Encode.string userData.name) , ( “email”, Json.Encode.string userData.email) ] 

A função Json.Decode.map3 aceita um construtor do tipo UserData. Em seguida, três decodificadores do tipo são transmitidos de acordo com a ordem de sua declaração no tipo de usuário UserData.


A função decodeUserData pode ser usada em conjunto com as funções Json.Decode.decodeString ou Json.Decode.decodeValue. Um exemplo de uso dos artigos anteriores.


A função encodeUserData produz uma codificação de tipo personalizado do tipo Json.Encode.Value, que pode ser enviada. Em termos simples, Json.Encode.Value corresponde a um objeto JSON.


Opções simples são descritas na documentação, elas podem ser estudadas sem muita dificuldade. Vejamos os casos da vida que requerem um pouco de mão.


Decodificadores ou discriminadores de tipo de união


Suponha que tenhamos um catálogo de mercadorias. E cada produto pode ter um número arbitrário de atributos, cada um dos quais com o tipo de um dos muitos:


  1. inteiro
  2. corda
  3. enumerado. Assume a escolha de um dos valores válidos.

Um objeto JSON é válido da seguinte maneira:


 { “id”: 1, “name”: “Product name”, “price”: 1000, “attributes”: [ { “id”: 1, “name”: “Length”, “unit”: “meters”, “value”: 100 }, { “id”: 1, “name”: “Color”, “unit”: “”, “value”: { “id”: 1, “label”: “red” } },... ] } 

Os demais tipos possíveis não serão considerados; o trabalho com eles é semelhante. Em seguida, um tipo de produto personalizado teria a seguinte descrição:


 type alias Product = { id: Int , name: String , price: Int , attributes: Attributes } type alias Attributes = List Attribute type alias Attribute = { id: Int , name: String , unit: String , value: AttributeValue } type AttributeValue = IntValue Int | StringValue String | EnumValue Enum type alias Enum = { id: Int , label: String } 

Discutiremos um pouco os tipos descritos. Existe um produto (Produto), que contém uma lista de atributos / características (Atributos). Cada atributo (Atributo) contém um identificador, nome, dimensão e valor. O valor do atributo é descrito como tipo de união, um elemento para cada tipo de valor da característica. O tipo Enum descreve um valor de um conjunto aceitável e contém: identificador e valor legível por humanos.


Descrição do decodificador, omita o prefixo Json.Decode por questões de brevidade:


 decodeProduct : Decoder Product decodeProduct = map4 Product (field “id” int) (field “name” string) (field “price” int) (field “attributes” decodeAttributes) decodeAttributes : Decoder Attributes decodeAttributes = list decodeAttribute decodeAttribute : Decoder Attribute decodeAttribute = map4 Attribute (field “id” int) (field “name” string) (field “unit” string) (field “value” decodeAttributeValue) decodeAttributeValue : Decoder AttributeValue decodeAttributeValue = oneOf [ map IntValue int , map StringValue string , map EnumValue decodeEnumValue ] decodeEnumValue : Decoder Enum decodeEnumValue = map2 Enum (field “id” int) (field “label” string) 

Todo o truque está contido na função decodeAttributeValue. O uso da função Json.Decode.oneOf itera sobre todos os decodificadores válidos para o valor do atributo. Em caso de descompactação bem-sucedida por um dos decodificadores, o valor é marcado com a tag correspondente do tipo AttributeValue.


A codificação do tipo de produto pode ser executada usando a função Json.Encode.object, na qual os atributos do tipo codificado serão transmitidos. Vale a pena prestar atenção na codificação do tipo AttributeValue. De acordo com o objeto JSON descrito anteriormente, o codificador pode ser descrito como, o prefixo Json.Encode é omitido por questões de brevidade:


 encodeAttributeValue : AttributeValue -> Value encodeAttributeValue attributeValue = case attributeValue of IntValue value -> int value StringValue value -> string value EnumValue value -> object [ (“id”, int value.id) , (“id”, string value.label) ] 

Como você pode ver, comparamos as opções de tipo e usamos os codificadores correspondentes.


Altere a descrição dos atributos e defina-os usando um discriminador de tipo. O objeto de atributo JSON, nesse caso, ficaria assim:


 { “id”: 1, “name”: “Attribute name”, “type”: “int”, “value_int”: 1, “value_string”: null, “value_enum_id”: null, “value_enum_label”: null } 

Nesse caso, o discriminador de tipo é armazenado no campo de tipo e determina em qual campo o valor é armazenado. Essa estrutura de descrição provavelmente não é a mais conveniente, mas é frequentemente encontrada. Provavelmente não vale a pena alterar a descrição do tipo para esse objeto JSON, é melhor manter os tipos em um formato conveniente para uso interno. Nesse caso, a descrição do decodificador pode ser a seguinte:


 decodeAttribute2 : Decoder Attribute decodeAttribute2 = field "type" string |> andThen decodeAttributeValueType |> andThen (\attributeValue -> map4 Attribute (field "id" int) (field "name" string) (field "unit" string) (succeed attributeValue) ) decodeAttributeValueType : String -> Decoder AttributeValue decodeAttributeValueType valueType = case valueType of "int" -> field "value_int" int |> Json.Decode.map IntValue "string" -> field "value_string" string |> Json.Decode.map StringValue "enum" -> map2 Enum (field "value_enum_id" int) (field "value_enum_label" string) |> Json.Decode.map EnumValue _ -> Json.Decode.fail "Unknown attribute type" 

Na função decodeAttribute2, decodificamos primeiro o discriminador; se for bem-sucedido, decodificamos o valor do atributo. Em seguida, decodificamos os campos restantes do tipo Atributo e indicamos o valor obtido anteriormente como o valor do campo de valor.


O código fonte do decodificador .


Atualização de tipo parcial


Há casos em que a API não retorna o objeto inteiro, mas apenas parte dele. Por exemplo, ao se registrar para visualizar ou alterar o status de um objeto. Nesse caso, é mais conveniente receber imediatamente o objeto atualizado na mensagem e ocultar todas as manipulações atrás do decodificador.


Por exemplo, pegamos o mesmo produto, mas adicionamos o campo de status e processaremos a solicitação para fechar o produto.


 type alias Product = { id: Int , name: String , price: Int , attributes: Attributes , status: Int } decodeUpdateStatus : Product -> Decoder Product decodeUpdateStatus product = field “status” int |> andThen (\newStatus -> succeed { product | status = newStatus} ) 

Ou você pode usar a função Json.Decode.map.


 decodeUpdateStatus : Product -> Decoder Product decodeUpdateStatus product = field “status” int |> map (\newStatus -> { product | status = newStatus} ) 

Data e hora


Usaremos a função Date.fromString, que é implementada usando o construtor do tipo Date.


 decodeDateFromString : Decoder Date.Date decodeDateFromString = string |> andThen (\stringDate -> case Date.fromString stringDate of Ok date -> Json.Decode.succeed date Err reason -> Json.Decode.fail reason ) 

Se o registro de data e hora for usado como uma representação de data / hora, o decodificador em geral poderá ser descrito como:


 decodeDateFromTimestamp : Decoder Date.Date decodeDateFromTimestamp = oneOf [ int |> Json.Decode.map toFloat , float ] |> Json.Decode.map Date.fromTime 

Source: https://habr.com/ru/post/pt424437/


All Articles