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

    Wir reden weiter über Elm 0,18 .


    Ulme Bequeme und unbeholfene
    Ulme. Bequem und umständlich. Zusammensetzung
    Elm. Bequem und umständlich. HTTP, Aufgabe


    In diesem Artikel werden wir uns mit Encodern / Decodern beschäftigen.


    Decoder / Encoder werden verwendet für:


    1. Konvertierung von Antworten von Drittanbieter-Ressourcen (HTTP, WebSocket usw.);
    2. Port-Interaktionen. Ausführlicher über Ports und den nativen Code werde ich in den folgenden Artikeln berichten.

    Wie zuvor beschrieben, verlangt Elm von uns, externe Daten in interne Anwendungstypen umzuwandeln. Das Json.Decode- Modul ist für diesen Prozess verantwortlich . Umgekehrter Prozess - Json.Encode .


    Der Typ, der Dekodierungsregeln definiert, ist Json.Decode.Decoder a . Dieser Typ wird durch einen benutzerdefinierten Typ parametrisiert und bestimmt, wie ein benutzerdefinierter Typ a aus einem JSON-Objekt abgerufen wird.


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


    Betrachten Sie Beispiele für den Typ UserData.


    typealiasUser =
      { id: Int
      , name: String
      , email: String
      }

    Decoder zum Empfangen von Benutzerdaten:


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

    Die Funktion Json.Decode.map3 akzeptiert einen Konstruktor vom Typ UserData. Als nächstes werden drei Typdecoder entsprechend der Reihenfolge übertragen, in der sie im UserData-Benutzertyp deklariert sind.


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


    Die Funktion encodeUserData codiert einen benutzerdefinierten Typ in einen Json.Encode.Value-Typ, der gesendet werden kann. Json.Encode.Value entspricht einfach einem JSON-Objekt.


    Einfache Optionen sind in der Dokumentation beschrieben, sie können ohne Schwierigkeiten untersucht werden. Schauen wir uns Lebenssituationen an, die Fingerfertigkeit erfordern.


    Unionstyp-Decoder oder Typdiskriminatoren


    Angenommen, wir haben einen Warenkatalog. Und jedes Produkt kann eine beliebige Anzahl von Attributen haben, von denen jedes vom Typ eins der Gruppe ist:


    1. ganze Zahl;
    2. Linie;
    3. aufgezählt. Übernimmt die Wahl eines der gültigen Werte.

    JSON-Objekt hat die folgende Form:


    {
      “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 Elementtyp die folgende Beschreibung:


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

    Besprechen Sie leicht die beschriebenen Typen. Es gibt ein Produkt (Product), das eine Liste von Attributen / Merkmalen (Attributes) enthält. Jedes Attribut (Attribut) enthält einen Bezeichner, einen Namen, eine Dimension und einen Wert. Ein Attributwert wird als Vereinigungstyp beschrieben, und zwar für jeden Merkmalswerttyp. Der Aufzählungstyp beschreibt einen Wert aus der zulässigen Menge und enthält: einen Bezeichner und einen vom Menschen lesbaren Wert.


    Beschreibung des Decoders, das Präfix Json.Decode wurde der Kürze halber weggelassen:


    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. Mit der Funktion Json.Decode.oneOf werden alle gültigen Decoder für einen Attributwert durchsucht. Im Falle einer erfolgreichen Dekomprimierung durch einen der Decoder wird der Wert mit dem entsprechenden Tag vom Typ AttributeValue gekennzeichnet.


    Die Codierung des Product-Typs kann mit der Funktion Json.Encode.object durchgeführt werden, an die die Attribute des codierten Typs übergeben werden. Beachten Sie die Codierung des AttributeValue-Typs. In Übereinstimmung mit dem zuvor beschriebenen JSON-Objekt kann der Encoder als beschrieben werden, wobei das Präfix Json.Encode der Kürze halber weggelassen wird:


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

    Wie Sie sehen, passen wir die Typoptionen an und verwenden die entsprechenden Encoder.


    Ändern Sie die Beschreibung der Attribute und definieren Sie sie mithilfe des Typdiskriminators. Das Attribut JSON-Objekt würde in diesem Fall folgendermaßen aussehen:


    {
       “id”: 1,
       “name”: “Attributename”,
       “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, aber oft anzutreffende Struktur. Ob es sich lohnt, die Typenbeschreibung für dieses JSON-Objekt zu ändern, ist es wahrscheinlich nicht wert, es ist besser, die Typen für die interne Verwendung in einer bequemen Form zu halten. In diesem Fall kann der Decodierer wie folgt beschrieben werden:


    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 decodieren wir zuerst den Diskriminator, im Erfolgsfall decodieren wir den Attributwert. Als Nächstes decodieren wir die verbleibenden Felder des Attributtyps und geben den zuvor erhaltenen Wert als Wert des Wertfeldes an.


    Quellcode-Decoder .


    Partielle Typaktualisierung


    Es gibt Fälle, in denen die API nicht das gesamte Objekt zurückgibt, sondern nur einen Teil davon. Zum Beispiel bei der Registrierung, um den Status des Objekts anzuzeigen oder zu ändern. In diesem Fall ist es in der Nachricht günstiger, das aktualisierte Objekt sofort zu empfangen und alle Manipulationen hinter dem Decoder zu verbergen.


    Nehmen wir zum Beispiel dasselbe Produkt, fügen jedoch ein Statusfeld hinzu und bearbeiten die Anforderung, um das Produkt zu schließen.


    typealias 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 verwenden die Date.fromString-Funktion, die mit dem Date-Typ-Konstruktor implementiert wird.


    decodeDateFromString : Decoder Date.Date
    decodeDateFromString = 
      string
        |> andThen (\stringDate ->
          caseDate.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 allgemein beschrieben werden als:


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

    Jetzt auch beliebt: