Ulme. Bequem und umstÀndlich. Json.Encoder und Json.Decoder

Wir sprechen weiterhin ĂŒber Elm 0.18 .


Ulme. Bequem und umstÀndlich
Ulme. Bequem und umstÀndlich. Zusammensetzung
Ulme. Bequem und umstÀndlich. HTTP, Aufgabe


Dieser Artikel behandelt die Probleme von Encodern / Decodern.


Decoder / Encoder werden verwendet fĂŒr:


  1. Konvertierung von Antworten aus Ressourcen von Drittanbietern (HTTP, WebSocket usw.);
  2. Interaktionen ĂŒber Ports. In den folgenden Artikeln werde ich Ihnen mehr ĂŒber Ports und nativen Code erzĂ€hlen.

Wie bereits beschrieben, mĂŒssen wir bei Elm externe Daten in interne Anwendungstypen umwandeln. Das Json.Decode- Modul ist fĂŒr diesen Prozess verantwortlich. Der umgekehrte Vorgang ist Json.Encode .


Der Typ, der die Decodierungsregeln definiert, ist Json.Decode.Decoder a . Dieser Typ wird vom Benutzertyp parametrisiert und bestimmt, wie der Benutzertyp a vom JSON-Objekt abgerufen wird.


FĂŒr den Encoder ist nur der Ergebnistyp definiert - Json.Encode.Value .


Betrachten Sie Beispiele fĂŒr den Typ UserData.


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

Decoder zum Empfangen von Daten vom Benutzer:


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

Die Funktion Json.Decode.map3 akzeptiert einen Konstruktor vom Typ UserData. Als nĂ€chstes werden drei Decoder des Typs in der Reihenfolge ihrer Deklaration im Benutzertyp UserData ĂŒbertragen.


Die Funktion decodeUserData kann in Verbindung mit den Funktionen Json.Decode.decodeString oder Json.Decode.decodeValue verwendet werden. Ein Anwendungsbeispiel aus den vorherigen Artikeln.


Die Funktion encodeUserData erzeugt eine benutzerdefinierte Typcodierung vom Typ Json.Encode.Value, die gesendet werden kann. In einfachen Worten entspricht Json.Encode.Value einem JSON-Objekt.


Einfache Optionen sind in der Dokumentation beschrieben, sie können ohne große Schwierigkeiten untersucht werden. Schauen wir uns LebensfĂ€lle an, die etwas FingerspitzengefĂŒhl erfordern.


Union Decoder oder Typ Diskriminatoren


Angenommen, wir haben einen Warenkatalog. Und jedes Produkt kann eine beliebige Anzahl von Attributen haben, von denen jedes einen Typ von einem der vielen hat:


  1. ganze Zahl;
  2. Zeichenfolge
  3. aufgezĂ€hlt. Nimmt die Wahl eines der gĂŒltigen Werte an.

Ein JSON-Objekt ist wie folgt gĂŒltig:


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

Die verbleibenden möglichen Typen werden nicht berĂŒcksichtigt, die Arbeit mit ihnen ist Ă€hnlich. Dann hĂ€tte ein benutzerdefinierter Produkttyp die folgende Beschreibung:


 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 } 

Wir werden die beschriebenen Typen leicht diskutieren. Es gibt ein Produkt (Produkt), das eine Liste von Attributen / Merkmalen (Attribute) enthĂ€lt. Jedes Attribut (Attribut) enthĂ€lt einen Bezeichner, einen Namen, eine Dimension und einen Wert. Der Attributwert wird als Vereinigungstyp beschrieben, ein Element fĂŒr jeden Typ von Merkmalwert. Der Enum-Typ beschreibt einen Wert aus einem akzeptablen Satz und enthĂ€lt: Bezeichner und lesbaren Wert.


Beschreibung des Decoders, der KĂŒrze halber das PrĂ€fix Json.Decode weglassen:


 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) 

Der gesamte Trick ist in der Funktion decodeAttributeValue enthalten. Bei Verwendung der Funktion Json.Decode.oneOf werden alle gĂŒltigen Decoder fĂŒr den Attributwert durchlaufen. Bei erfolgreichem Entpacken durch einen der Decoder wird der Wert mit dem entsprechenden Tag vom Typ AttributeValue gekennzeichnet.


Die Produkttypcodierung kann mit der Funktion Json.Encode.object durchgefĂŒhrt werden, an die codierte Typattribute ĂŒbergeben werden. Es lohnt sich, auf die Codierung vom Typ AttributeValue zu achten. In Übereinstimmung mit dem zuvor beschriebenen JSON-Objekt kann der Encoder wie folgt beschrieben werden: Das Json.Encode-PrĂ€fix wird der KĂŒrze halber weggelassen:


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

Wie Sie sehen können, vergleichen wir die Typoptionen und verwenden die entsprechenden Encoder.


Ändern Sie die Beschreibung der Attribute und definieren Sie sie mithilfe eines Typdiskriminators. Das JSON-Attributobjekt wĂŒrde in diesem Fall folgendermaßen aussehen:


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

In diesem Fall wird der Typdiskriminator im Typfeld gespeichert und bestimmt, in welchem ​​Feld der Wert gespeichert wird. Eine solche Beschreibungsstruktur ist wahrscheinlich nicht die bequemste, wird aber hĂ€ufig gefunden. Es lohnt sich wahrscheinlich nicht, die Typbeschreibung fĂŒr dieses JSON-Objekt zu Ă€ndern. Es ist besser, die Typen fĂŒr den internen Gebrauch in einer geeigneten Form aufzubewahren. In diesem Fall kann die Beschreibung des Decoders wie folgt sein:


 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" 

In der Funktion decodeAttribute2 dekodieren wir zuerst den Diskriminator. Wenn dies erfolgreich ist, dekodieren wir den Attributwert. Als nÀchstes dekodieren wir die verbleibenden Felder des Attributtyps und geben den zuvor erhaltenen Wert als Wert des Wertefelds an.


Der Quellcode des Decoders .


Teilweise Typaktualisierung


Es gibt FĂ€lle, in denen die API nicht das gesamte Objekt, sondern nur einen Teil davon zurĂŒckgibt. Zum Beispiel, wenn Sie sich registrieren, um den Status eines Objekts anzuzeigen oder zu Ă€ndern. In diesem Fall ist es bequemer, das aktualisierte Objekt sofort in der Nachricht zu empfangen und alle Manipulationen hinter dem Decoder zu verbergen.


Zum Beispiel nehmen wir dasselbe Produkt, fĂŒgen jedoch das Statusfeld hinzu und verarbeiten die Anforderung zum Schließen des Produkts.


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

Oder Sie können die Funktion Json.Decode.map verwenden.


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

Datum und Uhrzeit


Wir werden die Date.fromString-Funktion verwenden, die mit dem Konstruktor des Date-Typs implementiert wird.


 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 ) 

Wenn der Zeitstempel als Datums- / Zeitdarstellung verwendet wird, kann der Decoder im Allgemeinen wie folgt beschrieben werden:


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

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


All Articles