榆树 舒适而笨拙。 Json.Encoder和Json.Decoder

我们继续谈论榆木0.18


榆树 舒适尴尬
榆树 舒适而笨拙。 组成
榆树 舒适而笨拙。 Http,任务


本文将讨论编码器/解码器的问题。


解码器/编码器用于:


  1. 转换来自第三方资源(Http,WebSocket等)的响应;
  2. 通过端口进行交互。 在以下文章中,我将告诉您有关端口和本机代码的更多信息。

如前所述,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 “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) ] 

Json.Decode.map3函数接受UserData类型的构造函数。 接下来,根据在用户类型UserData中的声明顺序,发送该类型的三个解码器。


该decodeUserData函数可以与Json.Decode.decodeString或Json.Decode.decodeValue函数结合使用。 先前文章中的用法示例


encodeUserData函数生成Json.Encode.Value类型的自定义类型编码,可以将其发送出去。 简单来说,Json.Encode.Value对应于一个JSON对象。


文档中描述了简单的选项,可以轻松研究它们。 让我们看看需要一些技巧的生活案例。


联合类型解码器或类型鉴别器


假设我们有一个商品目录。 每个产品可以具有任意数量的属性,每个属性都具有以下一种属性:


  1. 整数
  2. 枚举。 假设选择有效值之一。

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 

Source: https://habr.com/ru/post/zh-CN424437/


All Articles