Next Step Against JSON Schema API
Introduction
Due to the complexity of JSON schemas, it can be nontrivial to evaluate what can be added next to an annotation. This API will address this issue by providing the client all the fields that can be annotated unconditionally and immediately.
To understand the necessity of an API like this, we can take a look at one simple example.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"country": {
"enum": ["USA", "CA"]
}
},
"if": {
"properties": {
"country": {
"const": "USA"
}
}
},
"then": {
"properties": {
"state": {
"type": "string"
}
}
}
}
In this JSON schema we have a “country” field with an enumeration that gives us the choice of choosing “USA” or “CA”. If our annotations were empty, our proposed API would tell us that we can fill in the “country” field. If the annotations were to be updated with the country “USA”, a second call to our API would tell us that we can now fill in a “state”. The complexity of these schemas can grow quickly and it may not be user friendly for an annotator to parse the schema to understand what they can add next to the annotations and what becomes invalid upon updates to the annotations. This API is posed to solve this issue.
Example Use Case 1
The simplest use case is where we have an existing entity with a bound JSON schema, but it may or may not contain annotations on it. In this situation, using this proposed idea, a client can do repeated calls to this API to determine a next step in an iterative annotation process. The client may ask for the next step (meaning all unfilled fields that can be filled immediately) on an entity, and once the job returns the fields, the client can use these fields to render a form for the user to fill out. When the user fills out the form, the client can use existing API to add these new annotations given by the user to the entity. To handle this case, the missing API is figuring out this next step on the entity. This API will need to address this by evaluating the current annotations on the entity to its bound JSON schema to determine what fields are available to be immediately filled unconditionally with respect to the annotations present.
Example Use Case 2
The second use case is where the entity does not exist yet, but we know the destination that the entity will be uploaded to. This destination will contain a JSON schema bound to it, and we want our API to build the JSON annotations before the client decides to upload the entity to that destination and apply the annotations. This next implementation will allow the client to use the API to send a JSON representing the annotations and get back the fillable fields that can be rendered to the user to be filled out. These fields are determined by the JSON schema at the destination against the JSON annotations in the request. This implementation should be used to iteratively build up a JSON on the client side. At the end of this process, the client should have a JSON that they could put onto an entity that will be uploaded to that destination.
This API should handle both of our use cases.
Proposed API
Response | URL | Request | Description |
---|---|---|---|
AsyncJobId | POST /schema/next/step/async/start/ | NextStepRequest | Start an asynchronous job to get the next step for annotating against a JSON schema. |
NextStepResponse
| GET /schema/next/step/async/get/{asyncToken} | AsyncJobId | Get the results for the asynchronous job of the next step for annotating against a JSON schema |
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "org.sagebionetworks-NextStepRequest",
"description": "A request to start an asynchronous job get the next step in annotating against a schema for some annotation state.",
"implements": [
{
"$ref": "org.sagebionetworks.repo.model.asynch.AsynchronousResquestBody"
}
],
"properties": {
"schemaId": {
"description": "ID of the schema to get next step for.",
"type": "number"
},
"annotations": {
"description": "Annotation state that will be compared to the destination's JSON schema.",
"type": "object"
}
}
}
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "org.sagebionetworks-NextStepResponse",
"description": "The results of an asynchronous job to get the next step for annotating against a JSON schema.",
"implements": [
{
"$ref": "org.sagebionetworks.repo.model.asynch.AsynchronousResponseBody"
}
],
"properties": {
"nextStepSchema": {
"description": "JSON schema of the next step",
"$ref": "org.sagebionetworks.repo.model.schema.JsonSchema"
}
}
}
We can use the above API to fetch next-step JSON schemas that can be rendered by the client for user input. Because the request requires a schema ID, the client can first fetch the schema ID from the entity or entity container of interest. Each time the client calls our API, they provide the current state of the annotations being built. After rendering the next-step to the user and taking input, the client can update the current annotations on the client side, before calling our API again with the newly updated annotations.
The following document demonstrates handling of the next-step schema. Handling Next Step JSON Schema Cases