Search code examples
jsonracket

Issue writing to JSON file - invalid key


Context: My professor wants me to set up an autograder for his class (for gradescope) and I am encountering some difficulties when putting the results into a results.json file

Error:

write-json: expected argument of type <legal JSON key value>; given: "tests"
  context...:
   .../json/main.rkt:132:2: loop
   .../json/main.rkt:94:0: write-json*
   .../source/autograder.rkt:48:0: grade
   body of '#%mzc:autograder

Code:

(define results-file "../results/results.json")

(define (starter-data-entry name) 
  (make-hash `(("score" . 0) ("max_score" . 0) ("output" . "Will be manually graded") ("name" . ,name))))

(define results-data
  (make-hash `(("tests" . ,(for/list ([i (in-range 1 13)])
                                 (starter-data-entry (format "question~a" i)))))))

(define (grade-question fn-hw fn-sol input-cases id max-score)
  (let* ([results
          (for/fold ([acc (cons 0 0)]) ; acc is a pair (total-tests . passed-tests)
                    ([input (in-list input-cases)])
            (let* ([hw-result (apply fn-hw input)]
                   [sol-result (apply fn-sol input)]
                   [total-tests (car acc)]
                   [passed-tests (cdr acc)])
              (if (equal? hw-result sol-result)
                  (cons (+ total-tests 1) (+ passed-tests 1))
                  (cons (+ total-tests 1) passed-tests))))]
         [total-tests (car results)]
         [passed-tests (cdr results)]
         [score (floor (* max-score (/ passed-tests total-tests)))]
         [output (format "Passed ~a out of ~a test cases." passed-tests total-tests)])
    (hash-update! results-data "tests"
        (lambda (tests)
            (map (lambda (test)
                (if (equal? (hash-ref test "name") id)
                    (hash "name" id "score" score "max_score" max-score "output" output)
                    test))
                tests)))
    (void)))

(define (write-json-file filepath data)
    (call-with-output-file filepath
        (lambda (out)
            (write-json data out))
        #:exists 'replace))

(define (grade)
    (grade-question hw:sequence sol:sequence (sequence-test-case-generator 50) "question1" 7)
    (grade-question hw:string-append-map sol:string-append-map (string-append-map-test-case-generator 50) "question2" 7)
    ; ... more calls to grade-question, not really relevant

    (display results-data)
    (write-json-file results-file results-data)
)

(grade)

I have a working solution (but it is pretty scuffed - print the results-data hash to std-out and then pass that into a python file that parses the string and writes to the json file), but I am just curious what I am doing wrong because I really cannot figure it out.

There's a lot of holes in my racket knowledge and I'm not that proficient in it so maybe it is due a silly mistake I am unaware of; I know its not a permissions issue with writing to the file and the results.json file already exists. I have tried to write the template "results-data" to the json file also - which gave me the same error - so the hash-update! in grade-question shouldn't be a part of the issue.


Solution

  • The primary JSON library for Racket represents JSON objects as hash tables with symbols as keys, not strings.

    Trivial example:

    #lang racket/base
    
    (require json)
    
    (define (starter-data-entry name) 
      (hasheq 'score 0 'max_score 0 'output "Will be manually graded" 'name name))
    
    (write-json (starter-data-entry "foo"))