当前位置: 首页 > 知识库问答 >
问题:

带有选项字段的F#记录在Asp中未正确反序列化。Net WebApi 2。x应用程序

孔运珧
2023-03-14

我有一个C#Asp。Net MVC(5.2.7)应用程序,支持WebApi 2。x目标。净额4.5。1.我正在试验F#,并在解决方案中添加了一个F#库项目。该web应用程序引用了F#库。

现在,我希望能够让C#WebApi控制器返回F#对象,并保存F#对象。我有麻烦序列化F#记录与选项字段。下面是代码:

C#WebApi控制器:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using FsLib;


namespace TestWebApp.Controllers
{
  [Route("api/v1/Test")]
  public class TestController : ApiController
  {
    // GET api/<controller>
    public IHttpActionResult Get()
    {
      return Json(Test.getPersons);
    }

    // GET api/<controller>/5
    public string Get(int id)
    {
      return "value";
    }
    [AllowAnonymous]
    // POST api/<controller>
    public IHttpActionResult Post([FromBody] Test.Person value)
    {
      return Json(Test.doSomethingCrazy(value));
    }

    // PUT api/<controller>/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/<controller>/5
    public void Delete(int id)
    {
    }
  }
}

FsLib.fs:

namespace FsLib

open System.Web.Mvc
open Option
open Newtonsoft.Json

module Test =

  [<CLIMutable>]
  //[<JsonConverter(typeof<Newtonsoft.Json.Converters.IdiomaticDuConverter>)>] 
  type Person = {
    Name: string; 
    Age: int; 
    [<JsonConverter(typeof<Newtonsoft.Json.FSharp.OptionConverter>)>]
    Children: Option<int> }

  let getPersons = [{Name="Scorpion King";Age=30; Children = Some 3} ; {Name = "Popeye"; Age = 40; Children = None}] |> List.toSeq
  let doSomethingCrazy (person: Person) = {
    Name = person.Name.ToUpper(); 
    Age = person.Age + 2 ;
    Children = if person.Children.IsNone then Some 1 else person.Children |> Option.map (fun v -> v + 1);  }

   let deserializePerson (str:string) = JsonConvert.DeserializeObject<Person>(str)  

以下是选项转换器:

namespace Newtonsoft.Json.FSharp

open System
open System.Collections.Generic
open Microsoft.FSharp.Reflection
open Newtonsoft.Json
open Newtonsoft.Json.Converters

/// Converts F# Option values to JSON
type OptionConverter() =
    inherit JsonConverter()

    override x.CanConvert(t) = 
        t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>

    override x.WriteJson(writer, value, serializer) =
        let value = 
            if value = null then null
            else 
                let _,fields = FSharpValue.GetUnionFields(value, value.GetType())
                fields.[0]  
        serializer.Serialize(writer, value)

    override x.ReadJson(reader, t, existingValue, serializer) =        
        let innerType = t.GetGenericArguments().[0]
        let innerType = 
            if innerType.IsValueType then typedefof<Nullable<_>>.MakeGenericType([|innerType|])
            else innerType        
        let value = serializer.Deserialize(reader, innerType)
        let cases = FSharpType.GetUnionCases(t)
        if value = null then FSharpValue.MakeUnion(cases.[0], [||])
        else FSharpValue.MakeUnion(cases.[1], [|value|])

我想将选项字段序列化为值(如果它不是None),如果它是None,则序列化为null。反之亦然,null-

序列化工作正常:

[
    {
        "Name": "Scorpion King",
        "Age": 30,
        "Children": 3
    },
    {
        "Name": "Popeye",
        "Age": 40,
        "Children": null
    }
]

但是,当我发布到url时,Person参数被序列化为null,并且不会调用ReadJson方法。我使用Postman(chrome应用程序)通过选择主体来发布-

在WebApiConfig.cs我有:

config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.FSharp.OptionConverter());

然而,如果我只是有了这个,并从Children字段中删除了JsonConverter属性,它似乎没有任何效果。

这是发送到服务器的内容:

POST /api/v1/Test HTTP/1.1
Host: localhost:8249
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: b831e048-2317-2580-c62f-a00312e9103b

Name=Blob&Age=103&Children=2

所以,我不知道出了什么问题,为什么反序列化器将对象转换为null。如果我删除选项字段它工作正常。此外,如果我从有效载荷中删除儿童字段,它也可以正常工作。我不明白为什么不调用OptionCoverter的ReadJson方法。

有什么想法吗?

谢啦

更新:在评论中,我没有发布应用程序/json有效载荷,这是正确的。我做到了,序列化仍然不能正常工作。

更新2:经过更多测试后,这项工作:

public IHttpActionResult Post(/*[FromBody]Test.Person value */)
{
  HttpContent requestContent = Request.Content;
  string jsonContent = requestContent.ReadAsStringAsync().Result;

  var value = Test.deserializePerson(jsonContent);
  return Json(Test.doSomethingCrazy(value));
}

以下是我用于测试请求的Linqpad代码(我没有使用postman):

var baseAddress = "http://localhost:49286/api/v1/Test";

var http = (HttpWebRequest) WebRequest.Create(new Uri(baseAddress));
http.Accept = "application/json";
http.ContentType = "application/json";
http.Method = "POST";

string parsedContent = "{\"Name\":\"Blob\",\"Age\":100,\"Children\":2}";
ASCIIEncoding encoding = new ASCIIEncoding();
Byte[] bytes = encoding.GetBytes(parsedContent);

Stream newStream = http.GetRequestStream();
newStream.Write(bytes, 0, bytes.Length);
newStream.Close();

var response = http.GetResponse();

var stream = response.GetResponseStream();
var sr = new StreamReader(stream);
var content = sr.ReadToEnd();

content.Dump();

更新3:

这很好用-例如,我使用了C类:

   public IHttpActionResult Post(/*[FromBody]Test.Person*/ Person2 value)
    {
//      HttpContent requestContent = Request.Content;
//      string jsonContent = requestContent.ReadAsStringAsync().Result;
//
//      var value = Test.deserializePerson(jsonContent);
      value.Children = value.Children.HasValue ? value.Children.Value + 1 : 1;
      return Json(value);//Json(Test.doSomethingCrazy(value));
    }
   public class Person2
    {
      public string Name { get; set; }
      public int Age { get; set; }
      public int? Children { get; set; }
    }

共有2个答案

元嘉木
2023-03-14

如果你愿意升级框架版本,你可以使用新的系统。文本JsonAPI应该是好的和快速的,以及FSharp。SystemTextJson软件包为您提供了一个自定义转换器,支持记录、区分工会等。

龙志勇
2023-03-14

根据这篇文章,我找到了一个解决方案:https://stackoverflow.com/a/26036775/832783.

我在WebApiConfig注册方法中添加了以下行:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver();

我还得调查这里发生了什么。

更新1:

事实证明,在ApiController方法中调用Json(...)创建了一个新的JsonSeriazerSet对象,而不是使用global.asax.中的默认设置。这意味着,当使用json()结果。

 类似资料:
  • 根据这里描述的内容,我应该使用JSR-310表示而不是数字表示来序列化对象。但是,我得到了数字表示。可能有什么问题? 我是这样配置我正在使用的映射器的: 这是我得到的反序列化示例 对于以下事例类:

  • 我对SOAP响应有问题,希望有人能帮助我。 当SOAP响应被SOAP::Lite模块反序列化时,我认为出了问题。在SOAP-响应中有一些“正常”节点和被引用的节点。但它不正确的反序列化。 SOAP响应: 结果哈希的转储: Perl代码:

  • 问题内容: 我希望我的笔记应用程序能为您提供一些帮助。假设我的记事清单上有3个记事。我想删除列表顶部的注释。无论我尝试删除哪一个,始终会首先删除列表最底部的注释。我检查了React控制台,并且处于应用程序组件状态的notes数组表示已正确删除。但实际上,事实并非如此。如何获得它以便删除我选择的确切音符? 问题答案: 用作时发生此错误。React使用该属性来跟踪列表中的元素。当您从数组中间删除元素时

  • 上次我在考虑如何在我们的应用程序中正确使用记录器。例如,我有一个控制器,它返回一个用户流,但在日志中,我看到“Fetch users”日志是由另一个线程记录的,而不是处理管道上的线程,但这是一个好的方法吗? 在这种情况下,使用了两个线程,从我的角度来看,这不是一个好的选择,但我找不到在反应应用程序中使用记录器的好做法。我认为下面的方法更好,因为分配内存是来自处理线程,而不是来自spring web

  • 由于没有,可以使用什么本机实现来处理这个问题? 我注意到,我可以用它格式化数据到JSON,但是我如何反序列化? 或者我在?

  • 我试图序列化和反序列化一个对象在java中使用Prop3 下面是我在java中的序列化和反序列化的样子 我的输出如下 当我试图使用ByteString和utf-8路由进行序列化和反序列化时,我哪里出错了? 谢谢!