简体   繁体   English

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

[英]How do I resolve JSON conversion conflicts between F# and Azure Functions?

Question:问题:

How do I resolve JSON conversion conflicts between F# and Azure Functions?如何解决 F# 和 Azure Functions 之间的 JSON 转换冲突?

Research:研究:

I've reviewed several related questions.我已经回顾了几个相关的问题。 However, they tend to focus on returning a response versus sending a request.然而,他们倾向于专注于返回响应而不是发送请求。

In addition, I really don't want to decorate every single member in each record type that I have across all my client and backend services.此外,我真的不想装饰我在所有客户端和后端服务中拥有的每种记录类型中的每个成员

F# Client: F# 客户端:

I thought I could use a contract resolver on the client to remedy the JSON conflicts.我想我可以在客户端上使用合同解析器来解决 JSON 冲突。 However, I failed.然而,我失败了。

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# Server: C#服务器:

I then thought I could use a contract resolver on the server to remedy the JSON conflicts.然后我想我可以在服务器上使用合同解析器来解决 JSON 冲突。 However, I still failed.然而,我还是失败了。

In addition, I get exceptions when attempting to deserialize.此外,我在尝试反序列化时遇到异常。 It complains that it can't convert a string into one of my record types.它抱怨它无法将字符串转换为我的记录类型之一。

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 ! ! !

            ...
        }
    }

} }

Error:错误:

Could not cast or convert from System.String to Courier.Account.Language+DeviceOrigin.无法从 System.String 强制转换或转换为 Courier.Account.Language+DeviceOrigin。

Unwanted Resolution:不需要的分辨率:

As mentioned earlier , decorating the properties of record types resolves the issue but is extremely tedious for every data-transfer structure that's used in all of my solutions.正如前面提到,修饰记录类型的属性可以解决这个问题,但是对于我所有解决方案中使用的每个数据传输结构来说都非常乏味。

The following works if I decorate the types on both the client and the server:如果我在客户端和服务器上装饰类型,则以下内容有效:

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

Appendix:附录:

Return an F# record type as JSON in Azure Functions 在 Azure Functions 中以 JSON 形式返回 F# 记录类型

Serializing F# Record type to JSON includes '@' character after each property 将 F# 记录类型序列化为 JSON 包括每个属性后的“@”字符

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

I am not sure what exactly the problem is as the question is lacking a minimal reprodible example, but looking at the other questions it seems @ appended by F# on field members causes issues to some.我不确定问题究竟是什么,因为该问题缺少一个最小的可重复示例,但是查看其他问题,似乎由 F# 附加在字段成员上的@会导致某些问题。 I tried reproducing it on my machine but NewtonSoft.Json doesn't append @ for me.我尝试在我的机器上重现它,但 NewtonSoft.Json 没有为我附加@ This make me think OP is running a different version of .net and/or NewtonSoft.Json on client and server which gives the problem seen.这让我认为 OP 正在客户端和服务器上运行不同版本的 .net 和/或 NewtonSoft.Json,这导致了问题。

It might be a good idea to check that Client and Server are running the same version of NewtonSoft.Json and preferably the latest.检查客户端和服务器是否运行相同版本的 NewtonSoft.Json 并且最好是最新版本可能是个好主意。

Something that could help is providing your own NamingStrategy , this allows you to override the names serialized into Json, possibly letting you to strip the @ .可以提供帮助的是提供您自己的NamingStrategy ,这允许您覆盖序列化为 Json 的名称,可能让您去除@

Here's the complete source code that I tried running on .NET Core 3.1 for NewtonSoft.Json 12.0.3.这是我尝试在 .NET Core 3.1 上为 NewtonSoft.Json 12.0.3 运行的完整源代码。 I don't see @ but potentially the NamingStrategy can help OP get further.我没有看到@但可能 NamingStrategy 可以帮助 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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM