Search code examples
f#json.netjson-deserialization

How to disallow empty string when deserializing record types with newtonsoft in F#?


Is there any way to force parsing of only non-empty string fields of a record type in F# using Newtonsoft.Json?

#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 =          "[email protected]"
    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)

Solution

  • You might want to consider using Newtonsoft's schema support for this, which is in a separate package called Newtonsoft.Json.Schema. You can specify many different kinds of constraints using annotations. For example, to disallow blank names, you can use MinLength:

    open System.ComponentModel.DataAnnotations
    
    type Customer = {
        [<MinLength(1)>]
        Name:           string
        Email:          string
        ContactPhoneNo: string
    }
    

    Once you've annotated your type, you can generate a schema:

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

    Then use it to validate the serialized JSON:

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

    If you want to skip the overhead of first loading JSON into a JObject in order to validate it, you can use a JSchemaValidatingReader instead:

    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
    

    See this documentation for details.