gschjetne / json-mop

A metaclass for bridging CLOS and JSON objects
MIT License
61 stars 9 forks source link

Add required slot to json-serializable-slot class #14

Open kilianmh opened 1 year ago

kilianmh commented 1 year ago

It is quite useful to specify required slots.

Therefore we can add an required slot to the json-serializable-slot class. Then in the initialize-slots-from-json function it will be checked for each required slot.

I'm however not sure if there should be a warning or an error when a required slot is not present? @gschjetne

gschjetne commented 1 year ago

I'm not sure whether to put it in the scope of the project or not. With CLOS one can do a method combination with json-to-clos and add any validation necessary, including checking that required slots are present.

On the other hand, I have speculated about adding JSON schema support in #4, which would entail having this feature.

kilianmh commented 1 year ago

Indeed, with a method json-to-clos :after and (input hash-table) it should work:

(defmethod json-to-clos :after ((input hash-table) class &rest initargs)
  (declare (ignore initargs))
  (let ((class-object
          (find-class class)))
    (declare (type json-serializable-class class-object))
    (loop for slot in (closer-mop:class-direct-slots class-object)
          do (when (required slot)
               (let ((json-key-name
                       (json-key-name slot)))
                 (declare (type string json-key-name))
                 (unless (gethash json-key-name input)
                   (error (concatenate 'string "The key \"" json-key-name "\" is not present in " (format nil "~A" input)
                                      ", but required in "(format nil "~A" class-object)"."))))))))

The only downside is that we have to find-class, class-direct-slots, and loop two times, which decreases performance slightly.

The json-serializable-slot class definition could look similar to this:

(defclass json-serializable-slot (closer-mop:standard-direct-slot-definition)
  ((json-key :initarg :json-key
             :initform nil
             :reader json-key-name)
   (json-type :initarg :json-type
              :initform :any
              :reader json-type)
   (required :initarg :required
             :initform nil
             :reader required)))