Using JSON Schema with the JSON Type Provider
The JSON Type Provider allows you to use JSON Schema to provide statically typed access to JSON documents, similar to how the XML Type Provider supports XML Schema.
Basic Usage with JSON Schema
Let's start with a basic JSON Schema example:
let personSchema = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
},
"email": {
"type": "string",
"format": "email"
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["home", "work", "mobile"]
},
"number": {
"type": "string"
}
},
"required": ["type", "number"]
}
}
},
"required": ["firstName", "lastName"]
}
"""
// Create a type based on the schema
[<Literal>]
let PersonSchemaLiteral = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
},
"email": {
"type": "string",
"format": "email"
},
"phoneNumbers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["home", "work", "mobile"]
},
"number": {
"type": "string"
}
},
"required": ["type", "number"]
}
}
},
"required": ["firstName", "lastName"]
}
"""
type Person = JsonProvider<Schema=PersonSchemaLiteral>
// Parse a JSON document that conforms to the schema
let person = Person.Parse("""
{
"firstName": "John",
"lastName": "Smith",
"age": 42,
"email": "john.smith@example.com",
"phoneNumbers": [
{
"type": "home",
"number": "555-1234"
},
{
"type": "mobile",
"number": "555-6789"
}
]
}
""")
// Access the strongly typed properties
printfn "Name: %s %s" person.FirstName person.LastName
printfn "Age: %A" person.Age
printfn "Email: %A" person.Email
printfn "Phone: %s" person.PhoneNumbers.[0].Number
Using Schema Files
You can also load a JSON Schema from a file:
// Assuming you have a schema file:
// type Product = JsonProvider<Schema="schemas/product.json">
Validating JSON Against Schema
When using the JSON Provider with the Schema parameter, data validation occurs automatically at parse time based on the schema rules:
- Properties are required according to the schema (firstName and lastName)
- Property types match those defined in the schema (age is a non-negative integer)
- Format constraints are checked (email is a valid email format)
- Pattern constraints are validated (orderId matches the pattern "^ORD-[0-9]{6}$")
- Numeric constraints are enforced (minimum/maximum values)
Here's how validation works:
// Valid JSON that conforms to the schema
let validPerson = Person.Parse("""
{
"firstName": "Jane",
"lastName": "Doe",
"age": 35,
"email": "jane.doe@example.com"
}
""")
printfn "Valid JSON: %s %s" validPerson.FirstName validPerson.LastName
// Invalid JSON that violates schema rules will cause an exception
// Let's use try-catch to demonstrate validation errors:
let invalidJson = """
{
"firstName": "John",
"age": -5
}
"""
// In a real project when using the Schema parameter, the JsonProvider would validate
// against the schema rules. For the purposes of this demonstration, let's manually
// validate the JSON against the schema:
// Create a JSON value from the invalid JSON
let jsonValue = JsonValue.Parse(invalidJson)
// Check required fields from the schema
if jsonValue.TryGetProperty("lastName").IsNone then
printfn "Schema validation failed: missing required property 'lastName'"
// Check numeric constraints from the schema
if jsonValue.TryGetProperty("age").IsSome &&
jsonValue.["age"].AsInteger() < 0 then
printfn "Schema validation failed: 'age' must be non-negative"
Schema Constraints and Validation
JSON Schema supports various constraints that are validated:
String Constraints
|
Numeric Constraints
|
Array Constraints
|
Object Constraints
|
Working with Schema References
JSON Schema allows references to reuse schema definitions:
|
Advantages of Using JSON Schema
- Documentation: Schema provides documentation on what properties are available.
- Validation: Schema enforces constraints on data types, required properties, etc.
- Type Safety: Strong typing to prevent errors when working with JSON data.
- Discoverability: Better IntelliSense in your IDE.
- Consistency: Ensure all documents follow the same structure.
- Contract First Development: Define your data contract before implementation.
JSON Schema Features Supported
The JSON Schema support in FSharp.Data includes:
- Basic types (string, number, integer, boolean, object, array)
- Required properties
- Property format definitions (date-time, email, etc.)
- Enumerations
- Nested objects and arrays
- Minimum/maximum constraints
- String patterns and length constraints
- References ($ref) for reusing schema definitions
- Validation of documents against schema
Requirements and Limitations
- When using the
Schema
parameter, you cannot use theSample
parameter - Schema and SampleIsList parameters are mutually exclusive
- Currently supports JSON Schema Draft-07
- JSON Schema references ($ref) support is limited to local references within the schema
- Some advanced schema features like dependencies, conditionals, and unevaluatedProperties are not fully supported
Using JSON Schema in Your Project
To use JSON Schema with the JSON Type Provider:
- Define your schema (in a file or as a string)
- Create a type using
JsonProvider<Schema="path-to-schema.json">
orJsonProvider<Schema=schemaString>
- Use the generated type to parse and work with your JSON data
- Optionally use the validation functions for runtime validation
Complete Example with Nested Objects
Here's a more complex example with nested objects:
let orderSchema = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"orderId": {
"type": "string",
"pattern": "^ORD-[0-9]{6}$"
},
"customer": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"productId": { "type": "string" },
"name": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"price": { "type": "number", "minimum": 0 }
},
"required": ["productId", "quantity", "price"]
},
"minItems": 1
},
"totalAmount": { "type": "number", "minimum": 0 },
"orderDate": { "type": "string", "format": "date-time" }
},
"required": ["orderId", "customer", "items", "totalAmount", "orderDate"]
}
"""
// Create a type based on the order schema
[<Literal>]
let OrderSchemaLiteral = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"orderId": {
"type": "string",
"pattern": "^ORD-[0-9]{6}$"
},
"customer": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"productId": { "type": "string" },
"name": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 },
"price": { "type": "number", "minimum": 0 }
},
"required": ["productId", "quantity", "price"]
},
"minItems": 1
},
"totalAmount": { "type": "number", "minimum": 0 },
"orderDate": { "type": "string", "format": "date-time" }
},
"required": ["orderId", "customer", "items", "totalAmount", "orderDate"]
}
"""
type Order = JsonProvider<Schema=OrderSchemaLiteral>
let order = Order.Parse("""
{
"orderId": "ORD-123456",
"customer": {
"id": 1001,
"name": "Alice Smith",
"email": "alice@example.com"
},
"items": [
{
"productId": "PROD-001",
"name": "Laptop",
"quantity": 1,
"price": 1299.99
},
{
"productId": "PROD-002",
"name": "Mouse",
"quantity": 2,
"price": 25.99
}
],
"totalAmount": 1351.97,
"orderDate": "2023-10-01T12:00:00Z"
}
""")
printfn "Order: %s" order.OrderId
printfn "Customer: %s" order.Customer.Name
printfn "Items: %d" order.Items.Length
printfn "Total: %.2f" order.TotalAmount
printfn "Date: %A" order.OrderDate
Summary
The JSON Schema support in FSharp.Data provides a powerful way to work with strongly-typed JSON data based on schema definitions. It combines the benefits of static typing with the flexibility of JSON, making it an excellent choice for working with well-defined JSON APIs and data structures.
namespace FSharp
--------------------
namespace Microsoft.FSharp
namespace FSharp.Data
--------------------
namespace Microsoft.FSharp.Data
type LiteralAttribute = inherit Attribute new: unit -> LiteralAttribute
--------------------
new: unit -> LiteralAttribute
<summary>Typed representation of a JSON document.</summary> <param name='Sample'>Location of a JSON sample file or a string containing a sample JSON document.</param> <param name='SampleIsList'>If true, sample should be a list of individual samples for the inference.</param> <param name='RootName'>The name to be used to the root type. Defaults to `Root`.</param> <param name='Culture'>The culture used for parsing numbers and dates. Defaults to the invariant culture.</param> <param name='Encoding'>The encoding used to read the sample. You can specify either the character set name or the codepage number. Defaults to UTF8 for files, and to ISO-8859-1 the for HTTP requests, unless `charset` is specified in the `Content-Type` response header.</param> <param name='ResolutionFolder'>A directory that is used when resolving relative file references (at design time and in hosted execution).</param> <param name='EmbeddedResource'>When specified, the type provider first attempts to load the sample from the specified resource (e.g. 'MyCompany.MyAssembly, resource_name.json'). This is useful when exposing types generated by the type provider.</param> <param name='InferTypesFromValues'> This parameter is deprecated. Please use InferenceMode instead. If true, turns on additional type inference from values. (e.g. type inference infers string values such as "123" as ints and values constrained to 0 and 1 as booleans.)</param> <param name='PreferDictionaries'>If true, json records are interpreted as dictionaries when the names of all the fields are inferred (by type inference rules) into the same non-string primitive type.</param> <param name='InferenceMode'>Possible values: | NoInference -> Inference is disabled. All values are inferred as the most basic type permitted for the value (i.e. string or number or bool). | ValuesOnly -> Types of values are inferred from the Sample. Inline schema support is disabled. This is the default. | ValuesAndInlineSchemasHints -> Types of values are inferred from both values and inline schemas. Inline schemas are special string values that can define a type and/or unit of measure. Supported syntax: typeof<type> or typeof{type} or typeof<type<measure>> or typeof{type{measure}}. Valid measures are the default SI units, and valid types are <c>int</c>, <c>int64</c>, <c>bool</c>, <c>float</c>, <c>decimal</c>, <c>date</c>, <c>datetimeoffset</c>, <c>timespan</c>, <c>guid</c> and <c>string</c>. | ValuesAndInlineSchemasOverrides -> Same as ValuesAndInlineSchemasHints, but value inferred types are ignored when an inline schema is present. </param> <param name='Schema'>Location of a JSON Schema file or a string containing a JSON Schema document. When specified, Sample and SampleIsList must not be used.</param>
Parses the specified JSON Schema string
<summary> Represents a JSON value. Large numbers that do not fit in the Decimal type are represented using the Float case, while smaller numbers are represented as decimals to avoid precision loss. </summary>
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue">Int32.MaxValue</see> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>