Search code examples
jsonf#type-providersf#-datafsharp.data.typeproviders

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


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

Solution

  • 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"