docs: def items_differ(jsonitems, dbitems, subfield_dict):
      """ check whether or not jsonitems and dbitems differ """
  
      # short circuit common cases
      if len(jsonitems) == len(dbitems) == 0:
          # both are empty
          return False
      elif len(jsonitems) != len(dbitems):
          # if lengths differ, they're definitely different
          return True
  
      original_jsonitems = jsonitems
      jsonitems = copy.deepcopy(jsonitems)
      keys = jsonitems[0].keys()
  
      # go over dbitems looking for matches
      for dbitem in dbitems:
          order = getattr(dbitem, 'order', None)
          match = None
          for i, jsonitem in enumerate(jsonitems):
              # check if all keys (excluding subfields) match
              for k in keys:
                  if k not in subfield_dict and getattr(dbitem, k) != jsonitem.get(k, None):
                      break
              else:
                  # all fields match so far, possibly equal, just check subfields now
                  for k in subfield_dict:
                      jsonsubitems = jsonitem[k]
                      dbsubitems = list(getattr(dbitem, k).all())
                      if items_differ(jsonsubitems, dbsubitems, subfield_dict[k][2]):
                          break
                  else:
                      # if the dbitem sets 'order', then the order matters
                      if order is not None and int(order) != original_jsonitems.index(jsonitem):
                          break
                      # these items are equal, so let's mark it for removal
                      match = i
                      break
  
          if match is not None:
              # item exists in both, remove from jsonitems
              jsonitems.pop(match)
          else:
              # exists in db but not json
              return True
  
      # if we get here, jsonitems has to be empty because we asserted that the length was
      # the same and we found a match for each thing in dbitems, here's a safety check just in case
      if jsonitems:       # pragma: no cover
          return True
  
      return False