Scott Nimrod
Scott Nimrod

Reputation: 11570

Unable to deserialize json in .Net Core?

How can I perform json deserialization in F#?

I have the following code:

type Snippet = { title: String; tags: String list }

[<Test>]
let ``Apply tags to videos`` () =
    let response = httpClient.GetAsync(url) |> Async.AwaitTask |> Async.RunSynchronously

    if response.IsSuccessStatusCode
        then let tags = response.Content.ReadAsAsync<System.Collections.Generic.List<Snippet>>() |> Async.AwaitTask |> Async.RunSynchronously

The following line throws an exception:

response.Content.ReadAsAsync<System.Collections.Generic.List<Snippet>>() |> Async.AwaitTask |> Async.RunSynchronously
        ...

JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[Integration+Snippet]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path 'items', line 2, position 9.

The following is an example of the json that I'm trying to deserialize:

{
    "items": [
        {
            "snippet": {
                "title": "Giraffe: VS Code bug that doesn't show up in VS 20017 (3)",
                "tags": [
                    "#hangoutsonair",
                    "Hangouts On Air",
                    "#hoa"
                ]
            }
        },
        {
            "snippet": {
                "title": "Giraffe: VS Code bug that doesn't show up in VS 20017 (3)",
                "tags": [
                    "#hangoutsonair",
                    "Hangouts On Air",
                    "#hoa"
                ]
            }
        }
    ]
}

In summary, I'm using C#'s list for deserialization as I think I'm required to do.

However, I'm still not clear on what I'm doing wrong?

Upvotes: 0

Views: 2615

Answers (5)

user6996876
user6996876

Reputation:

How can I perform json deserialization in F#?

Just for the sake of going fully functional, you could use a monadic parser for the JSON deserialization .

So assuming that your example is given by

let exampleJson = response.Content.ReadAsStringAsync() |> Async.AwaitTask |> Async.RunSynchronously

let parsed = json().Parse exampleJson

and you have defined the target type

type Snippet = { title: String; tags: String list }

together with some helpers

let jparseObject (JSObject items) = items 
let jparseObjName jname = jparseObject >> Map.find jname
let jparseArray mapper (JSArray items ) = items |> Seq.map mapper
let jparseArrName jname mapper = Map.find jname >> jparseArray mapper
let jparseString (JSString str) = str
let jparseStrName jname = Map.find jname >> jparseString

then you would end up doing

let json2snippets  = jparseObjName @"""items"""  >> 
    jparseArray (jparseObjName @"""snippet""" >> 
    jparseObject >> fun snippet ->  
        {title =snippet |> jparseStrName @"""title"""; 
        tags=snippet |> jparseArrName  @"""tags""" jparseString |> Seq.toList })

where (json2snippets parsed |> Seq.toList) is a Snippet list

Upvotes: 0

Devin Lynch
Devin Lynch

Reputation: 185

I had a similar problem a while back and posted a question about it (except mine was in C#...I'm assuming the solution works in F# as well). The problem is that your data is actually a KeyValuePair with a string key and a Snippet list value.

Now, first off, it would make your JSON deserialization a lot easier if you got the Newtonsoft.Json NuGet package for your project. Here's how you would implement that

open System.Collections.Generic
open Newtonsoft.Json

// ... All your other code here.  Assuming response.Content is your json

let d = JsonConvert.DeserializeObject<Dictionary<string, List<Snippet>>(response.Content)

// And finally get your list
let items = d.["items"]

If you don't want to use the Json package, then just change your existing code to use a Dictionary<string, List<Snippet>> instead of a List<Snippet>.

Upvotes: 2

Scott Nimrod
Scott Nimrod

Reputation: 11570

I referenced this tool to identify the following types needed for deserialization:

[<CLIMutable>]
type Snippet =    { title: string; tags: string [] }

[<CLIMutable>]
type Item =       { snippet : Snippet }

[<CLIMutable>]
type RootObject = { items : Item[] }
...


if response.IsSuccessStatusCode
        then let root = response.Content.ReadAsAsync<RootObject>() |> Async.AwaitTask |> Async.RunSynchronously

The issue appears to be resolved.

Upvotes: 0

Rodrigo Vidal
Rodrigo Vidal

Reputation: 159

Your example would look something like this.

type Response = 
  { items : SnippetItem[] }
and SnippetItem = 
  { snippet : Snippet[]  }
and Snippet = 
  { title: string 
    tags: string list }

[<Test>]
let ``Apply tags to videos`` () =
  async {
    let! response = httpClient.GetAsync(url) |> Async.AwaitTask
    if response.IsSuccessStatusCode
    then 
        let! tags = response.Content.ReadAsAsync<Response>() |> Async.AwaitTask
        ....
  } |> Async.RunSynchronously

Upvotes: 0

Isaac Abraham
Isaac Abraham

Reputation: 3512

I think that it's because the JSON you're providing isn't an array of snippets. It's an object that has a property (called Items) that is an array of snippets.

Either you can create a type that has that property on it and deserialise that (which I'm told is best practice), or change the json to be a "naked" array (without the container items property).

Upvotes: 4

Related Questions