Orme. Confortable et maladroit. Json.Encoder et Json.Decoder

Nous continuons de parler de l' orme 0,18 .


Orme. Confortable et maladroit
Orme. Confortable et maladroit. La composition
Orme. Confortable et maladroit. Http, Task


Cet article couvrira les problèmes des encodeurs / décodeurs.


Les décodeurs / encodeurs sont utilisés pour:


  1. conversion des réponses de ressources tierces (Http, WebSocket, etc.);
  2. interactions via les ports. Je vais vous en dire plus sur les ports et le code natif dans les articles suivants.

Comme décrit précédemment, Elm nous oblige à transformer les données externes en types d'applications internes. Le module Json.Decode est responsable de ce processus. Le processus inverse est Json.Encode .


Le type qui définit les règles de décodage est Json.Decode.Decoder a . Ce type est paramétré par le type d'utilisateur et détermine comment obtenir le type d'utilisateur a à partir de l'objet JSON.


Pour l'encodeur, seul le type de résultat est défini - Json.Encode.Value .


Prenons des exemples pour le type UserData.


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

Décodeur pour recevoir des données de l'utilisateur:


 decodeUserData : Json.Decode.Decoder UserData decodeUserData = Json.Decode.map3 UserData (Json.Decode.field “id” Json.Decode.int) (Json.Decode.field “name” Json.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) ] 

La fonction Json.Decode.map3 accepte un constructeur de type UserData. Ensuite, trois décodeurs du type sont transmis conformément à l'ordre de leur déclaration dans le type d'utilisateur UserData.


La fonction decodeUserData peut être utilisée conjointement avec les fonctions Json.Decode.decodeString ou Json.Decode.decodeValue. Un exemple d'utilisation des articles précédents.


La fonction encodeUserData produit un encodage de type personnalisé du type Json.Encode.Value, qui peut être envoyé. En termes simples, Json.Encode.Value correspond à un objet JSON.


Des options simples sont décrites dans la documentation, elles peuvent être étudiées sans trop de difficultés. Regardons les cas de vie qui nécessitent un tour de passe-passe.


Décodeurs de type union ou discriminateurs de type


Supposons que nous ayons un catalogue de marchandises. Et chaque produit peut avoir un nombre arbitraire d'attributs, dont chacun a un type de l'un des nombreux:


  1. entier
  2. chaîne
  3. énumérés. Suppose le choix de l'une des valeurs valides.

Un objet JSON est valide comme suit:


 { “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” } },... ] } 

Les autres types possibles ne seront pas pris en compte; leur utilisation est similaire. Un type de produit personnalisé aurait alors la description suivante:


 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 } 

Nous discuterons légèrement des types décrits. Il existe un produit (Produit), qui contient une liste d'attributs / caractéristiques (Attributs). Chaque attribut (attribut) contient un identifiant, un nom, une dimension et une valeur. La valeur d'attribut est décrite comme type d'union, un élément pour chaque type de valeur caractéristique. Le type Enum décrit une valeur d'un ensemble acceptable et contient: l'identifiant et la valeur lisible par l'homme.


Description du décodeur, omettez le préfixe Json.Decode pour plus de brièveté:


 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) 

L'astuce est contenue dans la fonction decodeAttributeValue. L'utilisation de la fonction Json.Decode.oneOf parcourt tous les décodeurs valides pour la valeur d'attribut. En cas de décompression réussie par l'un des décodeurs, la valeur est étiquetée avec l'étiquette correspondante du type AttributeValue.


L'encodage du type de produit peut être effectué à l'aide de la fonction Json.Encode.object, dans laquelle les attributs de type encodés seront transmis. Il convient de prêter attention au codage de type AttributeValue. Conformément à l'objet JSON décrit précédemment, l'encodeur peut être décrit comme le préfixe Json.Encode omis par souci de concision:


 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) ] 

Comme vous pouvez le voir, nous comparons les options de type et utilisons les encodeurs correspondants.


Modifiez la description des attributs et définissez-les à l'aide d'un discriminateur de type. L'objet attribut JSON, dans ce cas, ressemblerait à ceci:


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

Dans ce cas, le discriminateur de type est stocké dans le champ type et détermine dans quel champ la valeur est stockée. Une telle structure de description n'est probablement pas la plus pratique, mais elle est souvent trouvée. Cela ne vaut probablement pas la peine de changer la description du type de cet objet JSON, il est préférable de conserver les types sous une forme pratique pour un usage interne. Dans ce cas, la description du décodeur peut être la suivante:


 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" 

Dans la fonction decodeAttribute2, nous décodons d'abord le discriminateur, en cas de succès, nous décodons la valeur d'attribut. Ensuite, nous décodons les champs restants du type Attribut et spécifions la valeur précédemment obtenue comme valeur du champ de valeur.


Le code source du décodeur .


Mise Ă  jour de type partielle


Dans certains cas, l'API ne renvoie pas l'intégralité de l'objet, mais seulement une partie de celui-ci. Par exemple, lors de l'enregistrement pour visualiser ou modifier le statut d'un objet. Dans ce cas, il est plus pratique de recevoir immédiatement l'objet mis à jour dans le message et de masquer toutes les manipulations derrière le décodeur.


Par exemple, nous prenons le même produit, mais y ajoutons le champ d'état et traiterons la demande de fermeture du produit.


 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 vous pouvez utiliser la fonction Json.Decode.map.


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

Date et heure


Nous utiliserons la fonction Date.fromString, qui est implémentée à l'aide du constructeur de type 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 ) 

Si l'horodatage est utilisé comme une représentation date / heure, le décodeur en général peut être décrit comme:


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

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


All Articles