ropensci / jsonvalidate

:heavy_check_mark::interrobang: Validate JSON
https://docs.ropensci.org/jsonvalidate
Other
49 stars 14 forks source link

More options for serialising `NULL` & `NA` when serialising? #65

Open annakrystalli opened 2 years ago

annakrystalli commented 2 years ago

Issue related to #64

We're using schema$serialise() to serialise an R list, created by reading in a json config file, using R to resolve pointers within in order to be able to then validate it against a schema. We are using schema$serialise() in order to get around the problems created by unboxing (with TRUE or FALSE) when using jsonlite::toJSON to re-serialise.

The problem we are having is how NA and null values are being serialised with schema$serialise().

NAs are being converted to null in the resulting JSON while NULLs are being converted to empty values which seems to be the default behaviour of toJSON. I was wondering if it would be possible to expose toJSON arguments na and null to allow for controlling the behaviour during serialisation?

Reproducible example

# FUNCTION -----
# substitute function
library(magrittr)
substitute_refs <- function(x, defs) {
    if (is.list(x)) {
        is_ref <- names(x) == "$ref"

        if (any(is_ref)) {
            def_location <- x[[is_ref]]

            def_idx <- gsub("#/", "", def_location, fixed = TRUE) %>%
                strsplit(split = "/") %>%
                unlist() %>%
                as.list()

            replace <- purrr::pluck(defs, !!!def_idx)

            if (is.null(replace)) {
                cli::cli_abort(
                    c(x = "definition for def {.val { def_location }} returning {.var NULL} ")
                )
            } else {
                x <- replace
            }
        } else {
            lapply(x, substitute_refs, defs = defs)
        }
    } else {
        x
    }
}

# CREATE DIR & PATHS -----
# Create temp dir
tmp_dir <- tempdir()

schema_path <- file.path(tmp_dir, "tasks-schema.json")
json_path <- file.path(tmp_dir, "tasks.json")

# DOWNLOAD NECESSARY FILES -----
# Download file and add newline to end
download.file(
    "https://raw.githubusercontent.com/Infectious-Disease-Modeling-Hubs/schemas/add-tasks-docs/tasks-schema.json",
    destfile = schema_path)
write("", file = schema_path, append = TRUE)

# Download json file and add newline to end
download.file(
    "https://raw.githubusercontent.com/annakrystalli/hub-infrastructure-experiments/json-schema-refs/json-schema/modified-hubmeta-examples/complex-hubmeta-mod.json",
    destfile = json_path)

#  SCHEMA ----
# Create schema to validate and serialise
schema <- jsonvalidate::json_schema$new(
    schema = schema_path,
    engine = "ajv")

# Validate unresolved json. Only 3 errors arising from unresolved pointers
schema$validate(json_path, verbose = TRUE) |>
    attr("errors")
#>                                         instancePath
#> 1 /rounds/0/model_tasks/0/task_ids/location/required
#> 2 /rounds/0/model_tasks/1/task_ids/location/required
#> 3 /rounds/1/model_tasks/0/task_ids/location/required
#>                                                                                                                schemaPath
#> 1 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/required/type
#> 2 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/required/type
#> 3 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/required/type
#>   keyword        type            message      schema
#> 1    type array, null must be array,null array, null
#> 2    type array, null must be array,null array, null
#> 3    type array, null must be array,null array, null
#>                                                                                                                                                                         parentSchema.description
#> 1 Array of location unique identifiers that must be present for submission to be valid. Can be null if no locations are required and all valid locations are specified in the optional property.
#> 2 Array of location unique identifiers that must be present for submission to be valid. Can be null if no locations are required and all valid locations are specified in the optional property.
#> 3 Array of location unique identifiers that must be present for submission to be valid. Can be null if no locations are required and all valid locations are specified in the optional property.
#>   parentSchema.type parentSchema.type                                $ref
#> 1       array, null            string #/$defs/task_ids/location/us_states
#> 2       array, null            string #/$defs/task_ids/location/us_states
#> 3       array, null            string #/$defs/task_ids/location/us_states
#>                                             dataPath
#> 1 /rounds/0/model_tasks/0/task_ids/location/required
#> 2 /rounds/0/model_tasks/1/task_ids/location/required
#> 3 /rounds/1/model_tasks/0/task_ids/location/required

# RESOLVE POINTERS ----
# Resolve pointers manually.
json_list <- jsonlite::read_json(json_path,
                                 simplifyVector = TRUE,
                                 simplifyDataFrame = FALSE
)

json_resolved <- substitute_refs(json_list, json_list["$defs"])
json_resolved
#> $rounds
#> $rounds[[1]]
#> $rounds[[1]]$round_id
#> [1] "round-1"
#> 
#> $rounds[[1]]$model_tasks
#> $rounds[[1]]$model_tasks[[1]]
#> $rounds[[1]]$model_tasks[[1]]$task_ids
#> $rounds[[1]]$model_tasks[[1]]$task_ids$origin_date
#> $rounds[[1]]$model_tasks[[1]]$task_ids$origin_date$required
#> [1] "2022-09-03"
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$origin_date$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$scenario_id
#> $rounds[[1]]$model_tasks[[1]]$task_ids$scenario_id$required
#> [1] 1
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$scenario_id$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$location
#> $rounds[[1]]$model_tasks[[1]]$task_ids$location$required
#>  [1] "01" "02" "04" "05" "06" "08" "09" "10" "11" "12" "13" "15" "16" "17" "18"
#> [16] "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31" "32" "33"
#> [31] "34" "35" "36" "37" "38" "39" "40" "41" "42" "44" "45" "46" "47" "48" "49"
#> [46] "50" "51" "53" "54" "55" "56"
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$location$optional
#> [1] "US"
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$target
#> $rounds[[1]]$model_tasks[[1]]$task_ids$target$required
#> NULL
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$target$optional
#> [1] "weekly rate"
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$horizon
#> $rounds[[1]]$model_tasks[[1]]$task_ids$horizon$required
#> NULL
#> 
#> $rounds[[1]]$model_tasks[[1]]$task_ids$horizon$optional
#> [1] 1 2
#> 
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types
#> $rounds[[1]]$model_tasks[[1]]$output_types$mean
#> $rounds[[1]]$model_tasks[[1]]$output_types$mean$type_id
#> [1] NA
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$mean$value
#> $rounds[[1]]$model_tasks[[1]]$output_types$mean$value$type
#> [1] "integer"
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$mean$value$minimum
#> [1] 0
#> 
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$type_id
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$type_id$required
#> [1] 0.25 0.50 0.75
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$type_id$optional
#> [1] 0.1 0.2 0.3 0.4 0.6 0.7 0.8 0.9
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$value
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$value$type
#> [1] "numeric"
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$value$minimum
#> [1] 0
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$quantile$value$maximum
#> [1] 1
#> 
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$type_id
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$type_id$required
#> [1] 10 20
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$type_id$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$value
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$value$type
#> [1] "numeric"
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$value$minimum
#> [1] 0
#> 
#> $rounds[[1]]$model_tasks[[1]]$output_types$cdf$value$maximum
#> [1] 1
#> 
#> 
#> 
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]
#> $rounds[[1]]$model_tasks[[2]]$task_ids
#> $rounds[[1]]$model_tasks[[2]]$task_ids$origin_date
#> $rounds[[1]]$model_tasks[[2]]$task_ids$origin_date$required
#> [1] "2022-09-03"
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$origin_date$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$scenario_id
#> $rounds[[1]]$model_tasks[[2]]$task_ids$scenario_id$required
#> [1] 1
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$scenario_id$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$location
#> $rounds[[1]]$model_tasks[[2]]$task_ids$location$required
#>  [1] "01" "02" "04" "05" "06" "08" "09" "10" "11" "12" "13" "15" "16" "17" "18"
#> [16] "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31" "32" "33"
#> [31] "34" "35" "36" "37" "38" "39" "40" "41" "42" "44" "45" "46" "47" "48" "49"
#> [46] "50" "51" "53" "54" "55" "56"
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$location$optional
#> [1] "US"
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$target
#> $rounds[[1]]$model_tasks[[2]]$task_ids$target$required
#> NULL
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$target$optional
#> [1] "peak week"
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$horizon
#> $rounds[[1]]$model_tasks[[2]]$task_ids$horizon$required
#> NULL
#> 
#> $rounds[[1]]$model_tasks[[2]]$task_ids$horizon$optional
#> [1] NA
#> 
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$output_types
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$type_id
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$type_id$required
#>  [1] "EW202240" "EW202241" "EW202242" "EW202243" "EW202244" "EW202245"
#>  [7] "EW202246" "EW202247" "EW202248" "EW202249" "EW202250" "EW202251"
#> [13] "EW202252" "EW202301" "EW202302" "EW202303" "EW202304" "EW202305"
#> [19] "EW202306" "EW202307" "EW202308" "EW202309" "EW202310" "EW202311"
#> [25] "EW202312" "EW202313" "EW202314" "EW202315" "EW202316" "EW202317"
#> [31] "EW202318" "EW202319" "EW202320"
#> 
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$type_id$optional
#> NULL
#> 
#> 
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$value
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$value$type
#> [1] "numeric"
#> 
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$value$minimum
#> [1] 0
#> 
#> $rounds[[1]]$model_tasks[[2]]$output_types$cdf$value$maximum
#> [1] 1
#> 
#> 
#> 
#> 
#> 
#> 
#> $rounds[[1]]$submissions_due
#> $rounds[[1]]$submissions_due$start
#> [1] "2022-09-01"
#> 
#> $rounds[[1]]$submissions_due$end
#> [1] "2022-09-05"
#> 
#> 
#> 
#> $rounds[[2]]
#> $rounds[[2]]$round_id
#> [1] "round-2"
#> 
#> $rounds[[2]]$model_tasks
#> $rounds[[2]]$model_tasks[[1]]
#> $rounds[[2]]$model_tasks[[1]]$task_ids
#> $rounds[[2]]$model_tasks[[1]]$task_ids$origin_date
#> $rounds[[2]]$model_tasks[[1]]$task_ids$origin_date$required
#> [1] "2022-10-01"
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$origin_date$optional
#> NULL
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$scenario_id
#> $rounds[[2]]$model_tasks[[1]]$task_ids$scenario_id$required
#> NULL
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$scenario_id$optional
#> [1] 2 3
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$location
#> $rounds[[2]]$model_tasks[[1]]$task_ids$location$required
#>  [1] "01" "02" "04" "05" "06" "08" "09" "10" "11" "12" "13" "15" "16" "17" "18"
#> [16] "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31" "32" "33"
#> [31] "34" "35" "36" "37" "38" "39" "40" "41" "42" "44" "45" "46" "47" "48" "49"
#> [46] "50" "51" "53" "54" "55" "56"
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$location$optional
#> [1] "US"
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$target
#> $rounds[[2]]$model_tasks[[1]]$task_ids$target$required
#> NULL
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$target$optional
#> [1] "weekly rate"
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$age_group
#> $rounds[[2]]$model_tasks[[1]]$task_ids$age_group$required
#> NULL
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$age_group$optional
#> [1] "0-5"   "6-18"  "19-24" "25-64" "65+"  
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$horizon
#> $rounds[[2]]$model_tasks[[1]]$task_ids$horizon$required
#> NULL
#> 
#> $rounds[[2]]$model_tasks[[1]]$task_ids$horizon$optional
#> [1] 1 2
#> 
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$output_types
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$type_id
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$type_id$required
#> [1] 0.25 0.50 0.75
#> 
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$type_id$optional
#> [1] 0.1 0.2 0.3 0.4 0.6 0.7 0.8 0.9
#> 
#> 
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$value
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$value$type
#> [1] "integer"
#> 
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$value$minimum
#> [1] 0
#> 
#> $rounds[[2]]$model_tasks[[1]]$output_types$quantile$value$maximum
#> [1] 1
#> 
#> 
#> 
#> 
#> 
#> 
#> $rounds[[2]]$submissions_due
#> $rounds[[2]]$submissions_due$start
#> [1] "2022-09-28"
#> 
#> $rounds[[2]]$submissions_due$end
#> [1] "2022-10-01"
#> 
#> 
#> $rounds[[2]]$last_data_date
#> [1] "2022-09-30"
#> 
#> 
#> 
#> $`$defs`
#> $`$defs`$task_ids
#> $`$defs`$task_ids$location
#> $`$defs`$task_ids$location$us_states
#>  [1] "01" "02" "04" "05" "06" "08" "09" "10" "11" "12" "13" "15" "16" "17" "18"
#> [16] "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31" "32" "33"
#> [31] "34" "35" "36" "37" "38" "39" "40" "41" "42" "44" "45" "46" "47" "48" "49"
#> [46] "50" "51" "53" "54" "55" "56"

# CONVERT TO JSON TO SERIALSE FOR VALIDATION
# Attempt at serialising with unboxing.
json <- json_resolved |> jsonlite::toJSON(
    null = "null",
    na = "string",
    pretty = TRUE,
    auto_unbox = TRUE
)
# All vectors serialised as arrays so generating schema errors where arrays
# expected
schema$validate(json, verbose = TRUE) |>
    attr("errors")
#>                                             instancePath
#> 1  /rounds/0/model_tasks/0/task_ids/origin_date/required
#> 2  /rounds/0/model_tasks/0/task_ids/scenario_id/required
#> 3     /rounds/0/model_tasks/0/task_ids/location/optional
#> 4       /rounds/0/model_tasks/0/task_ids/target/optional
#> 5      /rounds/0/model_tasks/0/output_types/mean/type_id
#> 6  /rounds/0/model_tasks/1/task_ids/origin_date/required
#> 7  /rounds/0/model_tasks/1/task_ids/scenario_id/required
#> 8     /rounds/0/model_tasks/1/task_ids/location/optional
#> 9       /rounds/0/model_tasks/1/task_ids/target/optional
#> 10     /rounds/0/model_tasks/1/task_ids/horizon/optional
#> 11 /rounds/1/model_tasks/0/task_ids/origin_date/required
#> 12    /rounds/1/model_tasks/0/task_ids/location/optional
#> 13      /rounds/1/model_tasks/0/task_ids/target/optional
#>                                                                                                                    schemaPath
#> 1  #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/required/type
#> 2  #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/scenario_id/properties/required/type
#> 3     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/optional/type
#> 4       #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/optional/type
#> 5      #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/mean/properties/type_id/type
#> 6  #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/required/type
#> 7  #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/scenario_id/properties/required/type
#> 8     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/optional/type
#> 9       #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/optional/type
#> 10     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/horizon/properties/optional/type
#> 11 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/required/type
#> 12    #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/location/properties/optional/type
#> 13      #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/optional/type
#>    keyword        type            message      schema
#> 1     type array, null must be array,null array, null
#> 2     type array, null must be array,null array, null
#> 3     type array, null must be array,null array, null
#> 4     type array, null must be array,null array, null
#> 5     type       array      must be array       array
#> 6     type array, null must be array,null array, null
#> 7     type array, null must be array,null array, null
#> 8     type array, null must be array,null array, null
#> 9     type array, null must be array,null array, null
#> 10    type null, array must be null,array null, array
#> 11    type array, null must be array,null array, null
#> 12    type array, null must be array,null array, null
#> 13    type array, null must be array,null array, null
#>                                                                                                                                                                                   parentSchema.description
#> 1  Array of origin date unique identifiers that must be present for submission to be valid. Can be null if no origin dates are required and all valid origin dates are specified in the optional property.
#> 2                      Array of identifiers of scenarios that must be present in a valid submission. Can be null if no scenario ids are required and all valid ids are specified in the optional property.
#> 3                                                       Array of valid but not required unique location identifiers. Can be null if all locations are required and are specified in the required property.
#> 4                                                           Array of valid but not required unique target identifiers. Can be null if all targets are required and are specified in the required property.
#> 5                                                                                                                      Not used for mean output type. Must be an array containing the single element 'NA'.
#> 6  Array of origin date unique identifiers that must be present for submission to be valid. Can be null if no origin dates are required and all valid origin dates are specified in the optional property.
#> 7                      Array of identifiers of scenarios that must be present in a valid submission. Can be null if no scenario ids are required and all valid ids are specified in the optional property.
#> 8                                                       Array of valid but not required unique location identifiers. Can be null if all locations are required and are specified in the required property.
#> 9                                                           Array of valid but not required unique target identifiers. Can be null if all targets are required and are specified in the required property.
#> 10                                                        Array of valid but not required unique horizon identifiers. Can be null if all horizons are required and are specified in the required property.
#> 11 Array of origin date unique identifiers that must be present for submission to be valid. Can be null if no origin dates are required and all valid origin dates are specified in the optional property.
#> 12                                                      Array of valid but not required unique location identifiers. Can be null if all locations are required and are specified in the required property.
#> 13                                                          Array of valid but not required unique target identifiers. Can be null if all targets are required and are specified in the required property.
#>    parentSchema.type parentSchema.items.type parentSchema.items.format
#> 1        array, null                  string                      date
#> 2        array, null         integer, string                      <NA>
#> 3        array, null                  string                      <NA>
#> 4        array, null                  string                      <NA>
#> 5              array                    NULL                      <NA>
#> 6        array, null                  string                      date
#> 7        array, null         integer, string                      <NA>
#> 8        array, null                  string                      <NA>
#> 9        array, null                  string                      <NA>
#> 10       null, array         integer, string                      <NA>
#> 11       array, null                  string                      date
#> 12       array, null                  string                      <NA>
#> 13       array, null                  string                      <NA>
#>    parentSchema.items.enum parentSchema.items.maxItems parentSchema.default
#> 1                     NULL                          NA                 NULL
#> 2                     NULL                          NA                 NULL
#> 3                     NULL                          NA                 NULL
#> 4                     NULL                          NA                 NULL
#> 5                       NA                           1                   NA
#> 6                     NULL                          NA                 NULL
#> 7                     NULL                          NA                 NULL
#> 8                     NULL                          NA                 NULL
#> 9                     NULL                          NA                 NULL
#> 10                    NULL                          NA                 NULL
#> 11                    NULL                          NA                 NULL
#> 12                    NULL                          NA                 NULL
#> 13                    NULL                          NA                 NULL
#>           data                                              dataPath
#> 1   2022-09-03 /rounds/0/model_tasks/0/task_ids/origin_date/required
#> 2            1 /rounds/0/model_tasks/0/task_ids/scenario_id/required
#> 3           US    /rounds/0/model_tasks/0/task_ids/location/optional
#> 4  weekly rate      /rounds/0/model_tasks/0/task_ids/target/optional
#> 5           NA     /rounds/0/model_tasks/0/output_types/mean/type_id
#> 6   2022-09-03 /rounds/0/model_tasks/1/task_ids/origin_date/required
#> 7            1 /rounds/0/model_tasks/1/task_ids/scenario_id/required
#> 8           US    /rounds/0/model_tasks/1/task_ids/location/optional
#> 9    peak week      /rounds/0/model_tasks/1/task_ids/target/optional
#> 10          NA     /rounds/0/model_tasks/1/task_ids/horizon/optional
#> 11  2022-10-01 /rounds/1/model_tasks/0/task_ids/origin_date/required
#> 12          US    /rounds/1/model_tasks/0/task_ids/location/optional
#> 13 weekly rate      /rounds/1/model_tasks/0/task_ids/target/optional

json <- json_resolved |>
    schema$serialise()

# Use Schema to validate JSON
schema$validate(json, verbose = TRUE) |>
    attr("errors")
#>                                                 instancePath
#> 1      /rounds/0/model_tasks/0/task_ids/origin_date/optional
#> 2      /rounds/0/model_tasks/0/task_ids/scenario_id/optional
#> 3           /rounds/0/model_tasks/0/task_ids/target/required
#> 4          /rounds/0/model_tasks/0/task_ids/horizon/required
#> 5        /rounds/0/model_tasks/0/output_types/mean/type_id/0
#> 6  /rounds/0/model_tasks/0/output_types/cdf/type_id/optional
#> 7        /rounds/0/model_tasks/0/output_types/cdf/value/type
#> 8      /rounds/0/model_tasks/1/task_ids/origin_date/optional
#> 9      /rounds/0/model_tasks/1/task_ids/scenario_id/optional
#> 10          /rounds/0/model_tasks/1/task_ids/target/required
#> 11         /rounds/0/model_tasks/1/task_ids/horizon/required
#> 12       /rounds/0/model_tasks/1/task_ids/horizon/optional/0
#> 13 /rounds/0/model_tasks/1/output_types/cdf/type_id/optional
#> 14       /rounds/0/model_tasks/1/output_types/cdf/value/type
#> 15     /rounds/1/model_tasks/0/task_ids/origin_date/optional
#> 16     /rounds/1/model_tasks/0/task_ids/scenario_id/required
#> 17          /rounds/1/model_tasks/0/task_ids/target/required
#> 18         /rounds/1/model_tasks/0/task_ids/horizon/required
#> 19       /rounds/1/model_tasks/0/task_ids/age_group/required
#>                                                                                                                                   schemaPath
#> 1                 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/optional/type
#> 2                 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/scenario_id/properties/optional/type
#> 3                      #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/required/type
#> 4                     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/horizon/properties/required/type
#> 5               #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/mean/properties/type_id/items/enum
#> 6  #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/cdf/properties/type_id/properties/optional/type
#> 7        #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/cdf/properties/value/properties/type/enum
#> 8                 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/optional/type
#> 9                 #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/scenario_id/properties/optional/type
#> 10                     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/required/type
#> 11                    #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/horizon/properties/required/type
#> 12              #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/horizon/properties/optional/items/type
#> 13 #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/cdf/properties/type_id/properties/optional/type
#> 14       #/properties/rounds/items/properties/model_tasks/items/properties/output_types/properties/cdf/properties/value/properties/type/enum
#> 15                #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/origin_date/properties/optional/type
#> 16                #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/scenario_id/properties/required/type
#> 17                     #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/target/properties/required/type
#> 18                    #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/horizon/properties/required/type
#> 19                  #/properties/rounds/items/properties/model_tasks/items/properties/task_ids/properties/age_group/properties/required/type
#>    keyword     params.type     params.allowedValues
#> 1     type     array, null                     NULL
#> 2     type     null, array                     NULL
#> 3     type     array, null                     NULL
#> 4     type     array, null                     NULL
#> 5     enum            NULL                       NA
#> 6     type     array, null                     NULL
#> 7     enum            NULL numeric, integer, double
#> 8     type     array, null                     NULL
#> 9     type     null, array                     NULL
#> 10    type     array, null                     NULL
#> 11    type     array, null                     NULL
#> 12    type integer, string                     NULL
#> 13    type     array, null                     NULL
#> 14    enum            NULL numeric, integer, double
#> 15    type     array, null                     NULL
#> 16    type     array, null                     NULL
#> 17    type     array, null                     NULL
#> 18    type     array, null                     NULL
#> 19    type     array, null                     NULL
#>                                       message                   schema
#> 1                          must be array,null              array, null
#> 2                          must be null,array              null, array
#> 3                          must be array,null              array, null
#> 4                          must be array,null              array, null
#> 5  must be equal to one of the allowed values                       NA
#> 6                          must be array,null              array, null
#> 7  must be equal to one of the allowed values numeric, integer, double
#> 8                          must be array,null              array, null
#> 9                          must be null,array              null, array
#> 10                         must be array,null              array, null
#> 11                         must be array,null              array, null
#> 12                     must be integer,string          integer, string
#> 13                         must be array,null              array, null
#> 14 must be equal to one of the allowed values numeric, integer, double
#> 15                         must be array,null              array, null
#> 16                         must be array,null              array, null
#> 17                         must be array,null              array, null
#> 18                         must be array,null              array, null
#> 19                         must be array,null              array, null
#>                                                                                                                                                                             parentSchema.description
#> 1                                           Array of valid but not required unique origin date identifiers. Can be null if all origin dates are required and are specified in the required property.
#> 2                                                    Array of identifiers of valid but not required scenarios. Can be null if all scenarios are required and are specified in the required property.
#> 3           Array of target unique identifiers that must be present for submission to be valid. Can be null if no targets are required and all valid targets are specified in the optional property.
#> 4        Array of horizon unique identifiers that must be present for submission to be valid. Can be null if no horizons are required and all valid horizons are specified in the optional property.
#> 5                                                                                                                                                                                               <NA>
#> 6                                                   Array of valid but not required unique target values. Can be null if all ptarget values are required and are specified in the required property.
#> 7                                                                                                                                              Data type of cumulative distribution function values.
#> 8                                           Array of valid but not required unique origin date identifiers. Can be null if all origin dates are required and are specified in the required property.
#> 9                                                    Array of identifiers of valid but not required scenarios. Can be null if all scenarios are required and are specified in the required property.
#> 10          Array of target unique identifiers that must be present for submission to be valid. Can be null if no targets are required and all valid targets are specified in the optional property.
#> 11       Array of horizon unique identifiers that must be present for submission to be valid. Can be null if no horizons are required and all valid horizons are specified in the optional property.
#> 12                                                                                                                                                                                              <NA>
#> 13                                                  Array of valid but not required unique target values. Can be null if all ptarget values are required and are specified in the required property.
#> 14                                                                                                                                             Data type of cumulative distribution function values.
#> 15                                          Array of valid but not required unique origin date identifiers. Can be null if all origin dates are required and are specified in the required property.
#> 16               Array of identifiers of scenarios that must be present in a valid submission. Can be null if no scenario ids are required and all valid ids are specified in the optional property.
#> 17          Array of target unique identifiers that must be present for submission to be valid. Can be null if no targets are required and all valid targets are specified in the optional property.
#> 18       Array of horizon unique identifiers that must be present for submission to be valid. Can be null if no horizons are required and all valid horizons are specified in the optional property.
#> 19 Array of age group unique identifiers that must be present for submission to be valid. Can be null if no age groups are required and all valid age groups are specified in the optional property.
#>    parentSchema.type parentSchema.items.type parentSchema.items.format
#> 1        array, null                  string                      date
#> 2        null, array         integer, string                      <NA>
#> 3        array, null                  string                      <NA>
#> 4        array, null         integer, string                      <NA>
#> 5               NULL                    NULL                      <NA>
#> 6        array, null                    NULL                      <NA>
#> 7               NULL                    NULL                      <NA>
#> 8        array, null                  string                      date
#> 9        null, array         integer, string                      <NA>
#> 10       array, null                  string                      <NA>
#> 11       array, null         integer, string                      <NA>
#> 12   integer, string                    NULL                      <NA>
#> 13       array, null                    NULL                      <NA>
#> 14              NULL                    NULL                      <NA>
#> 15       array, null                  string                      date
#> 16       array, null         integer, string                      <NA>
#> 17       array, null                  string                      <NA>
#> 18       array, null         integer, string                      <NA>
#> 19       array, null                  string                      <NA>
#>                                parentSchema.items.oneOf
#> 1                                                  NULL
#> 2                                                  NULL
#> 3                                                  NULL
#> 4                                                  NULL
#> 5                                                  NULL
#> 6  number, string, 0, NA, NA, ^EW[0-9]{6}, NA, 8, NA, 8
#> 7                                                  NULL
#> 8                                                  NULL
#> 9                                                  NULL
#> 10                                                 NULL
#> 11                                                 NULL
#> 12                                                 NULL
#> 13 number, string, 0, NA, NA, ^EW[0-9]{6}, NA, 8, NA, 8
#> 14                                                 NULL
#> 15                                                 NULL
#> 16                                                 NULL
#> 17                                                 NULL
#> 18                                                 NULL
#> 19                                                 NULL
#>           parentSchema.enum parentSchema.maxItems parentSchema.example    data
#> 1                      NULL                    NA                 <NA>    NULL
#> 2                      NULL                    NA                 <NA>    NULL
#> 3                      NULL                    NA                 <NA>    NULL
#> 4                      NULL                    NA                 <NA>    NULL
#> 5                        NA                     1                 <NA>    NULL
#> 6                      NULL                    NA                 <NA>    NULL
#> 7  numeric, integer, double                    NA               double numeric
#> 8                      NULL                    NA                 <NA>    NULL
#> 9                      NULL                    NA                 <NA>    NULL
#> 10                     NULL                    NA                 <NA>    NULL
#> 11                     NULL                    NA                 <NA>    NULL
#> 12                     NULL                    NA                 <NA>    NULL
#> 13                     NULL                    NA                 <NA>    NULL
#> 14 numeric, integer, double                    NA               double numeric
#> 15                     NULL                    NA                 <NA>    NULL
#> 16                     NULL                    NA                 <NA>    NULL
#> 17                     NULL                    NA                 <NA>    NULL
#> 18                     NULL                    NA                 <NA>    NULL
#> 19                     NULL                    NA                 <NA>    NULL
#>                                                     dataPath
#> 1      /rounds/0/model_tasks/0/task_ids/origin_date/optional
#> 2      /rounds/0/model_tasks/0/task_ids/scenario_id/optional
#> 3           /rounds/0/model_tasks/0/task_ids/target/required
#> 4          /rounds/0/model_tasks/0/task_ids/horizon/required
#> 5        /rounds/0/model_tasks/0/output_types/mean/type_id/0
#> 6  /rounds/0/model_tasks/0/output_types/cdf/type_id/optional
#> 7        /rounds/0/model_tasks/0/output_types/cdf/value/type
#> 8      /rounds/0/model_tasks/1/task_ids/origin_date/optional
#> 9      /rounds/0/model_tasks/1/task_ids/scenario_id/optional
#> 10          /rounds/0/model_tasks/1/task_ids/target/required
#> 11         /rounds/0/model_tasks/1/task_ids/horizon/required
#> 12       /rounds/0/model_tasks/1/task_ids/horizon/optional/0
#> 13 /rounds/0/model_tasks/1/output_types/cdf/type_id/optional
#> 14       /rounds/0/model_tasks/1/output_types/cdf/value/type
#> 15     /rounds/1/model_tasks/0/task_ids/origin_date/optional
#> 16     /rounds/1/model_tasks/0/task_ids/scenario_id/required
#> 17          /rounds/1/model_tasks/0/task_ids/target/required
#> 18         /rounds/1/model_tasks/0/task_ids/horizon/required
#> 19       /rounds/1/model_tasks/0/task_ids/age_group/required

# Errors because NAs encoded as NULL whereas NULL encoded as empty.
jsonlite::prettify(json)
#> {
#>     "rounds": [
#>         {
#>             "round_id": "round-1",
#>             "model_tasks": [
#>                 {
#>                     "task_ids": {
#>                         "origin_date": {
#>                             "required": [
#>                                 "2022-09-03"
#>                             ],
#>                             "optional": {
#> 
#>                             }
#>                         },
#>                         "scenario_id": {
#>                             "required": [
#>                                 1
#>                             ],
#>                             "optional": {
#> 
#>                             }
#>                         },
#>                         "location": {
#>                             "required": [
#>                                 "01",
#>                                 "02",
#>                                 "04",
#>                                 "05",
#>                                 "06",
#>                                 "08",
#>                                 "09",
#>                                 "10",
#>                                 "11",
#>                                 "12",
#>                                 "13",
#>                                 "15",
#>                                 "16",
#>                                 "17",
#>                                 "18",
#>                                 "19",
#>                                 "20",
#>                                 "21",
#>                                 "22",
#>                                 "23",
#>                                 "24",
#>                                 "25",
#>                                 "26",
#>                                 "27",
#>                                 "28",
#>                                 "29",
#>                                 "30",
#>                                 "31",
#>                                 "32",
#>                                 "33",
#>                                 "34",
#>                                 "35",
#>                                 "36",
#>                                 "37",
#>                                 "38",
#>                                 "39",
#>                                 "40",
#>                                 "41",
#>                                 "42",
#>                                 "44",
#>                                 "45",
#>                                 "46",
#>                                 "47",
#>                                 "48",
#>                                 "49",
#>                                 "50",
#>                                 "51",
#>                                 "53",
#>                                 "54",
#>                                 "55",
#>                                 "56"
#>                             ],
#>                             "optional": [
#>                                 "US"
#>                             ]
#>                         },
#>                         "target": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 "weekly rate"
#>                             ]
#>                         },
#>                         "horizon": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 1,
#>                                 2
#>                             ]
#>                         }
#>                     },
#>                     "output_types": {
#>                         "mean": {
#>                             "type_id": [
#>                                 null
#>                             ],
#>                             "value": {
#>                                 "type": "integer",
#>                                 "minimum": 0
#>                             }
#>                         },
#>                         "quantile": {
#>                             "type_id": {
#>                                 "required": [
#>                                     0.25,
#>                                     0.5,
#>                                     0.75
#>                                 ],
#>                                 "optional": [
#>                                     0.1,
#>                                     0.2,
#>                                     0.3,
#>                                     0.4,
#>                                     0.6,
#>                                     0.7,
#>                                     0.8,
#>                                     0.9
#>                                 ]
#>                             },
#>                             "value": {
#>                                 "type": "numeric",
#>                                 "minimum": 0,
#>                                 "maximum": 1
#>                             }
#>                         },
#>                         "cdf": {
#>                             "type_id": {
#>                                 "required": [
#>                                     10,
#>                                     20
#>                                 ],
#>                                 "optional": {
#> 
#>                                 }
#>                             },
#>                             "value": {
#>                                 "type": [
#>                                     "numeric"
#>                                 ],
#>                                 "minimum": 0,
#>                                 "maximum": 1
#>                             }
#>                         }
#>                     }
#>                 },
#>                 {
#>                     "task_ids": {
#>                         "origin_date": {
#>                             "required": [
#>                                 "2022-09-03"
#>                             ],
#>                             "optional": {
#> 
#>                             }
#>                         },
#>                         "scenario_id": {
#>                             "required": [
#>                                 1
#>                             ],
#>                             "optional": {
#> 
#>                             }
#>                         },
#>                         "location": {
#>                             "required": [
#>                                 "01",
#>                                 "02",
#>                                 "04",
#>                                 "05",
#>                                 "06",
#>                                 "08",
#>                                 "09",
#>                                 "10",
#>                                 "11",
#>                                 "12",
#>                                 "13",
#>                                 "15",
#>                                 "16",
#>                                 "17",
#>                                 "18",
#>                                 "19",
#>                                 "20",
#>                                 "21",
#>                                 "22",
#>                                 "23",
#>                                 "24",
#>                                 "25",
#>                                 "26",
#>                                 "27",
#>                                 "28",
#>                                 "29",
#>                                 "30",
#>                                 "31",
#>                                 "32",
#>                                 "33",
#>                                 "34",
#>                                 "35",
#>                                 "36",
#>                                 "37",
#>                                 "38",
#>                                 "39",
#>                                 "40",
#>                                 "41",
#>                                 "42",
#>                                 "44",
#>                                 "45",
#>                                 "46",
#>                                 "47",
#>                                 "48",
#>                                 "49",
#>                                 "50",
#>                                 "51",
#>                                 "53",
#>                                 "54",
#>                                 "55",
#>                                 "56"
#>                             ],
#>                             "optional": [
#>                                 "US"
#>                             ]
#>                         },
#>                         "target": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 "peak week"
#>                             ]
#>                         },
#>                         "horizon": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 null
#>                             ]
#>                         }
#>                     },
#>                     "output_types": {
#>                         "cdf": {
#>                             "type_id": {
#>                                 "required": [
#>                                     "EW202240",
#>                                     "EW202241",
#>                                     "EW202242",
#>                                     "EW202243",
#>                                     "EW202244",
#>                                     "EW202245",
#>                                     "EW202246",
#>                                     "EW202247",
#>                                     "EW202248",
#>                                     "EW202249",
#>                                     "EW202250",
#>                                     "EW202251",
#>                                     "EW202252",
#>                                     "EW202301",
#>                                     "EW202302",
#>                                     "EW202303",
#>                                     "EW202304",
#>                                     "EW202305",
#>                                     "EW202306",
#>                                     "EW202307",
#>                                     "EW202308",
#>                                     "EW202309",
#>                                     "EW202310",
#>                                     "EW202311",
#>                                     "EW202312",
#>                                     "EW202313",
#>                                     "EW202314",
#>                                     "EW202315",
#>                                     "EW202316",
#>                                     "EW202317",
#>                                     "EW202318",
#>                                     "EW202319",
#>                                     "EW202320"
#>                                 ],
#>                                 "optional": {
#> 
#>                                 }
#>                             },
#>                             "value": {
#>                                 "type": [
#>                                     "numeric"
#>                                 ],
#>                                 "minimum": 0,
#>                                 "maximum": 1
#>                             }
#>                         }
#>                     }
#>                 }
#>             ],
#>             "submissions_due": {
#>                 "start": [
#>                     "2022-09-01"
#>                 ],
#>                 "end": [
#>                     "2022-09-05"
#>                 ]
#>             }
#>         },
#>         {
#>             "round_id": "round-2",
#>             "model_tasks": [
#>                 {
#>                     "task_ids": {
#>                         "origin_date": {
#>                             "required": [
#>                                 "2022-10-01"
#>                             ],
#>                             "optional": {
#> 
#>                             }
#>                         },
#>                         "scenario_id": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 2,
#>                                 3
#>                             ]
#>                         },
#>                         "location": {
#>                             "required": [
#>                                 "01",
#>                                 "02",
#>                                 "04",
#>                                 "05",
#>                                 "06",
#>                                 "08",
#>                                 "09",
#>                                 "10",
#>                                 "11",
#>                                 "12",
#>                                 "13",
#>                                 "15",
#>                                 "16",
#>                                 "17",
#>                                 "18",
#>                                 "19",
#>                                 "20",
#>                                 "21",
#>                                 "22",
#>                                 "23",
#>                                 "24",
#>                                 "25",
#>                                 "26",
#>                                 "27",
#>                                 "28",
#>                                 "29",
#>                                 "30",
#>                                 "31",
#>                                 "32",
#>                                 "33",
#>                                 "34",
#>                                 "35",
#>                                 "36",
#>                                 "37",
#>                                 "38",
#>                                 "39",
#>                                 "40",
#>                                 "41",
#>                                 "42",
#>                                 "44",
#>                                 "45",
#>                                 "46",
#>                                 "47",
#>                                 "48",
#>                                 "49",
#>                                 "50",
#>                                 "51",
#>                                 "53",
#>                                 "54",
#>                                 "55",
#>                                 "56"
#>                             ],
#>                             "optional": [
#>                                 "US"
#>                             ]
#>                         },
#>                         "target": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 "weekly rate"
#>                             ]
#>                         },
#>                         "age_group": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 "0-5",
#>                                 "6-18",
#>                                 "19-24",
#>                                 "25-64",
#>                                 "65+"
#>                             ]
#>                         },
#>                         "horizon": {
#>                             "required": {
#> 
#>                             },
#>                             "optional": [
#>                                 1,
#>                                 2
#>                             ]
#>                         }
#>                     },
#>                     "output_types": {
#>                         "quantile": {
#>                             "type_id": {
#>                                 "required": [
#>                                     0.25,
#>                                     0.5,
#>                                     0.75
#>                                 ],
#>                                 "optional": [
#>                                     0.1,
#>                                     0.2,
#>                                     0.3,
#>                                     0.4,
#>                                     0.6,
#>                                     0.7,
#>                                     0.8,
#>                                     0.9
#>                                 ]
#>                             },
#>                             "value": {
#>                                 "type": "integer",
#>                                 "minimum": 0,
#>                                 "maximum": 1
#>                             }
#>                         }
#>                     }
#>                 }
#>             ],
#>             "submissions_due": {
#>                 "start": [
#>                     "2022-09-28"
#>                 ],
#>                 "end": [
#>                     "2022-10-01"
#>                 ]
#>             },
#>             "last_data_date": [
#>                 "2022-09-30"
#>             ]
#>         }
#>     ],
#>     "$defs": {
#>         "task_ids": {
#>             "location": {
#>                 "us_states": [
#>                     "01",
#>                     "02",
#>                     "04",
#>                     "05",
#>                     "06",
#>                     "08",
#>                     "09",
#>                     "10",
#>                     "11",
#>                     "12",
#>                     "13",
#>                     "15",
#>                     "16",
#>                     "17",
#>                     "18",
#>                     "19",
#>                     "20",
#>                     "21",
#>                     "22",
#>                     "23",
#>                     "24",
#>                     "25",
#>                     "26",
#>                     "27",
#>                     "28",
#>                     "29",
#>                     "30",
#>                     "31",
#>                     "32",
#>                     "33",
#>                     "34",
#>                     "35",
#>                     "36",
#>                     "37",
#>                     "38",
#>                     "39",
#>                     "40",
#>                     "41",
#>                     "42",
#>                     "44",
#>                     "45",
#>                     "46",
#>                     "47",
#>                     "48",
#>                     "49",
#>                     "50",
#>                     "51",
#>                     "53",
#>                     "54",
#>                     "55",
#>                     "56"
#>                 ]
#>             }
#>         }
#>     }
#> }
#> 

Created on 2022-11-21 with reprex v2.0.2