docs: public NotificationRecord[] readNotificationRecords(InputStream in) throws ConversionException, IOException, ClassNotFoundException {
          JSONArray json = parseArray(in);
          int size = json.size();
          NotificationRecord[] ret = new NotificationRecord[size];
          for (int i = 0; i < size; i++) {
              Object o = json.get(i);
              if (!(o instanceof JSONObject)) {
                  throwConversionException("readNotifications() expects a JSONObject.", o);
              }
              JSONObject obj = (JSONObject) o;
  
              // Fields common to all Notification classes.
              String className = readStringInternal(obj.get(N_CLASSNAME));
              String type = readStringInternal(obj.get(N_TYPE));
              ObjectName source = readObjectName(obj.get(N_SOURCE));
              long sequenceNumber = readLongInternal(obj.get(N_SEQUENCENUMBER));
              long timeStamp = readLongInternal(obj.get(N_TIMESTAMP));
              String message = readStringInternal(obj.get(N_MESSAGE));
              Object userData = readPOJOInternal(obj.get(N_USERDATA));
  
              // Read routing information (host name, server name, server user dir) if this is a routed notification.
              String hostName = readStringInternal(obj.get(N_HOSTNAME));
              String serverName;
              String serverUserDir;
              if (hostName != null) {
                  serverName = readStringInternal(obj.get(N_SERVERNAME));
                  serverUserDir = readStringInternal(obj.get(N_SERVERUSERDIR));
              } else {
                  serverName = null;
                  serverUserDir = null;
              }
  
              Notification n;
              if ("javax.management.Notification".equals(className)) {
                  n = new Notification(type, source, sequenceNumber, timeStamp, message);
              } else if ("javax.management.AttributeChangeNotification".equals(className)) {
                  if (!AttributeChangeNotification.ATTRIBUTE_CHANGE.equals(type)) {
                      throwConversionException("Type for AttributeChangeNotification should be ATTRIBUTE_CHANGE", o);
                  }
                  String attributeName = readStringInternal(obj.get(N_ATTRIBUTENAME));
                  String attributeType = readStringInternal(obj.get(N_ATTRIBUTETYPE));
                  Object oldValue = readPOJOInternal(obj.get(N_OLDVALUE));
                  Object newValue = readPOJOInternal(obj.get(N_NEWVALUE));
                  n = new AttributeChangeNotification(source, sequenceNumber, timeStamp, message, attributeName, attributeType, oldValue, newValue);
              } else if ("javax.management.remote.JMXConnectionNotification".equals(className)) {
                  String connectionId = readStringInternal(obj.get(N_CONNECTIONID));
                  n = new JMXConnectionNotification(type, source, connectionId, sequenceNumber, message, userData);
                  // Replace the value set by the constructor
                  n.setTimeStamp(timeStamp);
              } else if ("javax.management.MBeanServerNotification".equals(className)) {
                  ObjectName objectName = readObjectName(obj.get(N_MBEANNAME));
                  n = new MBeanServerNotification(type, source, sequenceNumber, objectName);
                  // Replace the value set by the constructor
                  n.setTimeStamp(timeStamp);
              } else if ("javax.management.relation.RelationNotification".equals(className)) {
                  String relationId = readStringInternal(obj.get(N_RELATIONID));
                  String typeName = readStringInternal(obj.get(N_RELATIONTYPENAME));
                  ObjectName objectName = readObjectName(obj.get(N_OBJECTNAME));
                  String roleName = readStringInternal(obj.get(N_ROLENAME));
                  if (roleName == null) {
                      List<ObjectName> mbeansToUnregister = readObjectNameList(obj.get(N_MBEANSTOUNREGISTER));
                      n = new RelationNotification(type, source, sequenceNumber, timeStamp, message, relationId, typeName, objectName, mbeansToUnregister);
                  } else {
                      List<ObjectName> oldValue = readObjectNameList(obj.get(N_OLDROLEVALUE));
                      List<ObjectName> newValue = readObjectNameList(obj.get(N_NEWROLEVALUE));
                      n = new RelationNotification(type, source, sequenceNumber, timeStamp, message, relationId, typeName, objectName, roleName, oldValue, newValue);
                  }
              } else if ("javax.management.timer.TimerNotification".equals(className)) {
                  Integer notificationID = readIntInternal(obj.get(N_NOTIFICATIONID));
                  n = new TimerNotification(type, source, sequenceNumber, timeStamp, message, notificationID);
              } else {
                  Object serialized = readSerialized(obj.get(N_SERIALIZED));
                  if (!(serialized instanceof Notification)) {
                      throwConversionException("readNotifications() expects a Notification.", o);
                  }
                  n = (Notification) serialized;
              }
  
              n.setUserData(userData);
  
              final NotificationRecord nr;
              if (hostName != null) {
                  nr = new NotificationRecord(n, source, hostName, serverName, serverUserDir);
              } else {
                  nr = new NotificationRecord(n, source);
              }
              ret[i] = nr;
          }
          return ret;
      }