简体   繁体   中英

How to deserialize json for correct type using f#

I'm new with F# and functional programming.

I have these two types

type User= { 
    active: bool 
    funds: int 
}  
and UserDto = {
    user: User
}

type Transaction = {
    amount: int
    time: DateTime 
}
and TransactionDto = {
    transaction: Transaction
}

And I have to take some JSON string as input and it can be a User or Transaction

I'm trying with Fsharp.Json and it works fine if I know the type.

let getUserInput = 
    let input = System.Console.ReadLine()
    let acc = Json.deserialize<Account> input
    printfn "%A" acc

Is there a way to do that without verifying if the input string contains account do this serialization else if the input string contains transaction do the other?

[EDIT]

As You guys said, I did like Devon Burriss said, but using a different approach. It follows the funcion

let getOperation json =
    let j = JObject.Parse json
    match j.Properties().Select(fun p -> p.Name).FirstOrDefault() with
        | "account" -> AccountOperation(JsonConvert.DeserializeObject<AccountDto>(json))
        | "transaction" -> TransactionOperation(JsonConvert.DeserializeObject<TransactionDto>(json))
        | _ -> InvalidOperation "invalid-operation"

As has been pointed out, you need to know the type to deserialize to that type.

If for some reason you do not know the type coming in and have control of the client, I would send extra information. A type name or since you are using F#, serialize a sum type like so:

type Dto =
| UserType of User
| UserDtoType of UserDto
| TransactionType of Transaction
| TransactionDtoType of TransactionDto

Now you can deserialize to Dto and pattern match from there.

match v with
| UserType x -> printfn "I am a User %A" x
| UserDtoType x -> printfn "I am a UserDto %A" x
| TransactionType x -> printfn "I am a Transaction %A" x
| TransactionDtoType x -> printfn "I am a TransactionDto %A" x

I know you specifically mentioned NOT checking the properties but I did want to point out going to a sum type if you do go that route.

If you can not change the client to send more information you could inspect the JSON and go to the sum type when you deserialize but that is quite manual and error-prone but I don't see another way. You could, of course, make this prettier but it should demonstrate what I mean.

let serialize x = JsonConvert.SerializeObject(x)
let deserialize json = 
    let jObj = JObject.Parse(json)
    if(jObj.ContainsKey("active")) then Dto.UserType (jObj.ToObject<User>())
    elif(jObj.ContainsKey("user")) then Dto.UserDtoType (jObj.ToObject<UserDto>())
    elif(jObj.ContainsKey("amount")) then Dto.TransactionType (jObj.ToObject<Transaction>())
    elif(jObj.ContainsKey("transaction")) then Dto.TransactionDtoType (jObj.ToObject<TransactionDto>())
    else failwith "Unknown type"

Note: This is using Newtonsoft purely because I know it has the lower level JObject and am not familiar with any lower level FSharp.Json features.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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