在 F# 中使用 newtonsoft 反序列化记录类型时如何禁止空字符串?

问题描述

有没有什么方法可以使用 Newtonsoft.Json 强制解析 F# 中记录类型的非空字符串字段?

#r """Newtonsoft.Json.dll"""

open Newtonsoft.Json

type Customer = {
    Name:           string
    Email:          string
    ContactPhoneNo: string
}

// one or more fields can be empty
let customer = {
    Name =           ""
    Email =          "ca@gmail.com"
    ContactPhoneNo = "+123456789"
}

let serializedCustomer =
    JsonConvert.SerializeObject(customer)

// this parses correctly with the Name field set as ""
// But as the name field is empty,it should not parse it
let deserializedCustomer =
    JsonConvert.DeserializeObject<Customer>(serializedCustomer)

解决方法

您可能需要考虑为此使用 Newtonsoft 的架构支持,它位于一个名为 Newtonsoft.Json.Schema 的单独包中。您可以使用注释指定许多不同类型的约束。例如,要禁止空白名称,您可以使用 MinLength:

open System.ComponentModel.DataAnnotations

type Customer = {
    [<MinLength(1)>]
    Name:           string
    Email:          string
    ContactPhoneNo: string
}

在对类型进行注释后,您可以生成架构:

let generator = JSchemaGenerator()
let schema = generator.Generate(typeof<Customer>)

然后用它来验证序列化的 JSON:

let jsonCustomer = JObject.Parse(serializedCustomer)
let isValid = jsonCustomer.IsValid(schema)

如果您想跳过首先将 JSON 加载到 JObject 以对其进行验证的开销,您可以改用 JSchemaValidatingReader

use strReader = new System.IO.StringReader(serializedCustomer)
use txtReader = new JsonTextReader(strReader)
use vldReader = new JSchemaValidatingReader(txtReader,Schema = schema)
let messages = ResizeArray()
vldReader.ValidationEventHandler.Add(fun args -> messages.Add(args.Message))
let serializer = JsonSerializer()
let deserializedCustomer = serializer.Deserialize<Customer>(vldReader)
printfn "%A" deserializedCustomer
let isValid = (messages.Count = 0)
printfn "%A" isValid

有关详细信息,请参阅 this documentation

,

您可以实现一个自定义 JsonConverter 来转换 string 类型的值,但在字符串为空时抛出异常:

let nonEmptyStringConverter = 
  { new JsonConverter() with
      override x.CanConvert(objectType) = objectType = typeof<string>
      override x.WriteJson(writer,value,serializer) =
        JValue(value :?> string).WriteTo(writer)
      override x.ReadJson(reader,objectType,existingValue,serializer) =
        let jt = JToken.Load(reader)
        if jt.Type = JTokenType.String then 
          let str = jt.Value<string>() 
          if String.IsNullOrEmpty str then failwith "Empty string"
          box str
        else failwith "Expected a string" }

如果您将其传递给 DeserializeObject,那么它将在您的示例中引发异常:

let serializedCustomer =
    JsonConvert.SerializeObject(customer)

let deserializedCustomer =
    JsonConvert.DeserializeObject<Customer>(serializedCustomer,nonEmptyStringConverter)

一个警告是,这将适用于类型中的所有 string 值,我希望您可能希望允许某些值为空。更好的方法是定义自定义类型并仅为此类型定义转换器,例如使用:

type NonEmptyString = NE of string

type Customer = 
  { Name : NonEmptyString
    Email: NonEmptyString
    ContactPhoneNo: string }