Olmo Cómodo e incómodo. Json.Encoder y Json.Decoder

Seguimos hablando de Elm 0.18 .


Olmo Cómodo e incómodo
Olmo Cómodo e incómodo. Composición
Olmo Cómodo e incómodo. Http, Tarea


Este artículo cubrirá los problemas de codificadores / decodificadores.


Los decodificadores / codificadores se utilizan para:


  1. conversión de respuestas de recursos de terceros (Http, WebSocket, etc.);
  2. interacciones a través de puertos. Te diré más sobre puertos y código nativo en los siguientes artículos.

Como se describió anteriormente, Elm requiere que transformemos los datos externos en tipos de aplicaciones internas. El módulo Json.Decode es responsable de este proceso. El proceso inverso es Json.Encode .


El tipo que define las reglas de decodificación es Json.Decode.Decoder a . Este tipo está parametrizado por el tipo de usuario y determina cómo obtener el tipo de usuario a del objeto JSON.


Para el codificador, solo se define el tipo de resultado: Json.Encode.Value .


Considere ejemplos para el tipo UserData.


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

Decodificador para recibir datos del usuario:


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

La función Json.Decode.map3 acepta un constructor de tipo UserData. A continuación, se transmiten tres decodificadores del tipo de acuerdo con el orden de su declaración en el tipo de usuario UserData.


La función decodeUserData se puede usar junto con las funciones Json.Decode.decodeString o Json.Decode.decodeValue. Un ejemplo de uso de los artículos anteriores.


La función encodeUserData produce una codificación de tipo personalizada del tipo Json.Encode.Value, que puede enviarse. En términos simples, Json.Encode.Value corresponde a un objeto JSON.


Las opciones simples se describen en la documentación, se pueden estudiar sin mucha dificultad. Echemos un vistazo a los casos de la vida que requieren un juego de manos.


Decodificadores de tipo de unión o discriminadores de tipo


Supongamos que tenemos un catálogo de productos. Y cada producto puede tener un número arbitrario de atributos, cada uno de los cuales tiene un tipo de uno de los muchos:


  1. entero
  2. cuerda
  3. enumerado Asume la elección de uno de los valores válidos.

Un objeto JSON es válido de la siguiente manera:


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

No se considerarán los tipos posibles restantes; trabajar con ellos es similar. Entonces, un tipo de producto personalizado tendría la siguiente descripción:


 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 ligeramente los tipos descritos. Hay un producto (Producto), que contiene una lista de atributos / características (Atributos). Cada atributo (Atributo) contiene un identificador, nombre, dimensión y valor. El valor del atributo se describe como tipo de unión, un elemento para cada tipo de valor característico. El tipo Enum describe un valor de un conjunto aceptable y contiene: identificador y valor legible por humanos.


Descripción del decodificador, omita el prefijo Json.Decode por brevedad:


 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 el truco está contenido en la función decodeAttributeValue. El uso de la función Json.Decode.oneOf itera sobre todos los decodificadores válidos para el valor del atributo. En caso de que uno de los decodificadores desempaque con éxito, el valor se etiqueta con la etiqueta correspondiente del tipo AttributeValue.


La codificación de tipo de producto se puede realizar utilizando la función Json.Encode.object, a la que se pasarán los atributos de tipo codificados. Vale la pena prestar atención a la codificación de tipo AttributeValue. De acuerdo con el objeto JSON descrito anteriormente, el codificador se puede describir como el prefijo Json.Encode omitido por brevedad:


 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 puede ver, comparamos las opciones de tipo y usamos los codificadores correspondientes.


Cambie la descripción de los atributos y defínalos utilizando un discriminador de tipos. El objeto de atributo JSON, en este caso, se vería así:


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

En este caso, el discriminador de tipo se almacena en el campo de tipo y determina en qué campo se almacena el valor. Tal estructura de descripción probablemente no sea la más conveniente, pero a menudo se encuentra. Probablemente no valga la pena cambiar la descripción del tipo para este objeto JSON, es mejor mantener los tipos en una forma conveniente para uso interno. En este caso, la descripción del decodificador puede ser la siguiente:


 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" 

En la función decodeAttribute2, primero decodificamos el discriminador, si tiene éxito, decodificamos el valor del atributo. A continuación, decodificamos los campos restantes del tipo Atributo y especificamos el valor obtenido previamente como el valor del campo de valor.


El código fuente del decodificador .


Actualización de tipo parcial


Hay casos en que la API no devuelve todo el objeto, sino solo parte de él. Por ejemplo, al registrarse para ver o cambiar el estado de un objeto. En este caso, es más conveniente recibir inmediatamente el objeto actualizado en el mensaje y ocultar todas las manipulaciones detrás del decodificador.


Por ejemplo, tomamos el mismo producto, pero le agregamos el campo de estado y procesaremos la solicitud para cerrar el producto.


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

O puede usar la función Json.Decode.map.


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

Fecha y hora


Utilizaremos la función Date.fromString, que se implementa utilizando el constructor del tipo Fecha.


 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 se utiliza Timestamp como una representación de fecha / hora, entonces el decodificador en general se puede describir como:


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

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


All Articles