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:
- Konvertierung von Antworten aus Ressourcen von Drittanbietern (HTTP, WebSocket usw.);
- 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:
- ganze Zahl;
- Zeichenfolge
- 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