我们继续谈论榆木0.18 。
榆树 舒适尴尬
榆树 舒适而笨拙。 组成
榆树 舒适而笨拙。 Http,任务
本文将讨论编码器/解码器的问题。
解码器/编码器用于:
- 转换来自第三方资源(Http,WebSocket等)的响应;
- 通过端口进行交互。 在以下文章中,我将告诉您有关端口和本机代码的更多信息。
如前所述,Elm要求我们将外部数据转换为内部应用程序类型。 Json.Decode模块负责此过程。 反向过程是Json.Encode 。
定义解码规则的类型是Json.Decode.Decoder a 。 此类型由用户类型参数化,并确定如何从JSON对象获取用户类型a。
对于编码器,仅定义结果类型-Json.Encode.Value 。
考虑类型为UserData的示例。
type alias User = { id: Int , name: String , email: String }
用于从用户接收数据的解码器:
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) ]
Json.Decode.map3函数接受UserData类型的构造函数。 接下来,根据在用户类型UserData中的声明顺序,发送该类型的三个解码器。
该decodeUserData函数可以与Json.Decode.decodeString或Json.Decode.decodeValue函数结合使用。 先前文章中的用法示例 。
encodeUserData函数生成Json.Encode.Value类型的自定义类型编码,可以将其发送出去。 简单来说,Json.Encode.Value对应于一个JSON对象。
文档中描述了简单的选项,可以轻松研究它们。 让我们看看需要一些技巧的生活案例。
联合类型解码器或类型鉴别器
假设我们有一个商品目录。 每个产品可以具有任意数量的属性,每个属性都具有以下一种属性:
- 整数
- 弦
- 枚举。 假设选择有效值之一。
JSON对象如下所示有效:
{ “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” } },... ] }
其余可能的类型将不予考虑;使用它们的方式类似。 然后,自定义产品类型将具有以下描述:
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 }
我们将略微讨论所描述的类型。 有一个产品(产品),其中包含属性/特征(Attributes)列表。 每个属性(Attribute)都包含一个标识符,名称,维度和值。 属性值被描述为联合类型,每种类型的特征值都有一个元素。 Enum类型描述了一个可接受的集合中的一个值,并且包含:标识符和人类可读的值。
解码器的描述,为简洁起见,省略Json.Decode前缀:
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)
整个技巧都包含在decodeAttributeValue函数中。 使用Json.Decode.oneOf函数遍历所有有效的解码器以获取属性值。 如果解码器之一成功解包,则该值将使用AttributeValue类型中的相应标记进行标记。
可以使用Json.Encode.object函数执行产品类型编码,已编码的类型属性将传递到该函数中。 值得注意AttributeValue类型的编码。 根据前面描述的JSON对象,可以将编码器描述为Json.Encode前缀,为简洁起见:
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) ]
如您所见,我们比较类型选项并使用相应的编码器。
更改属性的描述并使用类型区分符定义它们。 在这种情况下,JSON属性对象如下所示:
{ “id”: 1, “name”: “Attribute name”, “type”: “int”, “value_int”: 1, “value_string”: null, “value_enum_id”: null, “value_enum_label”: null }
在这种情况下,类型鉴别符存储在类型字段中,并确定将值存储在哪个字段中。 这样的描述结构可能不是最方便的,但是经常发现。 更改此JSON对象的类型描述可能不值得,最好将类型保留为方便使用的形式以供内部使用。 在这种情况下,解码器的描述可以如下:
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"
在解码属性2函数中,我们首先对鉴别符进行解码,如果成功,则对属性值进行解码。 接下来,我们解码Attribute类型的其余字段,并将先前获得的值指定为value字段的值。
解码器的源代码 。
部分类型更新
在某些情况下,API不会返回整个对象,而只是返回一部分。 例如,在注册查看或更改对象状态时。 在这种情况下,更方便的是立即接收消息中的更新对象,并将所有操作隐藏在解码器后面。
例如,我们采用相同的产品,但是在其中添加了状态字段,并将处理关闭该产品的请求。
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} )
或者,您可以使用Json.Decode.map函数。
decodeUpdateStatus : Product -> Decoder Product decodeUpdateStatus product = field “status” int |> map (\newStatus -> { product | status = newStatus} )
日期和时间
我们将使用Date.fromString函数,该函数是使用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 )
如果将时间戳记用作日期/时间表示,那么解码器通常可以描述为:
decodeDateFromTimestamp : Decoder Date.Date decodeDateFromTimestamp = oneOf [ int |> Json.Decode.map toFloat , float ] |> Json.Decode.map Date.fromTime