F# Data JSON type provider: How to handle a JSON property that can be an array or a property?

1.3k Views Asked by At

I am using the JSON type provider from the F# Data library to access JSON documents from an API. The documents contain a property (let's call it 'car') that sometimes is an array of objects and sometimes a single object. It is either this:

'car': [
  { ... },
  { ... }
]

or this:

'car': { ... }

The object in { ... } has the same structure in both cases.

The JSON type provider indicates that the property is of type:

JsonProvider<"../data/sample.json">.ArrayOrCar

where sample.json is my sample document.

My question is then: How can I figure out whether the property is an array (so that I can process it as an array) or a single object (so that I can process it as an object)?

UPDATE: A simplified sample JSON would look like this:

{
  "set": [
    {
      "car": [
        {
          "brand": "BMW" 
        },
        {
          "brand": "Audi"
        }
      ]
    },
    {
      "car": {
        "brand": "Toyota"
      }
    } 
  ]
}

And with the following code it will be pointed out that the type of doc.Set.[0].Car is JsonProvider<...>.ArrayOrCar:

type example = JsonProvider<"sample.json">
let doc = example.GetSample()
doc.Set.[0].Car
2

There are 2 best solutions below

0
On BEST ANSWER

If the type of the JSON value in the array is the same as the type of the directly nested JSON value, then JSON type provider will actually unify the two types and so you can process them using the same function.

Using your minimal JSON document as an example, the following works:

type J = JsonProvider<"sample.json">

// The type `J.Car` is the type of the car elements in the array    
// but also of the car record directly nested in the "car" field
let printBrand (car:J.Car) =
  printfn "%s" car.Brand

// Now you can use pattern matching to check if the current element
// has an array of cars or just a single record - then you can call
// `printBrand` either on all cars or on just a single car
let doc = J.GetSample()
for set in doc.Set do
  match set.Car.Record, set.Car.Array with
  | Some c, _ -> printBrand c
  | _, Some a -> for c in a do printBrand c
  | _ -> failwith "Wrong input"
0
On

After playing around with the library, I found that you can do something like this:

let car = doc.Set.[0].Car
let processObject (car:example.ArrayOrCar) = 
    match car.Array with
    | Some a -> printfn "do stuff with an array %A" a
    | None ->  match car.Record with 
               | Some c -> printfn "do stuff with an object %A" c
               | None -> printfn "fail here?"

To process the whole Set[] you can do something where you apply processObject to each element using something like Array.map.