Table of Contents

Basic usage

Bonsai.Sgen can generate many kinds of models, with different relationships between types.

Tip

Familiarity with Scripting Extensions is recommended before using this tool.

Single object

The first example models the Person record type from the Data definition article.

First, define the JSON Schema for the Person type:

person.json

{
  "title": "Person",
  "type": "object",
  "properties": {
    "age": { "type": "integer" },
    "firstName": { "type": "string" },
    "lastName": { "type": "string" },
    "dateOfBirth": { "type": "string", "format": "date-time" }
  }
}

Then generate the extension code with Bonsai.Sgen:

dotnet bonsai.sgen person.json -o Extensions --serializer json

The generated code follows the naming conventions of C#, so the firstName property in the schema becomes FirstName in the generated Person type. Serialized data keeps the names declared in the schema.

Use the generated operators directly in a workflow:

Person as BonsaiSgen

Multiple objects

Projects often need more than one record type. A single schema file can define several types, and Bonsai.Sgen generates them all at once:

person-and-dog.json

{
  "title": "PersonAndPet",
  "$defs": {
    "Person": {
      "title": "Person",
      "type": "object",
      "properties": {
        "age": { "type": "integer" },
        "firstName": { "type": "string" },
        "lastName": { "type": "string" },
        "dateOfBirth": { "type": "string", "format": "date-time" }
      }
    },
    "Dog": {
      "title": "Dog",
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "breed": { "type": "string" },
        "age": { "type": "integer" }
      }
    }
  },
  "type": "object",
  "properties": {
    "owner": { "$ref": "#/$defs/Person" },
    "pet": { "$ref": "#/$defs/Dog" }
  }
}
dotnet bonsai.sgen person-and-dog.json -o Extensions --serializer yaml

Person And Dog

Some points to note in this example:

  • The schema file contains the two definitions Person and Dog, each of which generates an operator of the same name.
  • The schema root, titled PersonAndPet, combines the two objects into a single record.
  • The --serializer option selects YAML for the generated serialization and deserialization operators.
  • The generated classes are in a different namespace from the previous example. By default, the namespace is derived from the name of the schema file, which prevents name clashes between schemas, such as between PersonAndDog.Person here and Person.Person in the previous example.
Tip

The --namespace option sets the namespace of the generated code explicitly.

Nested objects

Generated types can hold other generated types, as the PersonAndPet record holds a Person and a Dog. Workflows can create, compose and manipulate these nested objects directly:

Person And Dog Nested Building

Enums

Bonsai.Sgen also supports the generation of enums using the enum keyword in the JSON Schema. For example, a Pet enum can replace the Dog object of the previous example:

person-and-pet-enum.json

(...)

{
  "Pet": {
    "title": "Pet",
    "type": "string",
    "enum": ["Dog", "Cat", "Fish", "Bird", "Reptile"]
  }
},
"type": "object",
"properties": {
  "owner": {"$ref": "#/$defs/Person"},
  "pet": {"$ref": "#/$defs/Pet"}
}

In a workflow, the generated enums behave as ordinary Enum types:

Person and Pets

Tip

The x-enumNames annotation specifies the names of the generated enum members, for example to name integer values:

{
  "MyIntEnum": {
    "enum": [0, 1, 2, 3, 4],
    "title": "MyIntEnum",
    "type": "integer",
    "x-enumNames": ["None", "One", "Two", "Three", "Four"]
  }
}

Lists

Bonsai.Sgen also generates lists from the array type:

"pets": {
  "type": "array",
  "items": {"$ref": "#/$defs/Pet"}
}

An array generates a List<T> property, which can be created and manipulated like any other list:

Person and Pets

Dates and times

JSON Schema represents dates and times as strings with the date or date-time format:

"dateOfBirth": { "type": "string", "format": "date-time" }

Both formats generate a DateTimeOffset property. A date-time value follows RFC 3339, which requires the offset from UTC that DateTimeOffset stores. A date value has no offset, but .NET Framework has no type that holds only a date, so the generated property uses DateTimeOffset as well. A date stored as a DateTime converts implicitly to DateTimeOffset.

Nullable types

JSON Schema represents a missing value with the null type. Bonsai.Sgen generates a nullable type for a property that accepts either null or one other type:

"pet": {
  "anyOf": [
    {"$ref": "#/$defs/Pet"},
    {"type": "null"}
  ]
}

For a value type, the generated property is a nullable value type, whose HasValue property tests whether a value is present, and only then can its Value property be read.

For a reference type, the generated property keeps its type, since reference types always accept null in the generated C# code. An ExpressionTransform operator with the expression it == null tests whether the value is missing:

Nullable pet

Required fields

The JSON Schema required keyword lists the fields an object must contain, and the default keyword gives a field its initial value. A field with a default value starts with that value, both in a new object and when the field is missing from deserialized data. Any other field starts with the default value of its C# type, such as 0 or null. Only the JSON deserializer checks required, so an object created in a workflow or read from YAML has to be populated correctly before it is used.

Note

Languages and libraries use the terms nullable and required in different ways. This tool uses the following definitions:

  • nullable means the field can be null or of type T.
  • required means the field must be present in the object at deserialization time.
  • A field can be nullable and required at the same time. It must then be present in the object, but its value can be null.
  • A field can be not required and nullable. This does not mean its default value is null. The field accepts null, and the object provides a default value, but the declaration says nothing about what that default value is.
  • A field can be not required and not nullable. The field should then declare a default value that is not null.

Serialization and deserialization

Every record described by a schema can be serialized and deserialized, so objects convert in both directions between C# types and a text format suited to specification and logging. When the --serializer option is specified, Bonsai.Sgen generates serialization and deserialization operators for every type in the schema. Two formats are supported: json, using Newtonsoft.Json, and yaml, using YamlDotNet.

The SerializeToJson and SerializeToYaml operators convert an object of any type generated in the namespace into a string. The DeserializeFromJson and DeserializeFromYaml operators convert a string back into an object, and throw an exception if the string fails validation.

(de)serialization

Package references

The generated code depends on the library of the chosen serializer, so the Extensions.csproj file needs the matching package reference:

<PackageReference Include="YamlDotNet" Version="16.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

The Bonsai environment needs the same package. YamlDotNet is already included with the Bonsai editor, whereas Newtonsoft.Json has to be installed from the package manager.

Tip

The package manager lists only Bonsai packages by default. Check "Show advanced" to search for other packages, such as Newtonsoft.Json.