首页 > 解决方案 > 如何解决 F# 和 Azure Functions 之间的 JSON 转换冲突?

问题描述

问题:

如何解决 F# 和 Azure Functions 之间的 JSON 转换冲突?

研究:

我已经回顾了几个相关的问题。但是,他们倾向于专注于返回响应而不是发送请求。

此外,我真的不想装饰我在所有客户端和后端服务中拥有的每种记录类型中的每个成员。

F# 客户端:

我想我可以在客户端使用合同解析器来解决 JSON 冲突。然而,我失败了。

let postTo (baseAddress:string) (resource:string) (payload:Object) =

    let settings = JsonSerializerSettings()
    settings.ContractResolver <- new DefaultContractResolver()

    let json = JsonConvert.SerializeObject(payload, settings);

    let encodedUrl = getAddress baseAddress resource
    let result     = GlobalHttpClient.Instance.PostAsJsonAsync(encodedUrl, json) |> toResult
    result

C# 服务器:

然后我想我可以在服务器上使用合同解析器来解决 JSON 冲突。然而,我还是失败了。

此外,我在尝试反序列化时遇到异常。它抱怨它无法将字符串转换为我的一种记录类型。

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log,
    ExecutionContext context)
{
    try
    {
        using (var streamReader = new StreamReader(req.Body))
        {
            var requestBody = await streamReader.ReadToEndAsync();

            var settings = new JsonSerializerSettings();
            settings.ContractResolver = new DefaultContractResolver();

            var data = JsonConvert.DeserializeObject<DeviceOrigin>(requestBody, settings); // Exception thrown here ! ! !

            ...
        }
    }

}

错误:

无法从 System.String 转换或转换为 Courier.Account.Language+DeviceOrigin。

不需要的分辨率:

如前所述修饰记录类型的属性可以解决问题,但对于我所有解决方案中使用的每个数据传输结构来说都是极其乏味的。

如果我在客户端和服务器上都装饰类型,则以下工作:

[<DataContract>]
type DeviceOrigin = {

    [<field: DataMember(Name="DeviceId")>]
    DeviceId   : string

    [<field: DataMember(Name="Coordinate")>]
    Coordinate : Coordinate
}

[<DataContract>]
type Applicant = {

    [<field: DataMember(Name="ApplicantId")>]
    ApplicantId : string

    [<field: DataMember(Name="FirstName")>]
    FirstName   : string

    [<field: DataMember(Name="LastName")>]
    LastName    : string

    [<field: DataMember(Name="Phone")>]
    Phone       : string

    [<field: DataMember(Name="Email")>]
    Email       : string

    [<field: DataMember(Name="Origin")>]
    Origin      : Coordinate
}

附录:

在 Azure Functions 中将 F# 记录类型返回为 JSON

将 F# 记录类型序列化为 JSON 在每个属性后包含“@”字符

{"ApplicantId@":"","FirstName@":"John","LastName@":"Doe","Phone@":"555.555.5555","Email@":"j.doe@abc.com","Origin@":{"Latitude@":37.421998333333335,"Longitude@":-122.08400000000002}}

标签: c#f#json.net

解决方案


我不确定到底是什么问题,因为该问题缺少一个最小的可重复示例,但是查看@F# 对字段成员附加的其他问题似乎会给一些人带来问题。我尝试在我的机器上复制它,但 NewtonSoft.Json 没有@为我附加。这让我觉得 OP 在客户端和服务器上运行不同版本的 .net 和/或 NewtonSoft.Json,这会导致出现问题。

检查客户端和服务器是否运行相同版本的 NewtonSoft.Json 并且最好是最新版本可能是个好主意。

可以提供帮助的方法是提供您自己NamingStrategy@.

这是我尝试在 .NET Core 3.1 上为 NewtonSoft.Json 12.0.3 运行的完整源代码。我看不到@,但命名策略可能会帮助 OP 走得更远。

open Newtonsoft.Json
open Newtonsoft.Json.Serialization

[<CLIMutable>]
type Coordinate = 
  {
    Latitude    : float
    Longitude   : float
  }

[<CLIMutable>]
type Applicant = 
  {
    ApplicantId : string
    FirstName   : string
    LastName    : string
    Phone       : string
    Email       : string
    Origin      : Coordinate
  }
let applicant = 
  { 
    ApplicantId = "a_0001"
    FirstName   = "Hello"
    LastName    = "There"
    Phone       = "+1 555-2345"
    Email       = "a@b.com"
    Origin      = { Latitude = 23.45; Longitude = 65.43 }
  }

let jsonSerializerSettings = 
  let settings = JsonSerializerSettings ()
  let contractResolver = DefaultContractResolver ()
  // One can override the naming strategy, below prints input and output for SnakeCaseNamingStrategy
  //  One should be able to strip out the @ by providing a custom naming strategy
  let namingStrategy = 
    { new SnakeCaseNamingStrategy() with
        override x.GetDictionaryKey key =
          let key' = base.GetDictionaryKey key
          printfn "GetDictionaryKey: %A -> %A" key key'
          key'
        override x.GetExtensionDataName name = 
          let name' = base.GetExtensionDataName name
          printfn "GetExtensionDataName: %A -> %A" name name'
          name'
        override x.GetPropertyName (name, flag) = 
          let name' = base.GetPropertyName (name, flag)
          printfn "GetPropertyName: (%A, %A) -> %A" name flag name'
          name'
        override x.ResolvePropertyName name = 
          let name' = base.ResolvePropertyName name
          printfn "ResolvePropertyName: %A -> %A" name name'
          name'
    }
  contractResolver.NamingStrategy <- namingStrategy
  settings.ContractResolver <- contractResolver
  settings

let readJson (json: string): Applicant =
  JsonConvert.DeserializeObject<Applicant> (json, jsonSerializerSettings)

let writeJson (applicant: Applicant): string =
  JsonConvert.SerializeObject (applicant, jsonSerializerSettings)

[<EntryPoint>]
let main argv =
  let json = writeJson  applicant
  let app  = readJson   json

  printfn "E: %A" applicant
  printfn "J: %A" json
  printfn "A: %A" app
  0

推荐阅读