JSON surveys
Write tasks as JSON, load them from an asset or a server, and export tasks and results as JSON.
Every task, step, answer format and result of Research Package converts to and from JSON. A study can therefore keep its surveys and consent flows outside the app code, for example on a server. The app loads them when the participant starts a task.
JSON format
The JSON of a task follows its Dart classes:
- Every object has a
__typekey with the name of its Dart class, such asRPQuestionStep. This also applies to each choice, consent section, data type and signature. - Enum values are their names, which are case-sensitive:
SingleChoice,Overview,location. - Durations, such as the
timeoutof a timer step, are whole microseconds: 30 seconds is30000000. - Keys whose value is
nullare left out. Keys that a class does not have are ignored, also when they are misspelled. - Dates in results are ISO 8601 strings in local time, without a time zone offset.
Write a task in JSON
This morning survey has an instruction, two questions and a completion step:
Every answer format needs a questionType that matches the format, such as SingleChoice for a single choice question or Integer for an integer question. See Question types. The texts can be translation keys, as in Dart; see Localization.
Required keys
Loading fails when a required key is missing. Besides __type, these keys are required:
| Class | Required keys |
|---|---|
RPOrderedTask | identifier, steps |
RPNavigableOrderedTask | identifier, steps, stepNavigationRules, which may be {} |
RPInstructionStep, RPCompletionStep | identifier, title |
RPQuestionStep | identifier, title, answerFormat |
RPFormStep | identifier, title, questions, autoFocus, and an answerFormat of type RPFormAnswerFormat |
RPTimerStep | identifier, title, timeout |
RPVisualConsentStep | identifier, optional, consentDocument, title, which may be "" |
RPConsentReviewStep | identifier, title, optional, consentDocument |
| Every answer format | questionType |
RPChoiceAnswerFormat | answerStyle, choices |
RPChoice | text, value |
RPIntegerAnswerFormat, RPDoubleAnswerFormat | minValue, maxValue |
RPSliderAnswerFormat | minValue, maxValue, divisions |
RPImageChoiceAnswerFormat | choices |
RPImageChoice | imageUrl, description |
RPDateTimeAnswerFormat | dateTimeAnswerStyle |
RPConsentDocument | title, sections, signatures, which may be [] |
RPConsentSection | type, summary, and title in a Custom section |
RPDataTypeSection | dataName, dataInformation |
RPConsentSignature | identifier |
RPStepJumpRule | answerMap |
RPDirectStepNavigationRule | destinationStepIdentifier |
RPStepReorganizerRule | reorderingMap |
Other keys get their defaults when they are missing, such as optional: false. Loading does not check what the screens need, such as the text of a completion step. See Tasks and steps and Informed consent.
Load a task
Load a task from an asset with RPOrderedTask.fromJson():
ResearchPackage.ensureInitialized() registers the classes that fromJson() creates, and loading fails without it. RPOrderedTask.fromJson() returns the class that __type names, so it also loads an RPNavigableOrderedTask.
Declare the folder of the task files as an asset:
To load a task from a server, decode the body of the response with jsonDecode() and pass the map to RPOrderedTask.fromJson(). Some objects of a task change while it runs, so create a new task from the JSON for each run.
A mistake in the JSON throws when the task loads:
| Mistake | Error |
|---|---|
A __type that is missing, misspelled or not registered | SerializationException |
| A required key that is missing, or a value of the wrong type | TypeError |
| An enum value that is missing or misspelled | ArgumentError |
Load each task file in a unit test, so that a mistake shows before a participant opens the task.
Export tasks and results
jsonEncode() from dart:convert turns a task or a result into JSON, because every model class has a toJson() method. For readable output, use a JsonEncoder with an indent. Here, buildMorningSurvey() is your function that builds the task in Dart:
The output holds every key, also the ones with default values, such as optional and closeAfterFinished. It leaves out customIllustration, because a widget has no JSON form. Loading the output gives the same task again, so a task built in Dart is a good start for a JSON file.
onSubmit can store the result with jsonEncode(result), and RPTaskResult.fromJson() reads it back. The answers then stay plain JSON values, such as a list of maps for a choice answer. See Results.
Add your own step classes
fromJson() creates only registered classes. Research Package registers its own classes in ensureInitialized(), but a subclass of your own, such as an activity step, needs a registration of its own.
The subclass returns a function that creates it from JSON in fromJsonFunction, and FromJsonFactory from carp_serializable registers it. Add the package:
Then add the JSON support to the class, here to the TapCountActivity from the activity step example:
Register the class at the start of main(), next to Research Package:
register() stores the function under the __type of the object, TapCountActivity. The class inherits toJson() from RPActivityStep. A class with fields of its own writes them in its own toJson() and reads them in its function.
In a CARP Mobile Sensing (CAMS) study, an RPAppTask carries its task in the JSON of the study protocol. The format is the same in research_package 3.2.0, which CAMS apps use. See Building surveys.