content: package com.alibaba.fastjson2;
  
  import com.alibaba.fastjson2.filter.ContextAutoTypeBeforeHandler;
  import com.alibaba.fastjson2.filter.ExtraProcessor;
  import com.alibaba.fastjson2.filter.Filter;
  import com.alibaba.fastjson2.reader.*;
  import com.alibaba.fastjson2.util.*;
  
  import java.io.*;
  import java.lang.invoke.*;
  import java.lang.reflect.Type;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.net.URL;
  import java.nio.ByteBuffer;
  import java.nio.charset.Charset;
  import java.nio.charset.StandardCharsets;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.time.*;
  import java.time.format.DateTimeFormatter;
  import java.util.*;
  import java.util.function.BiFunction;
  import java.util.function.Function;
  import java.util.function.Supplier;
  
  import static com.alibaba.fastjson2.JSONFactory.*;
  import static com.alibaba.fastjson2.JSONReader.BigIntegerCreator.BIG_INTEGER_CREATOR;
  import static com.alibaba.fastjson2.util.JDKUtils.*;
  import static com.alibaba.fastjson2.util.TypeUtils.*;
  
  /**
   * JSONReader is the core class for reading and parsing JSON data in FASTJSON2.
   * It provides methods to read various data types from JSON including primitives,
   * objects, arrays, dates, and custom types.
   *
   * <p>JSONReader supports multiple input sources including strings, byte arrays,
   * streams, and readers. It also supports different character encodings such as
   * UTF-8, UTF-16, and ASCII.</p>
   *
   * <p>Example usage:
   * <pre>
   * String json = "{\"name\":\"John\", \"age\":30}";
   * try (JSONReader reader = JSONReader.of(json)) {
   *     JSONObject obj = reader.readObject();
   *     String name = (String) obj.get("name");
   *     Integer age = (Integer) obj.get("age");
   * }
   * </pre>
   *
   *
   * @since 2.0.0
   */
  public abstract class JSONReader
          implements Closeable {
      static final int MAX_EXP = 2047;
  
      static final byte JSON_TYPE_INT = 1;
      static final byte JSON_TYPE_DEC = 2;
      static final byte JSON_TYPE_STRING = 3;
      static final byte JSON_TYPE_BOOL = 4;
      static final byte JSON_TYPE_NULL = 5;
      static final byte JSON_TYPE_OBJECT = 6;
      static final byte JSON_TYPE_ARRAY = 7;
      static final byte JSON_TYPE_BIG_DEC = 8;
  
      static final byte JSON_TYPE_INT8 = 9;
      static final byte JSON_TYPE_INT16 = 10;
      static final byte JSON_TYPE_INT64 = 11;
      static final byte JSON_TYPE_FLOAT = 12;
      static final byte JSON_TYPE_DOUBLE = 13;
      static final byte JSON_TYPE_NaN = 14;
  
      static final char EOI = 0x1A;
      static final long SPACE = (1L << ' ') | (1L << '\n') | (1L << '\r') | (1L << '\f') | (1L << '\t') | (1L << '\b');
  
      static final boolean[] INT_VALUE_END = new boolean[256];
      static {
          Arrays.fill(INT_VALUE_END, true);
          char[] chars = {'.', 'e', 'E', 't', 'f', 'n', '{', '[', '0', '1', '2', '2', '3', '4', '5', '6', '7', '8', '9'};
          for (char ch : chars) {
              INT_VALUE_END[ch] = false;
          }
      }
  
      protected final Context context;
      public final boolean jsonb;
      public final boolean utf8;
  
      List<ResolveTask> resolveTasks;
  
      protected int offset;
      protected char ch;
      protected boolean comma;
  
      protected boolean nameEscape;
      protected boolean valueEscape;
      protected boolean wasNull;
      protected boolean boolValue;
      protected boolean negative;
  
      protected byte valueType;
      protected short exponent;
      protected short scale;
  
      protected int mag0;
      protected int mag1;
      protected int mag2;
      protected int mag3;
  
      protected int level;
  
      protected String stringValue;
      protected Object complex; // Map | List
  
      protected boolean typeRedirect; // redirect for {"@type":"xxx"",...
  
      protected byte[] doubleChars;
  
      /**
       * Gets the current character being processed by the reader.
       *
       * @return The current character
       */
      /**
       * Gets the current character being processed by the reader.
       *
       * @return The current character
       */
      public final char current() {
          return ch;
      }
  
      /**
       * Checks if the reader has reached the end of the input.
       *
       * @return true if at the end of input, false otherwise
       */
      public boolean isEnd() {
          return ch == EOI;
      }
  
      /**
       * Gets the type of the current JSON value.
       * This method returns a byte value representing the type of the current JSON value
       * being processed by the reader.
       *
       * @return The type of the current JSON value, or -128 if not applicable
       * @since 2.0.51
       */
      public byte getType() {
          return -128;
      }
  
      /**
       * Checks if the current character represents the start of an integer value.
       *
       * @return true if the current character is '-', '+', or a digit, false otherwise
       */
      public boolean isInt() {
          return ch == '-' || ch == '+' || (ch >= '0' && ch <= '9');
      }
  
      /**
       * Checks if the current JSON value is null.
       *
       * @return true if the current value is null, false otherwise
       */
      public abstract boolean isNull();
  
      /**
       * Checks if the reader has encountered a comma.
       *
       * @return true if a comma was encountered, false otherwise
       */
      public final boolean hasComma() {
          return comma;
      }
  
      /**
       * Reads a Date value from JSON data, returning null if the value is null.
       *
       * @return The Date value, or null if the value is null in JSON
       */
      public abstract Date readNullOrNewDate();
  
      /**
       * Checks if the current JSON value is null and advances the reader if it is.
       *
       * @return true if the current value is null, false otherwise
       */
      public abstract boolean nextIfNull();
  
      /**
       * Constructs a new JSONReader with the specified context and configuration.
       *
       * @param context the reading context to use
       * @param jsonb whether to use JSONB binary format
       * @param utf8 whether to use UTF-8 encoding
       * @since 2.0.51
       */
      public JSONReader(Context context, boolean jsonb, boolean utf8) {
          this.context = context;
          this.jsonb = jsonb;
          this.utf8 = utf8;
      }
  
      /**
       * Gets the reading context for this JSONReader.
       *
       * @return The Context object
       */
      public final Context getContext() {
          return context;
      }
  
      /**
       * Throws a JSONException if the specified class is not serializable and the
       * ErrorOnNoneSerializable feature is enabled.
       *
       * @param objectClass The class to check for serializability
       * @throws JSONException if the class is not serializable and the feature is enabled
       */
      public final void errorOnNoneSerializable(Class objectClass) {
          if ((context.features & MASK_ERROR_ON_NONE_SERIALIZABLE) != 0
                  && !Serializable.class.isAssignableFrom(objectClass)) {
              throw new JSONException("not support none-Serializable, class ".concat(objectClass.getName()));
          }
      }
  
      /**
       * Checks if a specific feature is enabled in the reading context.
       *
       * @param feature The feature to check
       * @return true if the feature is enabled, false otherwise
       */
      public final boolean isEnabled(Feature feature) {
          return (context.features & feature.mask) != 0;
      }
  
      /**
       * Gets the locale used for parsing in this JSONReader.
       *
       * @return The Locale object
       */
      public final Locale getLocale() {
          return context.getLocale();
      }
  
      /**
       * Gets the zone ID used for date/time parsing in this JSONReader.
       *
       * @return The ZoneId object
       */
      public final ZoneId getZoneId() {
          return context.getZoneId();
      }
  
      /**
       * Combines the context features with the specified additional features.
       *
       * @param features Additional features to combine with context features
       * @return The combined features bitmask
       */
      public final long features(long features) {
          return context.features | features;
      }
  
      /**
       * Gets the raw integer value from the current position in the JSON data.
       *
       * @return The raw integer value
       */
      public abstract int getRawInt();
  
      /**
       * Gets the raw long value from the current position in the JSON data.
       *
       * @return The raw long value
       */
      public abstract long getRawLong();
  
      /**
       * Checks if the next field name matches a 2-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @return true if the field name matches the 2-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match2();
  
      /**
       * Checks if the next value matches a 2-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @return true if the value matches the 2-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match2() {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 3-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @return true if the field name matches the 3-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match3();
  
      /**
       * Checks if the next value matches a 3-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @return true if the value matches the 3-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match3() {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 4-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param c4 the fourth character to match
       * @return true if the field name matches the 4-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match4(byte c4);
  
      /**
       * Checks if the next value matches a 4-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param c4 the fourth character to match
       * @return true if the value matches the 4-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match4(byte c4) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 5-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the field name matches the 5-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match5(int name1);
  
      /**
       * Checks if the next value matches a 5-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param c4 the fourth character to match
       * @param c5 the fifth character to match
       * @return true if the value matches the 5-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches a 5-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param c4 the fourth character to match
       * @param c5 the fifth character to match
       * @return true if the value matches the 5-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match5(byte c4, byte c5) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 6-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the field name matches the 6-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match6(int name1);
  
      /**
       * Checks if the next value matches a 6-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the value matches the 6-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches a 6-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the value matches the 6-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match6(int name1) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 7-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the field name matches the 7-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match7(int name1);
  
      /**
       * Checks if the next value matches a 7-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the value matches the 7-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches a 7-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @return true if the value matches the 7-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match7(int name1) {
          return false;
      }
  
      /**
       * Checks if the next field name matches an 8-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @param c8 the eighth character to match
       * @return true if the field name matches the 8-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match8(int name1, byte c8);
  
      /**
       * Checks if the next value matches an 8-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @param c8 the eighth character to match
       * @return true if the value matches the 8-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches an 8-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @param c8 the eighth character to match
       * @return true if the value matches the 8-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match8(int name1, byte c8) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 9-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the field name matches the 9-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match9(long name1);
  
      /**
       * Checks if the next value matches a 9-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @param c8 the eighth character to match
       * @param c9 the ninth character to match
       * @return true if the value matches the 9-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches a 9-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 4 bytes of the name to match
       * @param c8 the eighth character to match
       * @param c9 the ninth character to match
       * @return true if the value matches the 9-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match9(int name1, byte c8, byte c9) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 10-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the field name matches the 10-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match10(long name1);
  
      /**
       * Checks if the next value matches a 10-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the value matches the 10-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches a 10-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the value matches the 10-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match10(long name1) {
          return false;
      }
  
      /**
       * Checks if the next field name matches an 11-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the field name matches the 11-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match11(long name1);
  
      /**
       * Checks if the next value matches an 11-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the value matches the 11-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next value matches an 11-character pattern.
       * This method is used for optimized value matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @return true if the value matches the 11-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfValue4Match11(long name1) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 12-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 12-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match12(long name1, byte name2);
  
      /**
       * Checks if the next field name matches a 13-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 13-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match13(long name1, int name2);
  
      /**
       * Checks if the next field name matches a 14-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 14-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next field name matches a 14-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 14-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName4Match14(long name1, int name2) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 15-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 15-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next field name matches a 15-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 4 bytes of the name to match
       * @return true if the field name matches the 15-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName4Match15(long name1, int name2) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 16-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 4 bytes of the name to match
       * @param name3 the last byte of the name to match
       * @return true if the field name matches the 16-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match16(long name1, int name2, byte name3);
  
      /**
       * Checks if the next field name matches a 17-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 8 bytes of the name to match
       * @return true if the field name matches the 17-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match17(long name1, long name2);
  
      /**
       * Checks if the next field name matches an 18-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 8 bytes of the name to match
       * @return true if the field name matches the 18-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match18(long name1, long name2);
  
      /**
       * Checks if the next field name matches a 19-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 8 bytes of the name to match
       * @return true if the field name matches the 19-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next field name matches a 19-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the last 8 bytes of the name to match
       * @return true if the field name matches the 19-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName4Match19(long name1, long name2) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 20-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last byte of the name to match
       * @return true if the field name matches the 20-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match20(long name1, long name2, byte name3);
  
      /**
       * Checks if the next field name matches a 21-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 4 bytes of the name to match
       * @return true if the field name matches the 21-character pattern, false otherwise
       * @since 2.0.51
       */
      /**
       * Checks if the next field name matches a 21-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 4 bytes of the name to match
       * @return true if the field name matches the 21-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName4Match21(long name1, long name2, int name3) {
          return false;
      }
  
      /**
       * Checks if the next field name matches a 22-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 4 bytes of the name to match
       * @return true if the field name matches the 22-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match22(long name1, long name2, int name3);
  
      /**
       * Checks if the next field name matches a 23-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 4 bytes of the name to match
       * @return true if the field name matches the 23-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match23(long name1, long name2, int name3);
  
      /**
       * Checks if the next field name matches a 24-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 4 bytes of the name to match
       * @param name4 the last byte of the name to match
       * @return true if the field name matches the 24-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match24(long name1, long name2, int name3, byte name4);
  
      /**
       * Checks if the next field name matches a 25-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 8 bytes of the name to match
       * @return true if the field name matches the 25-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match25(long name1, long name2, long name3);
  
      /**
       * Checks if the next field name matches a 26-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 8 bytes of the name to match
       * @return true if the field name matches the 26-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match26(long name1, long name2, long name3);
  
      /**
       * Checks if the next field name matches a 27-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the last 8 bytes of the name to match
       * @return true if the field name matches the 27-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match27(long name1, long name2, long name3);
  
      /**
       * Checks if the next field name matches a 28-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 8 bytes of the name to match
       * @param c28 the last byte of the name to match
       * @return true if the field name matches the 28-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match28(long name1, long name2, long name3, byte c28);
  
      /**
       * Checks if the next field name matches a 29-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 8 bytes of the name to match
       * @param name4 the last 4 bytes of the name to match
       * @return true if the field name matches the 29-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match29(long name1, long name2, long name3, int name4);
  
      /**
       * Checks if the next field name matches a 30-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 8 bytes of the name to match
       * @param name4 the last 4 bytes of the name to match
       * @return true if the field name matches the 30-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match30(long name1, long name2, long name3, int name4);
  
      /**
       * Checks if the next field name matches a 31-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 8 bytes of the name to match
       * @param name4 the last 4 bytes of the name to match
       * @return true if the field name matches the 31-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match31(long name1, long name2, long name3, int name4);
  
      /**
       * Checks if the next field name matches a 32-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second to last 8 bytes of the name to match
       * @param name4 the second to last 4 bytes of the name to match
       * @param c32 the last byte of the name to match
       * @return true if the field name matches the 32-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match32(long name1, long name2, long name3, int name4, byte c32);
  
      /**
       * Checks if the next field name matches a 33-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the last 8 bytes of the name to match
       * @return true if the field name matches the 33-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match33(long name1, long name2, long name3, long name4);
  
      /**
       * Checks if the next field name matches a 34-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the last 8 bytes of the name to match
       * @return true if the field name matches the 34-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match34(long name1, long name2, long name3, long name4);
  
      /**
       * Checks if the next field name matches a 35-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the last 8 bytes of the name to match
       * @return true if the field name matches the 35-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match35(long name1, long name2, long name3, long name4);
  
      /**
       * Checks if the next field name matches a 36-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the second to last 8 bytes of the name to match
       * @param c35 the last byte of the name to match
       * @return true if the field name matches the 36-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match36(long name1, long name2, long name3, long name4, byte c35);
  
      /**
       * Checks if the next field name matches a 37-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third to last 8 bytes of the name to match
       * @param name5 the last 4 bytes of the name to match
       * @return true if the field name matches the 37-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match37(long name1, long name2, long name3, long name4, int name5);
  
      /**
       * Checks if the next field name matches a 38-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third to last 8 bytes of the name to match
       * @param name5 the last 4 bytes of the name to match
       * @return true if the field name matches the 38-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match38(long name1, long name2, long name3, long name4, int name5);
  
      /**
       * Checks if the next field name matches a 39-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third to last 8 bytes of the name to match
       * @param name5 the last 4 bytes of the name to match
       * @return true if the field name matches the 39-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match39(long name1, long name2, long name3, long name4, int name5);
  
      /**
       * Checks if the next field name matches a 40-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third to last 8 bytes of the name to match
       * @param name5 the second to last 4 bytes of the name to match
       * @param c40 the last byte of the name to match
       * @return true if the field name matches the 40-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match40(long name1, long name2, long name3, long name4, int name5, byte c40);
  
      /**
       * Checks if the next field name matches a 41-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third middle 8 bytes of the name to match
       * @param name5 the last 8 bytes of the name to match
       * @return true if the field name matches the 41-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match41(long name1, long name2, long name3, long name4, long name5);
  
      /**
       * Checks if the next field name matches a 42-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third middle 8 bytes of the name to match
       * @param name5 the last 8 bytes of the name to match
       * @return true if the field name matches the 42-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match42(long name1, long name2, long name3, long name4, long name5);
  
      /**
       * Checks if the next field name matches a 43-character pattern.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @param name1 the first 8 bytes of the name to match
       * @param name2 the middle 8 bytes of the name to match
       * @param name3 the second middle 8 bytes of the name to match
       * @param name4 the third middle 8 bytes of the name to match
       * @param name5 the last 8 bytes of the name to match
       * @return true if the field name matches the 43-character pattern, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfName4Match43(long name1, long name2, long name3, long name4, long name5);
  
      /**
       * Checks if the next field name matches an 8-character pattern with no additional characters.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @return true if the field name matches the 8-character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName8Match0() {
          return false;
      }
  
      /**
       * Checks if the next field name matches an 8-character pattern with 1 additional character.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @return true if the field name matches the 8+1 character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName8Match1() {
          return false;
      }
  
      /**
       * Checks if the next field name matches an 8-character pattern with 2 additional characters.
       * This method is used for optimized field name matching in JSONB format.
       *
       * @return true if the field name matches the 8+2 character pattern, false otherwise
       * @since 2.0.51
       */
      public boolean nextIfName8Match2() {
          return false;
      }
  
      /**
       * Handles resolve tasks for circular references in the JSON data.
       * This method processes all pending reference resolution tasks after the main
       * parsing is complete, resolving circular references and updating the object graph.
       *
       * @param root The root object of the parsed JSON structure
       */
      public final void handleResolveTasks(Object root) {
          if (resolveTasks == null) {
              return;
          }
  
          Object previous = null;
          for (ResolveTask resolveTask : resolveTasks) {
              JSONPath path = resolveTask.reference;
              FieldReader fieldReader = resolveTask.fieldReader;
  
              Object fieldValue;
              if (path.isPrevious()) {
                  fieldValue = previous;
              } else {
                  if (!path.isRef()) {
                      throw new JSONException("reference path invalid : " + path);
                  }
                  path.setReaderContext(context);
                  if ((context.features & Feature.FieldBased.mask) != 0) {
                      JSONWriter.Context writeContext = JSONFactory.createWriteContext();
                      writeContext.features |= JSONWriter.Feature.FieldBased.mask;
                      path.setWriterContext(writeContext);
                  }
  
                  fieldValue = path.eval(root);
                  previous = fieldValue;
              }
  
              Object resolvedName = resolveTask.name;
              Object resolvedObject = resolveTask.object;
  
              if (resolvedName != null) {
                  if (resolvedObject instanceof Map) {
                      Map map = (Map) resolvedObject;
                      if (resolvedName instanceof ReferenceKey) {
                          if (map instanceof LinkedHashMap) {
                              int size = map.size();
                              if (size == 0) {
                                  continue;
                              }
  
                              Object[] keys = new Object[size];
                              Object[] values = new Object[size];
  
                              int index = 0;
                              for (Object o : map.entrySet()) {
                                  Map.Entry entry = (Map.Entry) o;
                                  Object entryKey = entry.getKey();
                                  if (resolvedName == entryKey) {
                                      keys[index] = fieldValue;
                                  } else {
                                      keys[index] = entryKey;
                                  }
                                  values[index++] = entry.getValue();
                              }
                              map.clear();
  
                              for (int j = 0; j < keys.length; j++) {
                                  map.put(keys[j], values[j]);
                              }
                          } else {
                              map.put(fieldValue, map.remove(resolvedName));
                          }
                      } else {
                          map.put(resolvedName, fieldValue);
                      }
                      continue;
                  }
  
                  if (resolvedName instanceof Integer) {
                      if (resolvedObject instanceof List) {
                          int index = (Integer) resolvedName;
                          List list = (List) resolvedObject;
                          if (index == list.size()) {
                              list.add(fieldValue);
                          } else {
                              if (index < list.size() && list.get(index) == null) {
                                  list.set(index, fieldValue);
                              } else {
                                  list.add(index, fieldValue);
                              }
                          }
                          continue;
                      }
  
                      if (resolvedObject instanceof Object[]) {
                          int index = (Integer) resolvedName;
                          Object[] array = (Object[]) resolvedObject;
                          array[index] = fieldValue;
                          continue;
                      }
  
                      if (resolvedObject instanceof Collection) {
                          Collection collection = (Collection) resolvedObject;
                          collection.add(fieldValue);
                          continue;
                      }
                  }
              }
  
              fieldReader.accept(resolvedObject, fieldValue);
          }
      }
  
      /**
       * Gets an ObjectReader for the specified type from the context's provider.
       *
       * @param type The type for which to get an ObjectReader
       * @return An ObjectReader for the specified type
       */
      public final ObjectReader getObjectReader(Type type) {
          return context.provider.getObjectReader(type, (context.features & MASK_FIELD_BASED) != 0);
      }
  
      /**
       * Checks if the SmartMatch feature is enabled in the context.
       *
       * @return true if SmartMatch is enabled, false otherwise
       */
      public final boolean isSupportSmartMatch() {
          return (context.features & MASK_SUPPORT_SMART_MATCH) != 0;
      }
  
      /**
       * Checks if the InitStringFieldAsEmpty feature is enabled in the context.
       *
       * @return true if InitStringFieldAsEmpty is enabled, false otherwise
       */
      public final boolean isInitStringFieldAsEmpty() {
          return (context.features & MASK_INIT_STRING_FIELD_AS_EMPTY) != 0;
      }
  
      /**
       * Checks if the SmartMatch feature is enabled, considering additional features.
       *
       * @param features Additional features to consider
       * @return true if SmartMatch is enabled with the given features, false otherwise
       */
      public final boolean isSupportSmartMatch(long features) {
          return ((context.features | features) & MASK_SUPPORT_SMART_MATCH) != 0;
      }
  
      /**
       * Checks if the SupportArrayToBean feature is enabled in the context.
       *
       * @return true if SupportArrayToBean is enabled, false otherwise
       */
      public final boolean isSupportBeanArray() {
          return (context.features & MASK_SUPPORT_ARRAY_TO_BEAN) != 0;
      }
  
      /**
       * Checks if the SupportArrayToBean feature is enabled, considering additional features.
       *
       * @param features Additional features to consider
       * @return true if SupportArrayToBean is enabled with the given features, false otherwise
       */
      public final boolean isSupportBeanArray(long features) {
          return ((context.features | features) & MASK_SUPPORT_ARRAY_TO_BEAN) != 0;
      }
  
      /**
       * Checks if the SupportAutoType feature is enabled, considering additional features.
       *
       * @param features Additional features to consider
       * @return true if SupportAutoType is enabled with the given features, false otherwise
       */
      public final boolean isSupportAutoType(long features) {
          return ((context.features | features) & MASK_SUPPORT_AUTO_TYPE) != 0;
      }
  
      /**
       * Checks if the SupportAutoType feature or handler is enabled, considering additional features.
       *
       * @param features Additional features to consider
       * @return true if SupportAutoType or a handler is enabled with the given features, false otherwise
       */
      public final boolean isSupportAutoTypeOrHandler(long features) {
          return ((context.features | features) & MASK_SUPPORT_AUTO_TYPE) != 0 || context.autoTypeBeforeHandler != null;
      }
  
      /**
       * Checks if this reader is using JSONB binary format.
       *
       * @return true if using JSONB format, false otherwise
       */
      public final boolean isJSONB() {
          return jsonb;
      }
  
      /**
       * Checks if the IgnoreNoneSerializable feature is enabled in the context.
       *
       * @return true if IgnoreNoneSerializable is enabled, false otherwise
       */
      public final boolean isIgnoreNoneSerializable() {
          return (context.features & MASK_IGNORE_NONE_SERIALIZABLE) != 0;
      }
  
      /**
       * Checks if there is an auto-type before handler configured in the context.
       *
       * @return true if there is an auto-type before handler, false otherwise
       */
      public boolean hasAutoTypeBeforeHandler() {
          return context.autoTypeBeforeHandler != null;
      }
  
      /**
       * Checks the auto type for the specified class and hash, considering additional features.
       *
       * @param expectClass The expected class
       * @param expectClassHash The expected class hash
       * @param features Additional features to consider
       * @return An ObjectReader for the auto-detected type, or null if not found
       */
      public ObjectReader checkAutoType(Class expectClass, long expectClassHash, long features) {
          return null;
      }
  
      final char char1(int c) {
          switch (c) {
              case '0':
                  return '\0';
              case '1':
                  return '\1';
              case '2':
                  return '\2';
              case '3':
                  return '\3';
              case '4':
                  return '\4';
              case '5':
                  return '\5';
              case '6':
                  return '\6';
              case '7':
                  return '\7';
              case 'b': // 8
                  return '\b';
              case 't': // 9
                  return '\t';
              case 'n': // 10
                  return '\n';
              case 'v': // 11
                  return '\u000B';
              case 'f': // 12
              case 'F':
                  return '\f';
              case 'r': // 13
                  return '\r';
              case '"': // 34
              case '\'': // 39
              case '/': // 47
              case '.': // 47
              case '\\': // 92
              case '#':
              case '&':
              case '[':
              case ']':
              case '@':
              case '(':
              case ')':
              case '_':
              case ',':
              case '~':
              case ' ':
                  return (char) c;
              default:
                  throw new JSONException(info("unclosed.str '\\" + (char) c));
          }
      }
  
      static char char2(int c1, int c2) {
          return (char) (DIGITS2[c1] * 0x10
                  + DIGITS2[c2]);
      }
  
      /**
       * Checks if the current character is the start of a JSON object ('{') and advances the reader if it is.
       *
       * @return true if the current character is '{', false otherwise
       */
      public abstract boolean nextIfObjectStart();
  
      /**
       * Checks if the current value is a null or empty string and advances the reader if it is.
       *
       * @return true if the current value is null or an empty string, false otherwise
       */
      public abstract boolean nextIfNullOrEmptyString();
  
      /**
       * Checks if the current character is the end of a JSON object ('}') and advances the reader if it is.
       *
       * @return true if the current character is '}', false otherwise
       */
      public abstract boolean nextIfObjectEnd();
  
      /**
       * Starts reading a JSON array, advancing the reader to the first element.
       *
       * @return The maximum integer value as a placeholder
       * @throws JSONException if the current character is not the start of an array ('[')
       */
      public int startArray() {
          if (!nextIfArrayStart()) {
              throw new JSONException(info("illegal input, expect '[', but " + ch));
          }
          return Integer.MAX_VALUE;
      }
  
      /**
       * Checks if the current value represents a reference.
       *
       * @return true if the current value represents a reference, false otherwise
       */
      public abstract boolean isReference();
  
      /**
       * Reads a reference value from JSON data.
       *
       * @return The reference value as a string
       */
      public abstract String readReference();
  
      /**
       * Reads a reference value from JSON data and adds it to the specified list at the given index.
       *
       * @param list The list to which the reference should be added
       * @param i The index at which to add the reference in the list
       * @return true if a reference was read and added, false otherwise
       */
      public final boolean readReference(List list, int i) {
          if (!isReference()) {
              return false;
          }
          return readReference0(list, i);
      }
  
      /**
       * Reads a reference value from JSON data and adds it to the specified collection at the given index.
       *
       * @param list The collection to which the reference should be added
       * @param i The index at which to add the reference in the collection
       * @return true if a reference was read and added, false otherwise
       */
      public boolean readReference(Collection list, int i) {
          if (!isReference()) {
              return false;
          }
          return readReference0(list, i);
      }
  
      private boolean readReference0(Collection list, int i) {
          String path = readReference();
          if ("..".equals(path)) {
              list.add(list);
          } else {
              addResolveTask(list, i, JSONPath.of(path));
          }
          return true;
      }
  
      public final void addResolveTask(FieldReader fieldReader, Object object, JSONPath path) {
          if (resolveTasks == null) {
              resolveTasks = new ArrayList<>();
          }
          resolveTasks.add(new ResolveTask(fieldReader, object, fieldReader.fieldName, path));
      }
  
      public final void addResolveTask(Map object, Object key, JSONPath reference) {
          if (resolveTasks == null) {
              resolveTasks = new ArrayList<>();
          }
          if (object instanceof LinkedHashMap) {
              object.put(key, null);
          }
          resolveTasks.add(new ResolveTask(null, object, key, reference));
      }
  
      public final void addResolveTask(Collection object, int i, JSONPath reference) {
          if (resolveTasks == null) {
              resolveTasks = new ArrayList<>();
          }
          resolveTasks.add(new ResolveTask(null, object, i, reference));
      }
  
      public final void addResolveTask(Object[] object, int i, JSONPath reference) {
          if (resolveTasks == null) {
              resolveTasks = new ArrayList<>();
          }
          resolveTasks.add(new ResolveTask(null, object, i, reference));
      }
  
      /**
       * Checks if the current character represents the start of a JSON array.
       *
       * @return true if the current character is '[', false otherwise
       */
      /**
       * Checks if the current character represents the start of a JSON array.
       *
       * @return true if the current character is '[', false otherwise
       */
      public boolean isArray() {
          return this.ch == '[';
      }
  
      /**
       * Checks if the current character represents the start of a JSON object.
       *
       * @return true if the current character is '{', false otherwise
       */
      public boolean isObject() {
          return this.ch == '{';
      }
  
      /**
       * Checks if the current character represents the start of a JSON number.
       *
       * @return true if the current character is a digit or sign, false otherwise
       */
      public boolean isNumber() {
          switch (this.ch) {
              case '-':
              case '+':
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                  return true;
              default:
                  return false;
          }
      }
  
      /**
       * Checks if the current character represents the start of a JSON string.
       *
       * @return true if the current character is a quote, false otherwise
       */
      public boolean isString() {
          return this.ch == '"' || this.ch == '\'';
      }
  
      /**
       * Advances the reader to the end of the current JSON array.
       */
      public void endArray() {
          next();
      }
  
      /**
       * Checks if the current character matches the specified character and advances the reader if it does.
       *
       * @param ch The character to match
       * @return true if the current character matches the specified character, false otherwise
       */
      public abstract boolean nextIfMatch(char ch);
  
      /**
       * Checks if the current character is a comma (',') and advances the reader if it is.
       *
       * @return true if the current character is a comma, false otherwise
       */
      public abstract boolean nextIfComma();
  
      /**
       * Checks if the current character is the start of a JSON array ('[') and advances the reader if it is.
       *
       * @return true if the current character is '[', false otherwise
       */
      public abstract boolean nextIfArrayStart();
  
      /**
       * Checks if the current character is the end of a JSON array (']') and advances the reader if it is.
       *
       * @return true if the current character is ']', false otherwise
       */
      public abstract boolean nextIfArrayEnd();
  
      /**
       * Checks if the current value represents a Set and advances the reader if it is.
       * This method is used to detect Set-type collections during JSON parsing.
       *
       * @return true if the current value represents a Set, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfSet();
  
      /**
       * Checks if the current value represents infinity and advances the reader if it is.
       * This method is used to detect infinity values during JSON parsing.
       *
       * @return true if the current value represents infinity, false otherwise
       * @since 2.0.51
       */
      public abstract boolean nextIfInfinity();
  
      /**
       * Reads a pattern string from the JSON data.
       * This method is used to read regular expression patterns or other pattern strings.
       *
       * @return The pattern string read from the JSON data
       * @since 2.0.51
       */
      public abstract String readPattern();
  
      public final int getOffset() {
          return offset;
      }
  
      /**
       * Advances the reader to the next character in the JSON data.
       */
      public abstract void next();
  
      /**
       * Advances the reader to the next character in the JSON data, skipping comment processing.
       */
      public void nextWithoutComment() {
          next();
      }
  
      /**
       * Reads the hash code of the current JSON value.
       *
       * @return The hash code of the current value
       */
      public abstract long readValueHashCode();
  
      /**
       * Reads the hash code of the current type in the JSON data.
       *
       * @return The hash code of the current type
       */
      public long readTypeHashCode() {
          return readValueHashCode();
      }
  
      /**
       * Reads the hash code of the current field name in a JSON object.
       *
       * @return The hash code of the current field name
       */
      public abstract long readFieldNameHashCode();
  
      /**
       * Reads the hash code of the current field name in lowercase.
       *
       * @return The hash code of the current field name in lowercase
       */
      public abstract long getNameHashCodeLCase();
  
      /**
       * Reads the current field name in a JSON object.
       *
       * @return The current field name
       */
      public abstract String readFieldName();
  
      /**
       * Gets the current field name in a JSON object.
       *
       * @return The current field name
       */
      public abstract String getFieldName();
  
      /**
       * Sets the type redirect flag for this reader.
       *
       * @param typeRedirect true to enable type redirection, false to disable
       */
      public final void setTypeRedirect(boolean typeRedirect) {
          this.typeRedirect = typeRedirect;
      }
  
      /**
       * Checks if type redirection is enabled for this reader.
       *
       * @return true if type redirection is enabled, false otherwise
       */
      public final boolean isTypeRedirect() {
          return typeRedirect;
      }
  
      /**
       * Reads the hash code of the current field name in a JSON object without requiring quotes.
       *
       * @return The hash code of the current field name
       */
      public abstract long readFieldNameHashCodeUnquote();
  
      /**
       * Reads the current field name in a JSON object without requiring quotes.
       *
       * @return The current field name
       * @throws JSONException if the field name is null or empty
       */
      public final String readFieldNameUnquote() {
          if (ch == '/') {
              skipComment();
          }
          readFieldNameHashCodeUnquote();
          String name = getFieldName();
          if (name == null || name.isEmpty()) {
              throw new JSONException(info("illegal input"));
          }
          return name;
      }
  
      /**
       * Skips the current field name in a JSON object.
       *
       * @return true if the field name was successfully skipped, false otherwise
       */
      public abstract boolean skipName();
  
      /**
       * Skips the current JSON value, advancing the reader to the next value or end of the current structure.
       */
      public abstract void skipValue();
  
      /**
       * Checks if the current value represents binary data.
       *
       * @return true if the current value is binary data, false otherwise
       */
      public boolean isBinary() {
          return false;
      }
  
      /**
       * Reads a hexadecimal string from JSON data and converts it to a byte array.
       *
       * @return The byte array representation of the hexadecimal string
       */
      public abstract byte[] readHex();
  
      /**
       * Reads binary data from JSON data.
       *
       * @return The byte array representation of the binary data
       * @throws JSONException if there is an error parsing the binary data
       */
      public byte[] readBinary() {
          if (ch == 'x') {
              return readHex();
          }
  
          if (isString()) {
              String str = readString();
              if (str.isEmpty()) {
                  return null;
              }
  
              if ((context.features & Feature.Base64StringAsByteArray.mask) != 0) {
                  return Base64.getDecoder().decode(str);
              }
  
              throw new JSONException(info("not support input " + str));
          }
  
          if (nextIfArrayStart()) {
              int index = 0;
              byte[] bytes = new byte[64];
              while (true) {
                  if (ch == ']') {
                      next();
                      break;
                  }
                  if (index == bytes.length) {
                      int oldCapacity = bytes.length;
                      int newCapacity = oldCapacity + (oldCapacity >> 1);
                      bytes = Arrays.copyOf(bytes, newCapacity);
                  }
                  bytes[index++] = (byte) readInt32Value();
              }
              nextIfComma();
              return Arrays.copyOf(bytes, index);
          }
  
          throw new JSONException(info("not support read binary"));
      }
  
      /**
       * Reads a 32-bit integer value from JSON data.
       *
       * @return The 32-bit integer value
       */
      public abstract int readInt32Value();
  
      /**
       * Reads an array of 32-bit integer values from JSON data.
       *
       * @return The array of 32-bit integer values
       * @throws JSONException if there is an error parsing the JSON
       */
      public int[] readInt32ValueArray() {
          if (nextIfNull()) {
              return null;
          }
  
          if (nextIfArrayStart()) {
              int[] values = new int[8];
              int size = 0;
              while (!nextIfArrayEnd()) {
                  if (isEnd()) {
                      throw new JSONException(info("input end"));
                  }
  
                  if (size == values.length) {
                      values = Arrays.copyOf(values, values.length << 1);
                  }
  
                  values[size++] = readInt32Value();
              }
              nextIfComma();
  
              int[] array;
              if (size == values.length) {
                  array = values;
              } else {
                  array = Arrays.copyOf(values, size);
              }
              return array;
          }
  
          if (isString()) {
              String str = readString();
              if (str.isEmpty()) {
                  return null;
              }
  
              throw new JSONException(info("not support input " + str));
          }
  
          throw new JSONException(info("TODO"));
      }
  
      /**
       * Checks if the next value matches the specified type and advances the reader if it does.
       *
       * @param type The type to match
       * @return true if the next value matches the specified type, false otherwise
       * @throws JSONException as this operation is not supported
       */
      public boolean nextIfMatch(byte type) {
          throw new JSONException("UnsupportedOperation");
      }
  
      /**
       * Checks if the next value matches any type and advances the reader if it does.
       *
       * @return true if the next value matches any type, false otherwise
       * @throws JSONException as this operation is not supported
       */
      public boolean nextIfMatchTypedAny() {
          throw new JSONException("UnsupportedOperation");
      }
  
      public abstract boolean nextIfMatchIdent(char c0, char c1);
  
      public abstract boolean nextIfMatchIdent(char c0, char c1, char c2);
  
      public abstract boolean nextIfMatchIdent(char c0, char c1, char c2, char c3);
  
      public abstract boolean nextIfMatchIdent(char c0, char c1, char c2, char c3, char c4);
  
      public abstract boolean nextIfMatchIdent(char c0, char c1, char c2, char c3, char c4, char c5);
  
      /**
       * Reads a Byte object from JSON data.
       *
       * @return The Byte object, or null if the value is null in JSON
       */
      public final Byte readInt8() {
          Integer i = readInt32();
          if (i == null) {
              return null;
          }
          return i.byteValue();
      }
  
      /**
       * Reads a byte value from JSON data.
       *
       * @return The byte value
       */
      public byte readInt8Value() {
          int i = readInt32Value();
          return (byte) i;
      }
  
      /**
       * Reads a Short object from JSON data.
       *
       * @return The Short object, or null if the value is null in JSON
       */
      public final Short readInt16() {
          Integer i = readInt32();
          if (i == null) {
              return null;
          }
          return i.shortValue();
      }
  
      /**
       * Reads a short value from JSON data.
       *
       * @return The short value
       */
      public short readInt16Value() {
          int i = readInt32Value();
          return (short) i;
      }
  
      /**
       * Reads an Integer object from JSON data.
       *
       * @return The Integer object, or null if the value is null in JSON
       */
      public abstract Integer readInt32();
  
      /**
       * Reads a 32-bit integer value from JSON data, handling overflow conditions.
       *
       * @return The 32-bit integer value
       */
      protected final int readInt32ValueOverflow() {
          readNumber0();
          return getInt32Value();
      }
  
      /**
       * Reads a 64-bit long value from JSON data, handling overflow conditions.
       *
       * @return The 64-bit long value
       */
      protected final long readInt64ValueOverflow() {
          readNumber0();
          return getInt64Value();
      }
  
      public final int getInt32Value() {
          switch (valueType) {
              case JSON_TYPE_INT8:
              case JSON_TYPE_INT16:
              case JSON_TYPE_INT:
                  if (mag1 == 0 && mag2 == 0) {
                      if (negative) {
                          if (mag3 == Integer.MIN_VALUE) {
                              return mag3;
                          }
                          if (mag3 >= 0) {
                              return -mag3;
                          }
                      } else {
                          if (mag3 >= 0) {
                              return mag3;
                          }
                      }
                  }
                  Number number = getNumber();
                  if (number instanceof Long) {
                      long longValue = number.longValue();
                      if (longValue < Integer.MIN_VALUE || longValue > Integer.MAX_VALUE) {
                          throw new JSONException(info("integer overflow " + longValue));
                      }
                      return (int) longValue;
                  }
                  if (number instanceof BigInteger) {
                      BigInteger bigInt = (BigInteger) number;
                      if ((context.features & Feature.NonErrorOnNumberOverflow.mask) != 0) {
                          return bigInt.intValue();
                      }
                      try {
                          return bigInt.intValueExact();
                      } catch (ArithmeticException e) {
                          throw numberError();
                      }
                  }
                  return number.intValue();
              case JSON_TYPE_DEC:
                  return getNumber().intValue();
              case JSON_TYPE_BOOL:
                  return boolValue ? 1 : 0;
              case JSON_TYPE_NULL:
                  if ((context.features & Feature.ErrorOnNullForPrimitives.mask) != 0) {
                      throw new JSONException(info("int value not support input null"));
                  }
                  return 0;
              case JSON_TYPE_STRING: {
                  return toInt32(stringValue);
              }
              case JSON_TYPE_OBJECT: {
                  Number num = toNumber((Map) complex);
                  if (num != null) {
                      return num.intValue();
                  }
                  return 0;
              }
              case JSON_TYPE_INT64:
              case JSON_TYPE_FLOAT:
              case JSON_TYPE_DOUBLE: {
                  Number num = getNumber();
                  long int64 = num.longValue();
                  if ((int64 < Integer.MIN_VALUE || int64 > Integer.MAX_VALUE)
                          && (context.features & Feature.NonErrorOnNumberOverflow.mask) == 0
                  ) {
                      throw new JSONException(info("integer overflow " + int64));
                  }
                  return (int) int64;
                  //if ((context.features & Feature.NonErrorOnNumberOverflow.mask) != 0) {
              }
              case JSON_TYPE_BIG_DEC:
                  try {
                      return getBigDecimal()
                              .intValueExact();
                  } catch (ArithmeticException e) {
                      throw numberError();
                  }
              case JSON_TYPE_ARRAY: {
                  return toInt((List) complex);
              }
              default:
                  throw new JSONException("TODO : " + valueType);
          }
      }
  
      public final long getInt64Value() {
          switch (valueType) {
              case JSON_TYPE_INT8:
              case JSON_TYPE_INT16:
              case JSON_TYPE_INT:
                  if (mag1 == 0 && mag2 == 0) {
                      if (negative) {
                          if (mag3 == Integer.MIN_VALUE) {
                              return mag3;
                          }
                          if (mag3 >= 0) {
                              return -mag3;
                          }
                      } else {
                          if (mag3 >= 0) {
                              return mag3;
                          }
                      }
                  }
                  Number number = getNumber();
                  if (number instanceof BigInteger) {
                      BigInteger bigInt = (BigInteger) number;
                      if ((context.features & Feature.NonErrorOnNumberOverflow.mask) != 0) {
                          return bigInt.longValue();
                      }
                      try {
                          return bigInt.longValueExact();
                      } catch (ArithmeticException e) {
                          throw numberError();
                      }
                  }
                  return number.longValue();
              case JSON_TYPE_DEC:
              case JSON_TYPE_INT64:
              case JSON_TYPE_FLOAT:
              case JSON_TYPE_DOUBLE:
                  return getNumber().longValue();
              case JSON_TYPE_BOOL:
                  return boolValue ? 1 : 0;
              case JSON_TYPE_NULL:
                  if ((context.features & Feature.ErrorOnNullForPrimitives.mask) != 0) {
                      throw new JSONException(info("long value not support input null"));
                  }
                  return 0;
              case JSON_TYPE_STRING: {
                  return toInt64(stringValue);
              }
              case JSON_TYPE_OBJECT: {
                  return toLong((Map) complex);
              }
              case JSON_TYPE_ARRAY: {
                  return toInt((List) complex);
              }
              case JSON_TYPE_BIG_DEC:
                  try {
                      return getBigDecimal()
                              .longValueExact();
                  } catch (ArithmeticException e) {
                      throw numberError();
                  }
              default:
                  throw new JSONException("TODO : " + valueType);
          }
      }
  
      public final double getDoubleValue() {
          switch (valueType) {
              case JSON_TYPE_NaN:
                  return Double.NaN;
              case JSON_TYPE_INT8:
              case JSON_TYPE_INT16:
              case JSON_TYPE_INT:
                  if (mag1 == 0 && mag2 == 0 && mag3 != Integer.MIN_VALUE) {
                      return negative ? -mag3 : mag3;
                  }
                  Number number = getNumber();
                  if (number instanceof BigInteger) {
                      BigInteger bigInt = (BigInteger) number;
                      if ((context.features & Feature.NonErrorOnNumberOverflow.mask) != 0) {
                          return bigInt.longValue();
                      }
                      try {
                          return bigInt.longValueExact();
                      } catch (ArithmeticException e) {
                          throw numberError();
                      }
                  }
                  return number.doubleValue();
              case JSON_TYPE_DEC:
              case JSON_TYPE_INT64:
              case JSON_TYPE_FLOAT:
              case JSON_TYPE_DOUBLE:
                  return getNumber().doubleValue();
              case JSON_TYPE_BOOL:
                  return boolValue ? 1 : 0;
              case JSON_TYPE_NULL:
                  if ((context.features & Feature.ErrorOnNullForPrimitives.mask) != 0) {
                      throw new JSONException(info("long value not support input null"));
                  }
                  return 0;
              case JSON_TYPE_STRING: {
                  try {
                      return toDoubleValue(stringValue);
                  } catch (NumberFormatException e) {
                      throw new JSONException(info(e.getMessage()));
                  }
              }
              case JSON_TYPE_OBJECT: {
                  Map map = (Map) complex;
                  if (map == null || map.isEmpty()) {
                      wasNull = true;
                      return 0;
                  }
                  return toDoubleValue(map);
              }
              case JSON_TYPE_ARRAY: {
                  Collection list = (Collection) complex;
                  if (list == null || list.isEmpty()) {
                      wasNull = true;
                      return 0;
                  }
                  return toDoubleValue(complex);
              }
              case JSON_TYPE_BIG_DEC:
                  try {
                      return getBigDecimal()
                              .doubleValue();
                  } catch (ArithmeticException e) {
                      throw numberError();
                  }
              default:
                  throw new JSONException("TODO : " + valueType);
          }
      }
  
      public final float getFloatValue() {
          return (float) getDoubleValue();
      }
  
      /**
       * Reads an array of 64-bit long values from JSON data.
       *
       * @return The array of 64-bit long values
       * @throws JSONException if there is an error parsing the JSON
       */
      public long[] readInt64ValueArray() {
          if (nextIfNull()) {
              return null;
          }
  
          if (nextIfArrayStart()) {
              long[] values = new long[8];
              int size = 0;
              while (!nextIfArrayEnd()) {
                  if (isEnd()) {
                      throw new JSONException(info("input end"));
                  }
  
                  if (size == values.length) {
                      values = Arrays.copyOf(values, values.length << 1);
                  }
  
                  values[size++] = readInt64Value();
              }
              return size == values.length ? values : Arrays.copyOf(values, size);
          }
  
          if (isString()) {
              String str = readString();
              if (str.isEmpty()) {
                  return null;
              }
  
              throw error("not support input ".concat(str));
          }
  
          throw new JSONException(info("TODO"));
      }
  
      /**
       * Reads a 64-bit long value from JSON data.
       *
       * @return The 64-bit long value
       */
      public abstract long readInt64Value();
  
      /**
       * Reads a Long object from JSON data.
       *
       * @return The Long object, or null if the value is null in JSON
       */
      public abstract Long readInt64();
  
      /**
       * Reads a float value from JSON data.
       *
       * @return The float value
       */
      public abstract float readFloatValue();
  
      /**
       * Reads a Float object from JSON data.
       *
       * @return The Float object, or null if the value is null in JSON
       */
      public Float readFloat() {
          if (nextIfNull()) {
              return null;
          }
  
          wasNull = false;
          float value = readFloatValue();
          if (wasNull) {
              return null;
          }
          return value;
      }
  
      /**
       * Reads a double value from JSON data.
       *
       * @return The double value
       */
      public abstract double readDoubleValue();
  
      /**
       * Reads a Double object from JSON data.
       *
       * @return The Double object, or null if the value is null in JSON
       */
      public final Double readDouble() {
          if (nextIfNull()) {
              return null;
          }
  
          wasNull = false;
          double value = readDoubleValue();
          if (wasNull) {
              return null;
          }
          return value;
      }
  
      /**
       * Reads a Number value from JSON data.
       *
       * @return The Number value
       */
      public Number readNumber() {
          readNumber0();
          return getNumber();
      }
  
      /**
       * Reads a BigInteger value from JSON data.
       *
       * @return The BigInteger value
       */
      public BigInteger readBigInteger() {
          readNumber0();
          return getBigInteger();
      }
  
      /**
       * Reads a BigDecimal value from JSON data.
       *
       * @return The BigDecimal value
       */
      public abstract BigDecimal readBigDecimal();
  
      /**
       * Reads a UUID value from JSON data.
       *
       * @return The UUID value
       */
      public abstract UUID readUUID();
  
      /**
       * Reads a LocalDate value from JSON data.
       *
       * @return The LocalDate value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the date
       */
      public LocalDate readLocalDate() {
          if (nextIfNull()) {
              return null;
          }
  
          if (isInt()) {
              long millis = readInt64Value();
              if (context.formatUnixTime) {
                  millis *= 1000L;
              }
              Instant instant = Instant.ofEpochMilli(millis);
              ZonedDateTime zdt = instant.atZone(context.getZoneId());
              return zdt.toLocalDate();
          }
  
          if (context.dateFormat == null
                  || context.formatyyyyMMddhhmmss19
                  || context.formatyyyyMMddhhmmssT19
                  || context.formatyyyyMMdd8
                  || context.formatISO8601) {
              int len = getStringLength();
              LocalDateTime ldt = null;
              LocalDate localDate;
              switch (len) {
                  case 8:
                      localDate = readLocalDate8();
                      ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                      break;
                  case 9:
                      localDate = readLocalDate9();
                      ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                      break;
                  case 10:
                      localDate = readLocalDate10();
                      ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                      break;
                  case 11:
                      localDate = readLocalDate11();
                      ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                      break;
                  case 19:
                      ldt = readLocalDateTime19();
                      break;
                  case 20:
                      ldt = readLocalDateTime20();
                      break;
                  default:
                      if (len > 20) {
                          ldt = readLocalDateTimeX(len);
                      }
                      break;
              }
              if (ldt != null) {
                  return ldt.toLocalDate();
              }
          }
  
          String str = readString();
          if (str.isEmpty() || "null".equals(str)) {
              return null;
          }
  
          DateTimeFormatter formatter = context.getDateFormatter();
          if (formatter != null) {
              if (context.formatHasHour) {
                  return LocalDateTime
                          .parse(str, formatter)
                          .toLocalDate();
              }
              return LocalDate.parse(str, formatter);
          }
  
          if (IOUtils.isNumber(str)) {
              long millis = Long.parseLong(str);
              Instant instant = Instant.ofEpochMilli(millis);
              ZonedDateTime zdt = instant.atZone(context.getZoneId());
              return zdt.toLocalDate();
          }
  
          throw new JSONException("not support input : " + str);
      }
  
      /**
       * Reads a LocalDateTime value from JSON data.
       *
       * @return The LocalDateTime value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the date/time
       */
      public LocalDateTime readLocalDateTime() {
          if (isInt()) {
              long millis = readInt64Value();
              Instant instant = Instant.ofEpochMilli(millis);
              ZonedDateTime zdt = instant.atZone(context.getZoneId());
              return zdt.toLocalDateTime();
          }
  
          if (isTypeRedirect() && nextIfMatchIdent('"', 'v', 'a', 'l', '"')) {
              nextIfMatch(':');
              LocalDateTime dateTime = readLocalDateTime();
              nextIfObjectEnd();
              setTypeRedirect(false);
              return dateTime;
          }
  
          if (context.dateFormat == null
                  || context.formatyyyyMMddhhmmss19
                  || context.formatyyyyMMddhhmmssT19
                  || context.formatyyyyMMdd8
                  || context.formatISO8601) {
              int len = getStringLength();
              LocalDate localDate;
              switch (len) {
                  case 8:
                      localDate = readLocalDate8();
                      return localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                  case 9:
                      localDate = readLocalDate9();
                      return localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                  case 10:
                      localDate = readLocalDate10();
                      return localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                  case 11:
                      localDate = readLocalDate11();
                      return localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                  case 16:
                      return readLocalDateTime16();
                  case 17: {
                      LocalDateTime ldt = readLocalDateTime17();
                      if (ldt != null) {
                          return ldt;
                      }
                      break;
                  }
                  case 18: {
                      LocalDateTime ldt = readLocalDateTime18();
                      if (ldt != null) {
                          return ldt;
                      }
                      break;
                  }
                  case 19: {
                      LocalDateTime ldt = readLocalDateTime19();
                      if (ldt != null) {
                          return ldt;
                      }
                      break;
                  }
                  case 20: {
                      LocalDateTime ldt = readLocalDateTime20();
                      if (ldt != null) {
                          return ldt;
                      }
                      ZonedDateTime zdt = readZonedDateTimeX(len);
                      if (zdt != null) {
                          return zdt.toLocalDateTime();
                      }
                      break;
                  }
                  case 21:
                  case 22:
                  case 23:
                  case 24:
                  case 25:
                  case 26:
                  case 27:
                  case 28:
                  case 29:
                      LocalDateTime ldt = readLocalDateTimeX(len);
                      if (ldt != null) {
                          return ldt;
                      }
                      ZonedDateTime zdt = readZonedDateTimeX(len);
                      if (zdt != null) {
                          ZoneId contextZoneId = context.getZoneId();
                          if (!zdt.getZone().equals(contextZoneId)) {
                              ldt = zdt.toInstant().atZone(contextZoneId).toLocalDateTime();
                          } else {
                              ldt = zdt.toLocalDateTime();
                          }
                          return ldt;
                      }
                      break;
                  default:
                      break;
              }
          }
  
          String str = readString();
          if (str.isEmpty() || "null".equals(str)) {
              wasNull = true;
              return null;
          }
  
          DateTimeFormatter formatter = context.getDateFormatter();
          if (formatter != null) {
              if (!context.formatHasHour) {
                  return LocalDateTime.of(
                          LocalDate.parse(str, formatter),
                          LocalTime.MIN
                  );
              }
              return LocalDateTime.parse(str, formatter);
          }
  
          if (IOUtils.isNumber(str)) {
              long millis = Long.parseLong(str);
  
              if (context.formatUnixTime) {
                  millis *= 1000L;
              }
  
              Instant instant = Instant.ofEpochMilli(millis);
              return LocalDateTime.ofInstant(instant, context.getZoneId());
          }
  
          if (str.startsWith("/Date(") && str.endsWith(")/")) {
              String dotnetDateStr = str.substring(6, str.length() - 2);
              int i = dotnetDateStr.indexOf('+');
              if (i == -1) {
                  i = dotnetDateStr.indexOf('-');
              }
              if (i != -1) {
                  dotnetDateStr = dotnetDateStr.substring(0, i);
              }
              long millis = Long.parseLong(dotnetDateStr);
              Instant instant = Instant.ofEpochMilli(millis);
              return LocalDateTime.ofInstant(instant, context.getZoneId());
          }
  
          if ("0000-00-00 00:00:00".equals(str)) {
              wasNull = true;
              return null;
          }
          throw new JSONException(info("read LocalDateTime error " + str));
      }
  
      public abstract OffsetDateTime readOffsetDateTime();
  
      /**
       * Reads a ZonedDateTime value from JSON data.
       *
       * @return The ZonedDateTime value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the date/time
       */
      public ZonedDateTime readZonedDateTime() {
          if (isInt()) {
              long millis = readInt64Value();
              if (context.formatUnixTime) {
                  millis *= 1000L;
              }
              Instant instant = Instant.ofEpochMilli(millis);
              return instant.atZone(context.getZoneId());
          }
  
          if (isString()) {
              if (context.dateFormat == null
                      || context.formatyyyyMMddhhmmss19
                      || context.formatyyyyMMddhhmmssT19
                      || context.formatyyyyMMdd8
                      || context.formatISO8601) {
                  int len = getStringLength();
                  LocalDateTime ldt = null;
                  LocalDate localDate;
                  switch (len) {
                      case 8:
                          localDate = readLocalDate8();
                          ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                          break;
                      case 9:
                          localDate = readLocalDate9();
                          ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                          break;
                      case 10:
                          localDate = readLocalDate10();
                          ldt = localDate == null ? null : LocalDateTime.of(localDate, LocalTime.MIN);
                          break;
                      case 11:
                          localDate = readLocalDate11();
                          ldt = LocalDateTime.of(localDate, LocalTime.MIN);
                          break;
                      case 16:
                          ldt = readLocalDateTime16();
                          break;
                      case 17:
                          ldt = readLocalDateTime17();
                          break;
                      case 18:
                          ldt = readLocalDateTime18();
                          break;
                      case 19:
                          ldt = readLocalDateTime19();
                          break;
                      case 20: {
                          ldt = readLocalDateTime20();
                          break;
                      }
                      default:
                          ZonedDateTime zdt = readZonedDateTimeX(len);
                          if (zdt != null) {
                              return zdt;
                          }
                          break;
                  }
                  if (ldt != null) {
                      return ZonedDateTime.ofLocal(
                              ldt,
                              context.getZoneId(),
                              null
                      );
                  }
              }
  
              String str = readString();
              if (str.isEmpty() || "null".equals(str)) {
                  return null;
              }
  
              DateTimeFormatter formatter = context.getDateFormatter();
              if (formatter != null) {
                  if (!context.formatHasHour) {
                      LocalDate localDate = LocalDate.parse(str, formatter);
                      return ZonedDateTime.of(localDate, LocalTime.MIN, context.getZoneId());
                  }
                  LocalDateTime localDateTime = LocalDateTime.parse(str, formatter);
                  return ZonedDateTime.of(localDateTime, context.getZoneId());
              }
  
              if (IOUtils.isNumber(str)) {
                  long millis = Long.parseLong(str);
                  if (context.formatUnixTime) {
                      millis *= 1000L;
                  }
                  Instant instant = Instant.ofEpochMilli(millis);
                  return instant.atZone(context.getZoneId());
              }
  
              return ZonedDateTime.parse(str);
          }
  
          if (nextIfNull()) {
              return null;
          }
          throw new JSONException("TODO : " + ch);
      }
  
      public abstract OffsetTime readOffsetTime();
  
      /**
       * Reads a Calendar value from JSON data.
       *
       * @return The Calendar value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the date
       */
      public Calendar readCalendar() {
          if (isString()) {
              long millis = readMillisFromString();
              if (millis == 0 && wasNull) {
                  return null;
              }
  
              Calendar calendar = Calendar.getInstance();
              calendar.setTimeInMillis(millis);
              return calendar;
          }
  
          if (readIfNull()) {
              return null;
          }
  
          long millis = readInt64Value();
          if (context.formatUnixTime) {
              millis *= 1000;
          }
  
          Calendar calendar = Calendar.getInstance();
          calendar.setTimeInMillis(millis);
          return calendar;
      }
  
      /**
       * Reads a Date value from JSON data.
       *
       * @return The Date value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the date
       */
      public Date readDate() {
          if (isInt()) {
              long millis = readInt64Value();
              return new Date(millis);
          }
  
          if (readIfNull()) {
              return null;
          }
  
          if (nextIfNullOrEmptyString()) {
              return null;
          }
  
          if (current() == 'n') {
              return readNullOrNewDate();
          }
  
          long millis;
          if (isTypeRedirect() && nextIfMatchIdent('"', 'v', 'a', 'l', '"')) {
              nextIfMatch(':');
              millis = readInt64Value();
              nextIfObjectEnd();
              setTypeRedirect(false);
          } else if (isObject()) {
              JSONObject object = readJSONObject();
              Object date = object.get("$date");
              if (date instanceof String) {
                  return DateUtils.parseDate((String) date, context.getZoneId());
              }
              return toDate(object);
          } else {
              millis = readMillisFromString();
          }
  
          if (millis == 0 && wasNull) {
              return null;
          }
  
          return new Date(millis);
      }
  
      /**
       * Reads a LocalTime value from JSON data.
       *
       * @return The LocalTime value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the time
       */
      public LocalTime readLocalTime() {
          if (nextIfNull()) {
              return null;
          }
  
          if (isInt()) {
              long millis = readInt64Value();
              Instant instant = Instant.ofEpochMilli(millis);
              ZonedDateTime zdt = instant.atZone(context.getZoneId());
              return zdt.toLocalTime();
          }
  
          int len = getStringLength();
          switch (len) {
              case 5:
                  return readLocalTime5();
              case 6:
                  return readLocalTime6();
              case 7:
                  return readLocalTime7();
              case 8:
                  return readLocalTime8();
              case 9:
                  return readLocalTime9();
              case 10:
                  return readLocalTime10();
              case 11:
                  return readLocalTime11();
              case 12:
                  return readLocalTime12();
              case 15:
                  return readLocalTime15();
              case 18:
                  return readLocalTime18();
              case 19:
                  return readLocalDateTime19()
                          .toLocalTime();
              case 20:
                  return readLocalDateTime20()
                          .toLocalTime();
              default:
                  break;
          }
  
          String str = readString();
          if (str.isEmpty() || "null".equals(str)) {
              return null;
          }
  
          if (IOUtils.isNumber(str)) {
              long millis = Long.parseLong(str);
              Instant instant = Instant.ofEpochMilli(millis);
              ZonedDateTime zdt = instant.atZone(context.getZoneId());
              return zdt.toLocalTime();
          }
  
          throw new JSONException("not support len : " + str);
      }
  
      /**
       * Gets the length of the current string value in the JSON data.
       *
       * @return The length of the current string value
       */
      protected abstract int getStringLength();
  
      /**
       * Checks if the current value represents a date.
       *
       * @return true if the current value represents a date, false otherwise
       */
      public boolean isDate() {
          return false;
      }
  
      /**
       * Reads an Instant value from JSON data.
       *
       * @return The Instant value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the instant
       */
      public Instant readInstant() {
          if (nextIfNull()) {
              return null;
          }
  
          if (isNumber()) {
              long millis = readInt64Value();
              if (context.formatUnixTime) {
                  millis *= 1000L;
              }
              return Instant.ofEpochMilli(millis);
          }
  
          if (isObject()) {
              return (Instant) getObjectReader(Instant.class)
                      .createInstance(
                              readObject(),
                              0L
                      );
          }
  
          ZonedDateTime zdt = readZonedDateTime();
          if (zdt == null) {
              return null;
          }
  
          return Instant.ofEpochSecond(
                  zdt.toEpochSecond(),
                  zdt.toLocalTime().getNano());
      }
  
      /**
       * Reads milliseconds from a string value in JSON data.
       *
       * @return The milliseconds value
       * @throws JSONException if there is an error parsing the string
       */
      public final long readMillisFromString() {
          wasNull = false;
          String format = context.dateFormat;
          if (format == null
                  || context.formatyyyyMMddhhmmss19
                  || context.formatyyyyMMddhhmmssT19
                  || context.formatyyyyMMdd8
                  || context.formatISO8601) {
              int len = getStringLength();
              LocalDateTime ldt = null;
              LocalDate localDate;
              switch (len) {
                  case 8: {
                      localDate = readLocalDate8();
                      if (localDate == null) {
                          throw new JSONException("TODO : " + readString());
                      }
                      ldt = LocalDateTime.of(localDate, LocalTime.MIN);
                      break;
                  }
                  case 9: {
                      localDate = readLocalDate9();
                      if (localDate != null) {
                          ldt = LocalDateTime.of(localDate, LocalTime.MIN);
                      }
                      break;
                  }
                  case 10: {
                      localDate = readLocalDate10();
                      if (localDate == null) {
                          String str = readString();
                          if ("0000-00-00".equals(str)) {
                              wasNull = true;
                              return 0;
                          }
                          if (IOUtils.isNumber(str)) {
                              return Long.parseLong(str);
                          }
                          throw new JSONException("TODO : " + str);
                      } else {
                          ldt = LocalDateTime.of(localDate, LocalTime.MIN);
                      }
                      break;
                  }
                  case 11: {
                      localDate = readLocalDate11();
                      if (localDate != null) {
                          ldt = LocalDateTime.of(localDate, LocalTime.MIN);
                      }
                      break;
                  }
                  case 12: {
                      ldt = readLocalDateTime12();
                      break;
                  }
                  case 14: {
                      ldt = readLocalDateTime14();
                      break;
                  }
                  case 16: {
                      ldt = readLocalDateTime16();
                      break;
                  }
                  case 17: {
                      ldt = readLocalDateTime17();
                      break;
                  }
                  case 18: {
                      ldt = readLocalDateTime18();
                      break;
                  }
                  case 19: {
                      long millis = readMillis19();
                      if (millis != 0 || !wasNull) {
                          return millis;
                      }
  
                      ldt = readLocalDateTime19();
                      break;
                  }
                  case 20: {
                      ldt = readLocalDateTime20();
                      break;
                  }
                  default:
                      break;
              }
  
              ZonedDateTime zdt = null;
              if (ldt != null) {
                  zdt = ZonedDateTime.ofLocal(ldt, context.getZoneId(), null);
              } else if (len >= 20) {
                  zdt = readZonedDateTimeX(len);
                  if (zdt == null && (len >= 32 && len <= 35)) {
                      String str = readString();
                      zdt = DateUtils.parseZonedDateTime(str, null);
                  }
              }
  
              if (zdt != null) {
                  long seconds = zdt.toEpochSecond();
                  int nanos = zdt.toLocalTime().getNano();
                  if (seconds < 0 && nanos > 0) {
                      long millis = (seconds + 1) * 1000;
                      long adjustment = nanos / 1000_000 - 1000;
                      return millis + adjustment;
                  } else {
                      long millis = seconds * 1000L;
                      return millis + nanos / 1000_000;
                  }
              }
          }
  
          String str = readString();
  
          if (str.isEmpty() || "null".equals(str)) {
              wasNull = true;
              return 0;
          }
  
          if (context.formatMillis || context.formatUnixTime) {
              long millis = Long.parseLong(str);
              if (context.formatUnixTime) {
                  millis *= 1000L;
              }
              return millis;
          }
  
          if (format != null && !format.isEmpty()) {
              if ("yyyy-MM-dd HH:mm:ss".equals(format)) {
                  if ((str.length() < 4 || str.charAt(4) != '-') && IOUtils.isNumber(str)) {
                      return Long.parseLong(str);
                  }
  
                  return DateUtils.parseMillis19(str, context.getZoneId());
              }
  
              if ("yyyy-MM-dd HH:mm:ss.SSS".equals(format)
                      && str.length() == 19
                      && str.charAt(4) == '-'
                      && str.charAt(7) == '-'
                      && str.charAt(10) == ' '
                      && str.charAt(13) == ':'
                      && str.charAt(16) == ':'
              ) {
                  return DateUtils.parseMillis19(str, context.getZoneId());
              }
  
              SimpleDateFormat utilFormat = new SimpleDateFormat(format);
              try {
                  return utilFormat
                          .parse(str)
                          .getTime();
              } catch (ParseException e) {
                  throw new JSONException("parse date error, " + str + ", expect format " + utilFormat.toPattern());
              }
          }
          if ("0000-00-00T00:00:00".equals(str)
                  || "0001-01-01T00:00:00+08:00".equals(str)) {
              return 0;
          }
  
          if (str.startsWith("/Date(") && str.endsWith(")/")) {
              String dotnetDateStr = str.substring(6, str.length() - 2);
              int i = dotnetDateStr.indexOf('+');
              if (i == -1) {
                  i = dotnetDateStr.indexOf('-');
              }
              if (i != -1) {
                  dotnetDateStr = dotnetDateStr.substring(0, i);
              }
              return Long.parseLong(dotnetDateStr);
          } else if (IOUtils.isNumber(str)) {
              return Long.parseLong(str);
          }
  
          throw new JSONException(info("format " + format + " not support, input " + str));
      }
  
      protected abstract LocalDateTime readLocalDateTime12();
  
      protected abstract LocalDateTime readLocalDateTime14();
  
      protected abstract LocalDateTime readLocalDateTime16();
  
      protected abstract LocalDateTime readLocalDateTime17();
  
      protected abstract LocalDateTime readLocalDateTime18();
  
      protected abstract LocalDateTime readLocalDateTime19();
  
      protected abstract LocalDateTime readLocalDateTime20();
  
      /**
       * Reads milliseconds from a 19-character date string in JSON data.
       *
       * @return The milliseconds value
       */
      public abstract long readMillis19();
  
      protected abstract LocalDateTime readLocalDateTimeX(int len);
  
      protected abstract LocalTime readLocalTime5();
  
      protected abstract LocalTime readLocalTime6();
  
      protected abstract LocalTime readLocalTime7();
  
      protected abstract LocalTime readLocalTime8();
  
      protected abstract LocalTime readLocalTime9();
  
      protected abstract LocalTime readLocalTime10();
  
      protected abstract LocalTime readLocalTime11();
  
      protected abstract LocalTime readLocalTime12();
  
      protected abstract LocalTime readLocalTime15();
  
      protected abstract LocalTime readLocalTime18();
  
      protected abstract LocalDate readLocalDate8();
  
      protected abstract LocalDate readLocalDate9();
  
      protected abstract LocalDate readLocalDate10();
  
      protected abstract LocalDate readLocalDate11();
  
      protected abstract ZonedDateTime readZonedDateTimeX(int len);
  
      /**
       * Reads a number value from JSON data and passes it to a consumer.
       *
       * @param consumer The consumer to accept the number value
       * @param quoted Whether the number should be quoted in the output
       */
      public void readNumber(ValueConsumer consumer, boolean quoted) {
          readNumber0();
          Number number = getNumber();
          consumer.accept(number);
      }
  
      /**
       * Reads a string value from JSON data and passes it to a consumer.
       *
       * @param consumer The consumer to accept the string value
       * @param quoted Whether the string should be quoted in the output
       */
      public void readString(ValueConsumer consumer, boolean quoted) {
          String str = readString(); //
          if (quoted) {
              consumer.accept(JSON.toJSONString(str));
          } else {
              consumer.accept(str);
          }
      }
  
      /**
       * Reads a string value from JSON data.
       *
       * @return The string value
       */
      public abstract String readString();
  
      /**
       * Reads a number value from JSON data and stores it in internal fields.
       * This is a low-level method used by other number reading methods.
       */
      protected abstract void readNumber0();
  
      /**
       * Reads a Base64 encoded string from JSON data and decodes it to bytes.
       *
       * @return The decoded byte array
       */
      /**
       * Reads a Base64 encoded string from JSON data and decodes it to bytes.
       *
       * @return The decoded byte array
       */
      public byte[] readBase64() {
          String str = readString();
          if (str != null) {
              String prefix = "data:image/";
              int p0, p1;
              String base64 = "base64";
              if (str.startsWith(prefix)
                      && (p0 = str.indexOf(';', prefix.length() + 1)) != -1
                      && (p1 = str.indexOf(',', p0 + 1)) != -1 && str.regionMatches(p0 + 1, base64, 0, base64.length())) {
                  str = str.substring(p1 + 1);
              }
          }
          if (str.isEmpty()) {
              return new byte[0];
          }
          return Base64.getDecoder().decode(str);
      }
  
      /**
       * Reads a JSON array of strings and returns it as a String array.
       *
       * @return The String array
       */
      /**
       * Reads a JSON array of strings and returns it as a String array.
       *
       * @return The String array, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the JSON
       */
      public String[] readStringArray() {
          if ((ch == 'n') && nextIfNull()) {
              return null;
          }
  
          if (nextIfArrayStart()) {
              String[] values = null;
              int size = 0;
              for (; ; ) {
                  if (nextIfArrayEnd()) {
                      if (values == null) {
                          values = new String[0];
                      }
                      break;
                  }
  
                  if (isEnd()) {
                      throw new JSONException(info("input end"));
                  }
  
                  if (values == null) {
                      values = new String[16];
                  } else {
                      if (size == values.length) {
                          values = Arrays.copyOf(values, values.length << 1);
                      }
                  }
  
                  values[size++] = readString();
              }
  
              if (values.length == size) {
                  return values;
              }
              return Arrays.copyOf(values, size);
          }
  
          if (this.ch == '"' || this.ch == '\'') {
              String str = readString();
              if (str.isEmpty()) {
                  return null;
              }
  
              throw new JSONException(info("not support input " + str));
          }
  
          throw new JSONException(info("not support input"));
      }
  
      /**
       * Reads a character value from JSON data.
       *
       * @return The character value
       */
      public char readCharValue() {
          String str = readString();
          if (str == null || str.isEmpty()) {
              wasNull = true;
              return '\0';
          }
          return str.charAt(0);
      }
  
      /**
       * Reads a Character object from JSON data.
       *
       * @return The Character object, or null if the value is null in JSON
       */
      public Character readCharacter() {
          String str = readString();
          if (str == null || str.isEmpty()) {
              wasNull = true;
              return '\0';
          }
          return str.charAt(0);
      }
  
      /**
       * Reads a null value from JSON data and advances the reader.
       */
      public abstract void readNull();
  
      protected double readNaN() {
          throw new JSONException("not support");
      }
  
      /**
       * Checks if the current JSON value is null and advances the reader if it is.
       *
       * @return true if the current value is null, false otherwise
       */
      public abstract boolean readIfNull();
  
      /**
       * Gets the current string value from the JSON data.
       *
       * @return The current string value
       */
      public abstract String getString();
  
      public boolean wasNull() {
          return wasNull;
      }
  
      public <T> T read(Type type) {
          boolean fieldBased = (context.features & Feature.FieldBased.mask) != 0;
          ObjectReader objectReader = context.provider.getObjectReader(type, fieldBased);
          return (T) objectReader.readObject(this, null, null, 0);
      }
  
      public final void read(List list) {
          if (!nextIfArrayStart()) {
              throw new JSONException("illegal input, offset " + offset + ", char " + ch);
          }
  
          level++;
          if (level >= context.maxLevel) {
              throw new JSONException("level too large : " + level);
          }
  
          for (; ; ) {
              if (nextIfArrayEnd()) {
                  level--;
                  break;
              }
  
              Object item = ObjectReaderImplObject.INSTANCE.readObject(this, null, null, 0);
              list.add(item);
  
              nextIfComma();
          }
  
          nextIfComma();
      }
  
      public final void read(Collection list) {
          if (!nextIfArrayStart()) {
              throw new JSONException("illegal input, offset " + offset + ", char " + ch);
          }
  
          level++;
          if (level >= context.maxLevel) {
              throw new JSONException("level too large : " + level);
          }
  
          for (; ; ) {
              if (nextIfArrayEnd()) {
                  level--;
                  break;
              }
              Object item = readAny();
              list.add(item);
  
              nextIfComma();
          }
  
          nextIfComma();
      }
  
      public final void readObject(Object object, Feature... features) {
          long featuresLong = 0;
          for (Feature feature : features) {
              featuresLong |= feature.mask;
          }
          readObject(object, featuresLong);
      }
  
      public final void readObject(Object object, long features) {
          if (object == null) {
              throw new JSONException("object is null");
          }
          Class objectClass = object.getClass();
          boolean fieldBased = ((context.features | features) & Feature.FieldBased.mask) != 0;
          ObjectReader objectReader = context.provider.getObjectReader(objectClass, fieldBased);
          if (objectReader instanceof ObjectReaderBean) {
              ObjectReaderBean objectReaderBean = (ObjectReaderBean) objectReader;
              objectReaderBean.readObject(this, object, features);
          } else if (object instanceof Map) {
              read((Map) object, features);
          } else {
              throw new JSONException("read object not support");
          }
      }
  
      /**
       * Reads JSON data into a Map using a specified ObjectReader for the values.
       *
       * @param object The Map to populate with data
       * @param itemReader The ObjectReader to use for reading values
       * @param features Reader features to apply during reading
       * @since 2.0.35
       */
      public void read(Map object, ObjectReader itemReader, long features) {
          nextIfObjectStart();
          Map map;
          if (object instanceof Wrapper) {
              map = ((Wrapper) object).unwrap(Map.class);
          } else {
              map = object;
          }
  
          long contextFeatures = features | context.getFeatures();
  
          for (int i = 0; ; ++i) {
              if (ch == '/') {
                  skipComment();
              }
  
              if (nextIfObjectEnd()) {
                  break;
              }
  
              if (i != 0 && !comma) {
                  throw new JSONException(info());
              }
  
              String name = readFieldName();
              Object value = itemReader.readObject(this, itemReader.getObjectClass(), name, features);
  
              if (value == null && (contextFeatures & Feature.IgnoreNullPropertyValue.mask) != 0) {
                  continue;
              }
  
              Object origin = map.put(name, value);
              if (origin != null) {
                  if ((contextFeatures & Feature.DuplicateKeyValueAsArray.mask) != 0) {
                      if (origin instanceof Collection) {
                          ((Collection) origin).add(value);
                          map.put(name, origin);
                      } else {
                          JSONArray array = JSONArray.of(origin, value);
                          map.put(name, array);
                      }
                  }
              }
          }
  
          nextIfComma();
      }
  
      /**
       * Reads JSON data into a Map with specified features.
       *
       * @param object The Map to populate with data
       * @param features Reader features to apply during reading
       */
      public void read(Map object, long features) {
          if (ch == '\'' && ((context.features & Feature.DisableSingleQuote.mask) != 0)) {
              throw notSupportName();
          }
          if ((ch == '"' || ch == '\'') && !typeRedirect) {
              String str = readString();
              if (str.isEmpty()) {
                  return;
              }
              if (str.charAt(0) == '{') {
                  try (JSONReader jsonReader = JSONReader.of(str, context)) {
                      jsonReader.readObject(object, features);
                      return;
                  }
              }
          }
  
          boolean match = nextIfObjectStart();
          boolean typeRedirect = false;
          if (!match) {
              if (typeRedirect = isTypeRedirect()) {
                  setTypeRedirect(false);
              } else {
                  if (isString()) {
                      String str = readString();
                      if (str.isEmpty()) {
                          return;
                      }
                  }
                  throw new JSONException(info());
              }
          }
  
          Map map;
          if (object instanceof Wrapper) {
              map = ((Wrapper) object).unwrap(Map.class);
          } else {
              map = object;
          }
  
          long contextFeatures = features | context.getFeatures();
  
          for (int i = 0; ; ++i) {
              if (ch == '/') {
                  skipComment();
              }
  
              if (nextIfObjectEnd()) {
                  break;
              }
  
              if (i != 0 && !comma) {
                  throw new JSONException(info());
              }
  
              Object name;
              if (match || typeRedirect) {
                  if ((ch >= '0' && ch <= '9') || ch == '-') {
                      name = null;
                  } else {
                      name = readFieldName();
                  }
              } else {
                  name = getFieldName();
                  match = true;
              }
  
              if (name == null) {
                  if (isNumber()) {
                      name = readNumber();
                      if ((context.features & Feature.NonStringKeyAsString.mask) != 0) {
                          name = name.toString();
                      }
                      if (comma) {
                          throw new JSONException(info("syntax error, illegal key-value"));
                      }
                  } else {
                      if ((context.features & Feature.AllowUnQuotedFieldNames.mask) != 0) {
                          name = readFieldNameUnquote();
                      } else {
                          throw new JSONException(info("not allow unquoted fieldName"));
                      }
                  }
                  if (ch == ':') {
                      next();
                  }
              }
  
              if (isReference()) {
                  String reference = readReference();
                  Object value = null;
                  if ("..".equals(reference)) {
                      value = map;
                  } else {
                      JSONPath jsonPath;
                      try {
                          jsonPath = JSONPath.of(reference);
                      } catch (Exception ignored) {
                          map.put(name, JSONObject.of("$ref", reference));
                          continue;
                      }
                      addResolveTask(map, name, jsonPath);
                  }
                  map.put(name, value);
                  continue;
              }
  
              comma = false;
              Object value;
              switch (ch) {
                  case '-':
                  case '+':
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                  case '.':
                      value = readNumber();
                      break;
                  case '[':
                      value = readArray();
                      break;
                  case '{':
                      if (typeRedirect) {
                          value = ObjectReaderImplObject.INSTANCE.readObject(this, null, name, features);
                      } else {
                          value = readObject();
                      }
                      break;
                  case '"':
                  case '\'':
                      value = readString();
                      break;
                  case 't':
                  case 'f':
                      value = readBoolValue();
                      break;
                  case 'n':
                      value = readNullOrNewDate();
                      break;
                  case '/':
                      next();
                      if (ch == '/') {
                          skipComment();
                      } else {
                          throw new JSONException("FASTJSON" + JSON.VERSION + "input not support " + ch + ", offset " + offset);
                      }
                      continue;
                  case 'S':
                      if (nextIfSet()) {
                          value = read(HashSet.class);
                      } else {
                          throw new JSONException("FASTJSON" + JSON.VERSION + "error, offset " + offset + ", char " + ch);
                      }
                      break;
                  case 'I':
                      if (nextIfInfinity()) {
                          value = Double.POSITIVE_INFINITY;
                      } else {
                          throw new JSONException("FASTJSON" + JSON.VERSION + "error, offset " + offset + ", char " + ch);
                      }
                      break;
                  case 'x':
                      value = readBinary();
                      break;
                  default:
                      throw new JSONException("FASTJSON" + JSON.VERSION + "error, offset " + offset + ", char " + ch);
              }
  
              if (value == null && (contextFeatures & Feature.IgnoreNullPropertyValue.mask) != 0) {
                  continue;
              }
  
              if ((contextFeatures & Feature.SupportAutoType.mask) != 0
                      && name.equals("@type")
                      && object.getClass().getName().equals(value)
              ) {
                  continue;
              }
  
              Object origin = map.put(name, value);
              if (origin != null) {
                  if ((contextFeatures & Feature.DuplicateKeyValueAsArray.mask) != 0) {
                      if (origin instanceof Collection) {
                          ((Collection) origin).add(value);
                          map.put(name, origin);
                      } else {
                          JSONArray array = JSONArray.of(origin, value);
                          map.put(name, array);
                      }
                  }
              }
          }
  
          nextIfComma();
      }
  
      /**
       * Reads JSON data into a Map with specified key and value types.
       *
       * @param object The Map to populate with data
       * @param keyType The type of keys in the map
       * @param valueType The type of values in the map
       * @param features Reader features to apply during reading
       */
      public final void read(Map object, Type keyType, Type valueType, long features) {
          boolean match = nextIfObjectStart();
          if (!match) {
              throw new JSONException("illegal input， offset " + offset + ", char " + ch);
          }
  
          ObjectReader keyReader = context.getObjectReader(keyType);
          ObjectReader valueReader = context.getObjectReader(valueType);
  
          long contextFeatures = features | context.getFeatures();
  
          for (int i = 0; ; ++i) {
              if (ch == '/') {
                  skipComment();
              }
  
              if (nextIfObjectEnd()) {
                  break;
              }
  
              if (i != 0 && !comma) {
                  throw new JSONException(info());
              }
  
              Object name;
  
              if (keyType == String.class) {
                  name = readFieldName();
              } else {
                  name = keyReader.readObject(this, null, null, 0L);
                  nextIfMatch(':');
              }
  
              Object value = valueReader.readObject(this, null, null, 0L);
  
              if (value == null && (contextFeatures & Feature.IgnoreNullPropertyValue.mask) != 0) {
                  continue;
              }
  
              Object origin = object.put(name, value);
              if (origin != null) {
                  if ((contextFeatures & Feature.DuplicateKeyValueAsArray.mask) != 0) {
                      if (origin instanceof Collection) {
                          ((Collection) origin).add(value);
                          object.put(name, origin);
                      } else {
                          JSONArray array = JSONArray.of(origin, value);
                          object.put(name, array);
                      }
                  }
              }
          }
  
          nextIfComma();
      }
  
      public <T> T read(Class<T> type) {
          boolean fieldBased = (context.features & Feature.FieldBased.mask) != 0;
          ObjectReader objectReader = context.provider.getObjectReader(type, fieldBased);
          return (T) objectReader.readObject(this, null, null, 0);
      }
  
      /**
       * Reads JSON data and returns it as a Map.
       *
       * @return A Map representation of the JSON data
       * @throws JSONException if there is an error parsing the JSON
       */
      public Map<String, Object> readObject() {
          nextIfObjectStart();
  
          level++;
          if (level >= context.maxLevel) {
              throw new JSONException("level too large : " + level);
          }
  
          Map innerMap = null;
          Map object;
          if (context.objectSupplier == null) {
              if ((context.features & Feature.UseNativeObject.mask) != 0) {
                  object = new HashMap();
              } else {
                  object = new JSONObject();
              }
          } else {
              object = context.objectSupplier.get();
              innerMap = TypeUtils.getInnerMap(object);
          }
  
          for (int i = 0; ; ++i) {
              if (ch == '/') {
                  skipComment();
              }
  
              if (ch == '}') {
                  next();
                  break;
              }
  
              Object name = readFieldName();
              if (name == null) {
                  if (ch == EOI) {
                      throw new JSONException("input end");
                  }
  
                  if (ch == '-' || (ch >= '0' && ch <= '9')) {
                      readNumber0();
                      name = getNumber();
                  } else if (ch == '{') {
                      name = readObject();
                  } else if (ch == '[') {
                      name = readArray();
                  } else {
                      name = readFieldNameUnquote();
                  }
                  nextIfMatch(':');
              }
  
              if (i == 0 && (context.features & Feature.ErrorOnNotSupportAutoType.mask) != 0 && "@type".equals(name)) {
                  String typeName = readString();
                  throw new JSONException("autoType not support : " + typeName);
              }
              Object val;
              switch (ch) {
                  case '-':
                  case '+':
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                      readNumber0();
                      val = getNumber();
                      break;
                  case '[':
                      val = readArray();
                      break;
                  case '{':
                      if (isReference()) {
                          addResolveTask(object, name, JSONPath.of(readReference()));
                          val = null;
                      } else {
                          val = readObject();
                      }
                      break;
                  case '"':
                  case '\'':
                      val = readString();
                      break;
                  case 't':
                  case 'f':
                      val = readBoolValue();
                      break;
                  case 'n':
                      val = readNullOrNewDate();
                      break;
                  case '/':
                      skipComment();
                      continue;
                  case 'I':
                      if (nextIfInfinity()) {
                          val = Double.POSITIVE_INFINITY;
                          break;
                      } else {
                          throw new JSONException(info("illegal input " + ch));
                      }
                  case 'S':
                      if (nextIfSet()) {
                          val = read(Set.class);
                          break;
                      } else {
                          throw new JSONException(info("illegal input " + ch));
                      }
                  default:
                      throw new JSONException(info("illegal input " + ch));
              }
  
              if (val == null && (context.features & Feature.IgnoreNullPropertyValue.mask) != 0) {
                  continue;
              }
  
              Object origin;
              if (innerMap != null) {
                  origin = innerMap.put(name, val);
              } else {
                  origin = object.put(name, val);
              }
              if (origin != null) {
                  if ((context.features & Feature.DuplicateKeyValueAsArray.mask) != 0) {
                      if (origin instanceof Collection) {
                          ((Collection) origin).add(val);
                          object.put(name, origin);
                      } else {
                          JSONArray array = JSONArray.of(origin, val);
                          object.put(name, array);
                      }
                  }
              }
          }
  
          if (comma = (ch == ',')) {
              next();
          }
  
          level--;
  
          return object;
      }
  
      /**
       * Skips a comment in the JSON data, advancing the reader to the end of the comment.
       */
      public abstract void skipComment();
  
      /**
       * Reads a boolean value from JSON data.
       *
       * @return The boolean value, or null if the value is null in JSON
       * @throws JSONException if there is an error parsing the JSON
       */
      public Boolean readBool() {
          if (nextIfNull()) {
              return null;
          }
  
          wasNull = false;
          boolean boolValue = readBoolValue();
          if (!boolValue && wasNull) {
              return null;
          }
          return boolValue;
      }
  
      /**
       * Reads a boolean value from JSON data as a primitive boolean.
       *
       * @return The boolean value
       * @throws JSONException if there is an error parsing the JSON
       */
      /**
       * Reads a boolean value from JSON data.
       *
       * @return The boolean value
       */
      public abstract boolean readBoolValue();
  
      /**
       * Reads any JSON value and returns it as an Object.
       *
       * @return The JSON value as an Object
       */
      public Object readAny() {
          return read(Object.class);
      }
  
      /**
       * Reads a JSON array with elements of a specified type.
       *
       * @param itemType The type of elements in the array
       * @return A List containing the array elements
       */
      public List readArray(Type itemType) {
          if (nextIfNull()) {
              return null;
          }
  
          List list = new ArrayList();
          if (ch == '[') {
              next();
  
              boolean fieldBased = (context.features & Feature.FieldBased.mask) != 0;
              ObjectReader objectReader = context.provider.getObjectReader(itemType, fieldBased);
              for (int i = 0; !nextIfArrayEnd(); i++) {
                  int mark = offset;
                  Object item;
                  if (isReference()) {
                      String reference = readReference();
                      if ("..".equals(reference)) {
                          item = list;
                      } else {
                          item = null;
                          addResolveTask(list, i, JSONPath.of(reference));
                      }
                  } else {
                      item = objectReader.readObject(this, null, null, 0);
                  }
                  list.add(item);
                  if (mark == offset || ch == '}' || ch == EOI) {
                      throw new JSONException("illegal input : " + ch + ", offset " + getOffset());
                  }
              }
          } else if (ch == '"' || ch == '\'' || ch == '{') {
              String str = readString();
              if (str != null && !str.isEmpty()) {
                  list.add(str);
              }
          } else {
              throw new JSONException(info("syntax error"));
          }
  
          if (comma = (ch == ',')) {
              next();
          }
  
          return list;
      }
  
      /**
       * Reads a JSON array with elements of specified types.
       *
       * @param types The types of elements in the array
       * @return A List containing the array elements
       */
      public List readList(Type[] types) {
          if (nextIfNull()) {
              return null;
          }
  
          if (!nextIfArrayStart()) {
              throw new JSONException("syntax error : " + ch);
          }
  
          int i = 0, max = types.length;
          List list = new ArrayList(max);
  
          for (Object item; !nextIfArrayEnd() && i < max; list.add(item)) {
              int mark = offset;
              item = read(types[i++]);
  
              if (mark == offset || ch == '}' || ch == EOI) {
                  throw new JSONException("illegal input : " + ch + ", offset " + getOffset());
              }
          }
  
          if (i != max) {
              throw new JSONException(info("element length mismatch"));
          }
  
          if (comma = (ch == ',')) {
              next();
          }
  
          return list;
      }
  
      /**
       * Reads a JSON array with elements of specified types into an Object array.
       *
       * @param types The types of elements in the array
       * @return An Object array containing the array elements
       */
      public final Object[] readArray(Type[] types) {
          if (nextIfNull()) {
              return null;
          }
  
          if (!nextIfArrayStart()) {
              throw new JSONException(info("syntax error"));
          }
  
          int i = 0, max = types.length;
          Object[] list = new Object[max];
  
          for (Object item; !nextIfArrayEnd() && i < max; list[i++] = item) {
              int mark = offset;
              item = read(types[i]);
  
              if (mark == offset || ch == '}' || ch == EOI) {
                  throw new JSONException("illegal input : " + ch + ", offset " + getOffset());
              }
          }
  
          if (i != max) {
              throw new JSONException(info("element length mismatch"));
          }
  
          if (comma = (ch == ',')) {
              next();
          }
  
          return list;
      }
  
      public final void readArray(List list, Type itemType) {
          readArray((Collection) list, itemType);
      }
  
      public void readArray(Collection list, Type itemType) {
          if (nextIfArrayStart()) {
              while (!nextIfArrayEnd()) {
                  Object item = read(itemType);
                  list.add(item);
              }
              return;
          }
  
          if (isString()) {
              String str = readString();
              if (itemType == String.class) {
                  list.add(str);
              } else {
                  Function typeConvert = context.getProvider().getTypeConvert(String.class, itemType);
                  if (typeConvert == null) {
                      throw new JSONException(info("not support input " + str));
                  }
                  if (str.indexOf(',') != -1) {
                      String[] items = str.split(",");
                      for (String strItem : items) {
                          Object item = typeConvert.apply(strItem);
                          list.add(item);
                      }
                  } else {
                      Object item = typeConvert.apply(str);
                      list.add(item);
                  }
              }
          } else {
              Object item = read(itemType);
              list.add(item);
          }
  
          if (comma = (ch == ',')) {
              next();
          }
      }
  
      /**
       * Reads a JSON array and returns it as a JSONArray.
       *
       * @return A JSONArray representation of the JSON array
       */
      public final JSONArray readJSONArray() {
          JSONArray array = new JSONArray();
          read(array);
          return array;
      }
  
      /**
       * Reads a JSON object and returns it as a JSONObject.
       *
       * @return A JSONObject representation of the JSON object
       */
      public final JSONObject readJSONObject() {
          JSONObject object = new JSONObject();
          read(object, 0L);
          return object;
      }
  
      /**
       * Reads a JSON array and returns it as a List.
       *
       * @return A List representation of the JSON array
       */
      public List readArray() {
          next();
  
          level++;
          if (level >= context.maxLevel) {
              throw new JSONException("level too large : " + level);
          }
  
          int i = 0;
          List<Object> list = null;
          Object first = null, second = null;
  
          _for:
          for (; ; ++i) {
              Object val;
              switch (ch) {
                  case ']':
                      next();
                      break _for;
                  case '[':
                      val = readArray();
                      break;
                  case '{':
                      if (context.autoTypeBeforeHandler != null || (context.features & Feature.SupportAutoType.mask) != 0) {
                          val = ObjectReaderImplObject.INSTANCE.readObject(this, null, null, 0);
                      } else if (isReference()) {
                          val = JSONPath.of(readReference());
                      } else {
                          val = readObject();
                      }
                      break;
                  case '\'':
                  case '"':
                      val = readString();
                      break;
                  case '-':
                  case '+':
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                      readNumber0();
                      val = getNumber();
                      break;
                  case 't':
                  case 'f':
                      val = readBoolValue();
                      break;
                  case 'n': {
                      readNull();
                      val = null;
                      break;
                  }
                  case 'N':
                      val = readNaN();
                      break;
                  case 'S':
                      if (nextIfSet()) {
                          val = read(java.util.Set.class);
                      } else {
                          throw new JSONException(info());
                      }
                      break;
                  case '/':
                      skipComment();
                      --i;
                      continue;
                  default:
                      throw new JSONException(info());
              }
  
              if (i == 0) {
                  first = val;
              } else if (i == 1) {
                  second = val;
              } else if (i == 2) {
                  if (context.arraySupplier != null) {
                      list = context.arraySupplier.get();
                  } else {
                      list = new JSONArray();
                  }
  
                  add(list, 0, first);
                  add(list, 1, second);
                  add(list, i, val);
              } else {
                  add(list, i, val);
              }
          }
  
          if (list == null) {
              if (context.arraySupplier != null) {
                  list = context.arraySupplier.get();
              } else {
                  if (context.isEnabled(Feature.UseNativeObject)) {
                      list = i == 2 ? new ArrayList(2) : new ArrayList(1);
                  } else {
                      list = i == 2 ? new JSONArray(2) : new JSONArray(1);
                  }
              }
  
              if (i == 1) {
                  add(list, 0, first);
              } else if (i == 2) {
                  add(list, 0, first);
                  add(list, 1, second);
              }
          }
  
          if (comma = (ch == ',')) {
              next();
          }
  
          level--;
  
          return list;
      }
  
      private void add(List<Object> list, int i, Object val) {
          if (val instanceof JSONPath) {
              addResolveTask(list, i, (JSONPath) val);
              list.add(null);
          } else {
              list.add(val);
          }
      }
  
      public final BigInteger getBigInteger() {
          Number number = getNumber();
  
          if (number == null) {
              return null;
          }
  
          if (number instanceof BigInteger) {
              return (BigInteger) number;
          }
          return BigInteger.valueOf(number.longValue());
      }
  
      public final BigDecimal getBigDecimal() {
          if (wasNull) {
              return null;
          }
  
          switch (valueType) {
              case JSON_TYPE_INT: {
                  if (mag1 == 0 && mag2 == 0 && mag3 >= 0) {
                      return BigDecimal.valueOf(negative ? -mag3 : mag3);
                  }
                  int[] mag;
                  if (mag0 == 0) {
                      if (mag1 == 0) {
                          long v3 = mag3 & 0XFFFFFFFFL;
                          long v2 = mag2 & 0XFFFFFFFFL;
  
                          if (v2 <= Integer.MAX_VALUE) {
                              long v23 = (v2 << 32) + (v3);
                              return BigDecimal.valueOf(negative ? -v23 : v23);
                          }
                          mag = new int[]{mag2, mag3};
                      } else {
                          mag = new int[]{mag1, mag2, mag3};
                      }
                  } else {
                      mag = new int[]{mag0, mag1, mag2, mag3};
                  }
  
                  int signum = negative ? -1 : 1;
                  BigInteger bigInt = BIG_INTEGER_CREATOR.apply(signum, mag);
                  return new BigDecimal(bigInt);
              }
              case JSON_TYPE_DEC: {
                  BigDecimal decimal = null;
  
                  if (exponent == 0 && mag0 == 0 && mag1 == 0) {
                      if (mag2 == 0 && mag3 >= 0) {
                          int unscaledVal = negative ? -mag3 : mag3;
                          decimal = BigDecimal.valueOf(unscaledVal, scale);
                      } else {
                          long v3 = mag3 & 0XFFFFFFFFL;
                          long v2 = mag2 & 0XFFFFFFFFL;
  
                          if (v2 <= Integer.MAX_VALUE) {
                              long v23 = (v2 << 32) + (v3);
                              long unscaledVal = negative ? -v23 : v23;
                              decimal = BigDecimal.valueOf(unscaledVal, scale);
                          }
                      }
                  }
  
                  if (decimal == null) {
                      int[] mag = mag0 == 0
                              ? mag1 == 0
                              ? mag2 == 0
                              ? new int[]{mag3}
                              : new int[]{mag2, mag3}
                              : new int[]{mag1, mag2, mag3}
                              : new int[]{mag0, mag1, mag2, mag3};
  
                      int signum = negative ? -1 : 1;
                      BigInteger bigInt = BIG_INTEGER_CREATOR.apply(signum, mag);
                      decimal = new BigDecimal(bigInt, scale);
                  }
  
                  if (exponent != 0) {
                      String doubleStr = decimal.toPlainString() + "E" + exponent;
                      double doubleValue = Double.parseDouble(doubleStr);
                      return toBigDecimal(doubleValue);
                  }
  
                  return decimal;
              }
              case JSON_TYPE_BIG_DEC: {
                  return toBigDecimal(stringValue);
              }
              case JSON_TYPE_BOOL:
                  return boolValue ? BigDecimal.ONE : BigDecimal.ZERO;
              case JSON_TYPE_STRING: {
                  try {
                      return toBigDecimal(stringValue);
                  } catch (NumberFormatException ex) {
                      throw new JSONException(info("read decimal error, value " + stringValue), ex);
                  }
              }
              case JSON_TYPE_OBJECT: {
                  JSONObject object = (JSONObject) complex;
                  BigDecimal decimal = object.getBigDecimal("value");
                  if (decimal == null) {
                      decimal = object.getBigDecimal("$numberDecimal");
                  }
                  if (decimal != null) {
                      return decimal;
                  }
                  throw new JSONException("TODO : " + valueType);
              }
              default:
                  throw new JSONException("TODO : " + valueType);
          }
      }
  
      public final Number getNumber() {
          if (wasNull) {
              return null;
          }
  
          switch (valueType) {
              case JSON_TYPE_INT:
              case JSON_TYPE_INT64: {
                  if (mag0 == 0 && mag1 == 0 && mag2 == 0 && mag3 != Integer.MIN_VALUE) {
                      int intValue;
                      if (negative) {
                          if (mag3 < 0) {
                              long longValue = -(mag3 & 0xFFFFFFFFL);
                              if ((context.features & Feature.UseBigIntegerForInts.mask) != 0) {
                                  return BigInteger.valueOf(longValue);
                              }
                              return longValue;
                          }
                          intValue = -mag3;
                      } else {
                          if (mag3 < 0) {
                              long longValue = mag3 & 0xFFFFFFFFL;
                              if ((context.features & Feature.UseBigIntegerForInts.mask) != 0) {
                                  return BigInteger.valueOf(longValue);
                              }
                              return longValue;
                          }
                          intValue = mag3;
                      }
  
                      if ((context.features & Feature.UseBigIntegerForInts.mask) != 0) {
                          return BigInteger.valueOf(intValue);
                      }
  
                      if ((context.features & Feature.UseLongForInts.mask) != 0) {
                          return (long) intValue;
                      }
  
                      if (valueType == JSON_TYPE_INT64) {
                          return (long) intValue;
                      }
                      return intValue;
                  }
                  int[] mag;
                  if (mag0 == 0) {
                      if (mag1 == 0) {
                          long v3 = mag3 & 0XFFFFFFFFL;
                          long v2 = mag2 & 0XFFFFFFFFL;
  
                          if (v2 <= Integer.MAX_VALUE) {
                              long v23 = (v2 << 32) + (v3);
                              long longValue = negative ? -v23 : v23;
                              if ((context.features & Feature.UseBigIntegerForInts.mask) != 0) {
                                  return BigInteger.valueOf(longValue);
                              }
                              return longValue;
                          }
                          mag = new int[]{mag2, mag3};
                      } else {
                          mag = new int[]{mag1, mag2, mag3};
                      }
                  } else {
                      mag = new int[]{mag0, mag1, mag2, mag3};
                  }
  
                  int signum = negative ? -1 : 1;
                  BigInteger integer = BIG_INTEGER_CREATOR.apply(signum, mag);
                  if ((context.features & Feature.UseLongForInts.mask) != 0) {
                      return integer.longValue();
                  }
                  return integer;
              }
              case JSON_TYPE_INT16: {
                  if (mag0 == 0 && mag1 == 0 && mag2 == 0 && mag3 >= 0) {
                      int intValue = negative ? -mag3 : mag3;
                      return (short) intValue;
                  }
                  throw new JSONException(info("shortValue overflow"));
              }
              case JSON_TYPE_INT8: {
                  if (mag0 == 0 && mag1 == 0 && mag2 == 0 && mag3 >= 0) {
                      int intValue = negative ? -mag3 : mag3;
                      return (byte) intValue;
                  }
                  throw new JSONException(info("shortValue overflow"));
              }
              case JSON_TYPE_DEC: {
                  BigDecimal decimal = null;
  
                  if (mag0 == 0 && mag1 == 0) {
                      if (mag2 == 0 && mag3 >= 0) {
                          int unscaledVal = negative ? -mag3 : mag3;
                          decimal = BigDecimal.valueOf(unscaledVal, scale);
                      } else {
                          long v3 = mag3 & 0XFFFFFFFFL;
                          long v2 = mag2 & 0XFFFFFFFFL;
  
                          if (v2 <= Integer.MAX_VALUE) {
                              long v23 = (v2 << 32) + v3;
                              long unscaledVal = negative ? -v23 : v23;
                              decimal = BigDecimal.valueOf(unscaledVal, scale);
                          }
                      }
                  }
  
                  if (decimal == null) {
                      int[] mag = mag0 == 0
                              ? mag1 == 0
                              ? new int[]{mag2, mag3}
                              : new int[]{mag1, mag2, mag3}
                              : new int[]{mag0, mag1, mag2, mag3};
                      int signum = negative ? -1 : 1;
                      BigInteger bigInt = BIG_INTEGER_CREATOR.apply(signum, mag);
  
                      int adjustedScale = scale - exponent;
                      decimal = new BigDecimal(bigInt, adjustedScale);
                      if (exponent != 0 && (context.features & (Feature.UseBigDecimalForDoubles.mask | Feature.UseBigDecimalForFloats.mask)) == 0) {
                          return decimal.doubleValue();
                      }
                  }
  
                  if (exponent != 0) {
                      String decimalStr = decimal.toPlainString();
                      if ((context.features & (Feature.UseBigDecimalForDoubles.mask | Feature.UseBigDecimalForFloats.mask)) == 0) {
                          return Double.parseDouble(
                                  decimalStr + "E" + exponent);
                      }
                      return decimal.signum() == 0 ? BigDecimal.ZERO : new BigDecimal(decimalStr + "E" + exponent);
                  }
  
                  if ((context.features & Feature.UseDoubleForDecimals.mask) != 0) {
                      return decimal.doubleValue();
                  }
  
                  return decimal;
              }
              case JSON_TYPE_BIG_DEC: {
                  if (scale > 0) {
                      if (scale > defaultDecimalMaxScale) {
                          throw new JSONException("scale overflow : " + scale);
                      }
                      return toBigDecimal(stringValue);
                  } else {
                      return new BigInteger(stringValue);
                  }
              }
              case JSON_TYPE_FLOAT:
              case JSON_TYPE_DOUBLE: {
                  int[] mag = mag0 == 0
                          ? mag1 == 0
                          ? mag2 == 0
                          ? new int[]{mag3}
                          : new int[]{mag2, mag3}
                          : new int[]{mag1, mag2, mag3}
                          : new int[]{mag0, mag1, mag2, mag3};
                  int signum = negative ? -1 : 1;
                  BigInteger bigInt = BIG_INTEGER_CREATOR.apply(signum, mag);
                  BigDecimal decimal = new BigDecimal(bigInt, scale);
  
                  if (valueType == JSON_TYPE_FLOAT) {
                      if (exponent != 0) {
                          return Float.parseFloat(
                                  decimal + "E" + exponent);
                      }
  
                      return decimal.floatValue();
                  }
  
                  if (exponent != 0) {
                      return Double.parseDouble(
                              decimal + "E" + exponent);
                  }
                  return decimal.doubleValue();
              }
              case JSON_TYPE_BOOL:
                  return boolValue ? 1 : 0;
              case JSON_TYPE_NULL:
                  return null;
              case JSON_TYPE_STRING: {
                  return toInt64(stringValue);
              }
              case JSON_TYPE_OBJECT: {
                  return toNumber((Map) complex);
              }
              case JSON_TYPE_ARRAY: {
                  return toNumber((List) complex);
              }
              default:
                  throw new JSONException("TODO : " + valueType);
          }
      }
  
      @Override
      /**
       * Closes the JSONReader and releases any resources associated with it.
       *
       * @throws IOException if an I/O error occurs
       */
      public abstract void close();
  
      protected final int toInt32(String val) {
          if (IOUtils.isNumber(val) || val.lastIndexOf(',') == val.length() - 4) {
              return TypeUtils.toIntValue(val);
          }
  
          throw error("parseInt error, value : " + val);
      }
  
      protected final long toInt64(String val) {
          if (IOUtils.isNumber(val)
                  || val.lastIndexOf(',') == val.length() - 4) {
              return TypeUtils.toLongValue(val);
          }
  
          if (val.length() > 10 && val.length() < 40) {
              try {
                  return DateUtils.parseMillis(val, context.zoneId);
              } catch (DateTimeException | JSONException | NullPointerException ignored) {
                  // ignored
              }
          }
  
          throw error("parseLong error, value : " + val);
      }
  
      protected final long toLong(Map map) {
          Object val = map.get("val");
          if (val instanceof Number) {
              return ((Number) val).intValue();
          }
          throw error("parseLong error, value : " + map);
      }
  
      protected final int toInt(List list) {
          if (list.size() == 1) {
              Object val = list.get(0);
              if (val instanceof Number) {
                  return ((Number) val).intValue();
              }
              if (val instanceof String) {
                  return Integer.parseInt((String) val);
              }
          }
  
          throw error("parseLong error, field : value " + list);
      }
  
      protected final Number toNumber(Map map) {
          Object val = map.get("val");
          if (val instanceof Number) {
              return (Number) val;
          }
          return null;
      }
  
      protected final BigDecimal decimal(JSONObject object) {
          BigDecimal decimal = object.getBigDecimal("value");
          if (decimal == null) {
              decimal = object.getBigDecimal("$numberDecimal");
          }
          if (decimal != null) {
              return decimal;
          }
          throw error("can not cast to decimal " + object);
      }
  
      protected final Number toNumber(List list) {
          if (list.size() == 1) {
              Object val = list.get(0);
              if (val instanceof Number) {
                  return (Number) val;
              }
  
              if (val instanceof String) {
                  return toBigDecimal((String) val);
              }
          }
          return null;
      }
  
      protected final String toString(List array) {
          JSONWriter writer = JSONWriter.of();
          writer.setRootObject(array);
          writer.write(array);
          return writer.toString();
      }
  
      protected final String toString(Map object) {
          JSONWriter writer = JSONWriter.of();
          writer.setRootObject(object);
          writer.write(object);
          return writer.toString();
      }
  
      /**
       * Creates a JSONReader from a byte array containing UTF-8 encoded JSON.
       *
       * @param utf8Bytes The byte array containing UTF-8 encoded JSON
       * @return A JSONReader instance
       */
      public static JSONReader of(byte[] utf8Bytes) {
          return of(utf8Bytes, 0, utf8Bytes.length, StandardCharsets.UTF_8, createReadContext());
      }
  
      @Deprecated
      public static JSONReader of(Context context, byte[] utf8Bytes) {
          return JSONReaderUTF8.of(utf8Bytes, 0, utf8Bytes.length, context);
      }
  
      public static JSONReader of(byte[] utf8Bytes, Context context) {
          return JSONReaderUTF8.of(utf8Bytes, 0, utf8Bytes.length, context);
      }
  
      /**
       * Creates a JSONReader from a character array containing JSON data.
       *
       * @param chars The character array containing JSON data
       * @return A JSONReader instance
       */
      public static JSONReader of(char[] chars) {
          return ofUTF16(
                  null,
                  chars,
                  0,
                  chars.length, createReadContext());
      }
  
      @Deprecated
      public static JSONReader of(Context context, char[] chars) {
          return ofUTF16(null, chars, 0, chars.length, context);
      }
  
      public static JSONReader of(char[] chars, Context context) {
          return ofUTF16(null, chars, 0, chars.length, context);
      }
  
      /**
       * Creates a JSONReader from a byte array containing JSONB (binary JSON) data.
       *
       * @param jsonbBytes The byte array containing JSONB data
       * @return A JSONReader instance for JSONB data
       */
      public static JSONReader ofJSONB(byte[] jsonbBytes) {
          return new JSONReaderJSONB(
                  JSONFactory.createReadContext(),
                  jsonbBytes,
                  0,
                  jsonbBytes.length);
      }
  
      @Deprecated
      public static JSONReader ofJSONB(Context context, byte[] jsonbBytes) {
          return new JSONReaderJSONB(
                  context,
                  jsonbBytes,
                  0,
                  jsonbBytes.length);
      }
  
      public static JSONReader ofJSONB(byte[] jsonbBytes, Context context) {
          return new JSONReaderJSONB(
                  context,
                  jsonbBytes,
                  0,
                  jsonbBytes.length);
      }
  
      public static JSONReader ofJSONB(InputStream in, Context context) {
          return new JSONReaderJSONB(context, in);
      }
  
      public static JSONReader ofJSONB(byte[] jsonbBytes, Feature... features) {
          Context context = JSONFactory.createReadContext();
          context.config(features);
          return new JSONReaderJSONB(
                  context,
                  jsonbBytes,
                  0,
                  jsonbBytes.length);
      }
  
      public static JSONReader ofJSONB(byte[] bytes, int offset, int length) {
          return new JSONReaderJSONB(
                  JSONFactory.createReadContext(),
                  bytes,
                  offset,
                  length);
      }
  
      public static JSONReader ofJSONB(byte[] bytes, int offset, int length, Context context) {
          return new JSONReaderJSONB(context, bytes, offset, length);
      }
  
      public static JSONReader ofJSONB(byte[] bytes, int offset, int length, SymbolTable symbolTable) {
          return new JSONReaderJSONB(
                  JSONFactory.createReadContext(symbolTable),
                  bytes,
                  offset,
                  length);
      }
  
      public static JSONReader of(byte[] bytes, int offset, int length, Charset charset) {
          Context context = JSONFactory.createReadContext();
  
          if (charset == StandardCharsets.UTF_8) {
              return JSONReaderUTF8.of(bytes, offset, length, context);
          }
  
          if (charset == StandardCharsets.UTF_16) {
              return ofUTF16(bytes, offset, length, context);
          }
  
          if (charset == StandardCharsets.US_ASCII || charset == StandardCharsets.ISO_8859_1) {
              return JSONReaderASCII.of(context, null, bytes, offset, length);
          }
  
          throw new JSONException("not support charset " + charset);
      }
  
      private static JSONReader ofUTF16(byte[] bytes, int offset, int length, Context ctx) {
          return new JSONReaderUTF16(ctx, bytes, offset, length);
      }
  
      private static JSONReader ofUTF16(String str, char[] chars, int offset, int length, Context ctx) {
          return new JSONReaderUTF16(ctx, str, chars, offset, length);
      }
  
      public static JSONReader of(byte[] bytes, int offset, int length, Charset charset, Context context) {
          if (charset == StandardCharsets.UTF_8) {
              return JSONReaderUTF8.of(bytes, offset, length, context);
          }
  
          if (charset == StandardCharsets.UTF_16) {
              return ofUTF16(bytes, offset, length, context);
          }
  
          if (charset == StandardCharsets.US_ASCII || charset == StandardCharsets.ISO_8859_1) {
              return JSONReaderASCII.of(context, null, bytes, offset, length);
          }
  
          throw new JSONException("not support charset " + charset);
      }
  
      public static JSONReader of(byte[] bytes, int offset, int length) {
          return of(bytes, offset, length, StandardCharsets.UTF_8, createReadContext());
      }
  
      public static JSONReader of(byte[] bytes, int offset, int length, Context context) {
          return new JSONReaderUTF8(context, bytes, offset, length);
      }
  
      public static JSONReader of(char[] chars, int offset, int length) {
          return ofUTF16(null, chars, offset, length, createReadContext());
      }
  
      public static JSONReader of(char[] chars, int offset, int length, Context context) {
          return ofUTF16(null, chars, offset, length, context);
      }
  
      public static JSONReader of(URL url, Context context) throws IOException {
          try (InputStream is = url.openStream()) {
              return of(is, StandardCharsets.UTF_8, context);
          }
      }
  
      /**
       * Creates a JSONReader from an InputStream containing JSON data.
       *
       * @param is The InputStream containing JSON data
       * @param charset The character encoding of the JSON data
       * @return A JSONReader instance
       */
      public static JSONReader of(InputStream is, Charset charset) {
          Context context = JSONFactory.createReadContext();
          return of(is, charset, context);
      }
  
      public static JSONReader of(InputStream is, Charset charset, Context context) {
          if (is == null) {
              throw new JSONException("inputStream is null");
          }
  
          if (charset == StandardCharsets.UTF_8 || charset == null) {
              return new JSONReaderUTF8(context, is);
          }
  
          if (charset == StandardCharsets.UTF_16) {
              return new JSONReaderUTF16(context, is);
          }
  
          if (charset == StandardCharsets.US_ASCII) {
              return JSONReaderASCII.of(context, is);
          }
  
          return JSONReader.of(new InputStreamReader(is, charset), context);
      }
  
      /**
       * Creates a JSONReader from a Reader containing JSON data.
       *
       * @param is The Reader containing JSON data
       * @return A JSONReader instance
       */
      public static JSONReader of(Reader is) {
          return new JSONReaderUTF16(
                  JSONFactory.createReadContext(),
                  is
          );
      }
  
      public static JSONReader of(Reader is, Context context) {
          return new JSONReaderUTF16(
                  context,
                  is
          );
      }
  
      public static JSONReader of(ByteBuffer buffer, Charset charset) {
          Context context = JSONFactory.createReadContext();
  
          if (charset == StandardCharsets.UTF_8 || charset == null) {
              return new JSONReaderUTF8(context, buffer);
          }
  
          throw new JSONException("not support charset " + charset);
      }
  
      public static JSONReader of(ByteBuffer buffer, Charset charset, Context context) {
          if (charset == StandardCharsets.UTF_8 || charset == null) {
              return new JSONReaderUTF8(context, buffer);
          }
  
          throw new JSONException("not support charset " + charset);
      }
  
      @Deprecated
      public static JSONReader of(Context context, String str) {
          return of(str, context);
      }
  
      /**
       * Creates a JSONReader from a String containing JSON data.
       *
       * @param str The String containing JSON data
       * @return A JSONReader instance
       */
      public static JSONReader of(String str) {
          return of(str, JSONFactory.createReadContext());
      }
  
      public static JSONReader of(String str, Context context) {
          if (str == null || context == null) {
              throw new NullPointerException();
          }
  
          if (STRING_VALUE != null && STRING_CODER != null) {
              try {
                  final int LATIN1 = 0;
                  int coder = STRING_CODER.applyAsInt(str);
                  if (coder == LATIN1) {
                      byte[] bytes = STRING_VALUE.apply(str);
                      return JSONReaderASCII.of(context, str, bytes, 0, bytes.length);
                  }
              } catch (Exception e) {
                  throw new JSONException("unsafe get String.coder error");
              }
          }
  
          final int length = str.length();
          char[] chars;
          if (JVM_VERSION == 8) {
              chars = JDKUtils.getCharArray(str);
          } else {
              chars = str.toCharArray();
          }
  
          return ofUTF16(str, chars, 0, length, context);
      }
  
      /**
       * Creates a JSONReader from a substring of a String containing JSON data.
       *
       * @param str The String containing JSON data
       * @param offset The starting position of the substring
       * @param length The length of the substring
       * @return A JSONReader instance
       */
      public static JSONReader of(String str, int offset, int length) {
          return of(str, offset, length, JSONFactory.createReadContext());
      }
  
      public static JSONReader of(String str, int offset, int length, Context context) {
          if (str == null || context == null) {
              throw new NullPointerException();
          }
  
          if (STRING_VALUE != null && STRING_CODER != null) {
              try {
                  final int LATIN1 = 0;
                  int coder = STRING_CODER.applyAsInt(str);
                  if (coder == LATIN1) {
                      byte[] bytes = STRING_VALUE.apply(str);
                      return JSONReaderASCII.of(context, str, bytes, offset, length);
                  }
              } catch (Exception e) {
                  throw new JSONException("unsafe get String.coder error");
              }
          }
  
          char[] chars;
          if (JVM_VERSION == 8) {
              chars = JDKUtils.getCharArray(str);
          } else {
              chars = str.toCharArray();
          }
  
          return ofUTF16(str, chars, offset, length, context);
      }
  
      final void bigInt(char[] chars, int off, int len) {
          int cursor = off, numDigits;
  
          numDigits = len - cursor;
          if (scale > 0) {
              numDigits--;
          }
          if (numDigits > 38) {
              throw new JSONException("number too large : " + new String(chars, off, numDigits));
          }
  
          // Process first (potentially short) digit group
          int firstGroupLen = numDigits % 9;
          if (firstGroupLen == 0) {
              firstGroupLen = 9;
          }
  
          {
              int start = cursor;
              int end = cursor += firstGroupLen;
  
              char c = chars[start++];
              if (c == '.') {
                  c = chars[start++];
                  cursor++;
  //                    end++;
              }
  
              int result = c - '0';
  
              for (int index = start; index < end; index++) {
                  c = chars[index];
                  if (c == '.') {
                      c = chars[++index];
                      cursor++;
                      if (end < len) {
                          end++;
                      }
                  }
  
                  int nextVal = c - '0';
                  result = 10 * result + nextVal;
              }
              mag3 = result;
          }
  
          // Process remaining digit groups
          while (cursor < len) {
              int groupVal;
              {
                  int start = cursor;
                  int end = cursor += 9;
  
                  char c = chars[start++];
                  if (c == '.') {
                      c = chars[start++];
                      cursor++;
                      end++;
                  }
  
                  int result = c - '0';
  
                  for (int index = start; index < end; index++) {
                      c = chars[index];
                      if (c == '.') {
                          c = chars[++index];
                          cursor++;
                          end++;
                      }
  
                      int nextVal = c - '0';
                      result = 10 * result + nextVal;
                  }
                  groupVal = result;
              }
  
              // destructiveMulAdd
              long ylong = 1000000000 & 0XFFFFFFFFL;
  
              long product;
              long carry = 0;
              for (int i = 3; i >= 0; i--) {
                  switch (i) {
                      case 0:
                          product = ylong * (mag0 & 0XFFFFFFFFL) + carry;
                          mag0 = (int) product;
                          break;
                      case 1:
                          product = ylong * (mag1 & 0XFFFFFFFFL) + carry;
                          mag1 = (int) product;
                          break;
                      case 2:
                          product = ylong * (mag2 & 0XFFFFFFFFL) + carry;
                          mag2 = (int) product;
                          break;
                      case 3:
                          product = ylong * (mag3 & 0XFFFFFFFFL) + carry;
                          mag3 = (int) product;
                          break;
                      default:
                          throw new ArithmeticException("BigInteger would overflow supported range");
                  }
                  carry = product >>> 32;
              }
  
              long zlong = groupVal & 0XFFFFFFFFL;
              long sum = (mag3 & 0XFFFFFFFFL) + zlong;
              mag3 = (int) sum;
  
              // Perform the addition
              carry = sum >>> 32;
              for (int i = 2; i >= 0; i--) {
                  switch (i) {
                      case 0:
                          sum = (mag0 & 0XFFFFFFFFL) + carry;
                          mag0 = (int) sum;
                          break;
                      case 1:
                          sum = (mag1 & 0XFFFFFFFFL) + carry;
                          mag1 = (int) sum;
                          break;
                      case 2:
                          sum = (mag2 & 0XFFFFFFFFL) + carry;
                          mag2 = (int) sum;
                          break;
                      case 3:
                          sum = (mag3 & 0XFFFFFFFFL) + carry;
                          mag3 = (int) sum;
                          break;
                      default:
                          throw new ArithmeticException("BigInteger would overflow supported range");
                  }
                  carry = sum >>> 32;
              }
          }
      }
  
      final void bigInt(byte[] chars, int off, int len) {
          int cursor = off, numDigits;
  
          numDigits = len - cursor;
          if (scale > 0) {
              numDigits--;
          }
          if (numDigits > 38) {
              throw new JSONException("number too large : " + new String(chars, off, numDigits));
          }
  
          // Process first (potentially short) digit group
          int firstGroupLen = numDigits % 9;
          if (firstGroupLen == 0) {
              firstGroupLen = 9;
          }
  
          {
              int start = cursor;
              int end = cursor += firstGroupLen;
  
              char c = (char) chars[start++];
              if (c == '.') {
                  c = (char) chars[start++];
                  cursor++;
  //                    end++;
              }
  
              int result = c - '0';
  
              for (int index = start; index < end; index++) {
                  c = (char) chars[index];
                  if (c == '.') {
                      c = (char) chars[++index];
                      cursor++;
                      if (end < len) {
                          end++;
                      }
                  }
  
                  int nextVal = c - '0';
                  result = 10 * result + nextVal;
              }
              mag3 = result;
          }
  
          // Process remaining digit groups
          while (cursor < len) {
              int groupVal;
              {
                  int start = cursor;
                  int end = cursor += 9;
  
                  char c = (char) chars[start++];
                  if (c == '.') {
                      c = (char) chars[start++];
                      cursor++;
                      end++;
                  }
  
                  int result = c - '0';
  
                  for (int index = start; index < end; index++) {
                      c = (char) chars[index];
                      if (c == '.') {
                          c = (char) chars[++index];
                          cursor++;
                          end++;
                      }
  
                      int nextVal = c - '0';
                      result = 10 * result + nextVal;
                  }
                  groupVal = result;
              }
  
              // destructiveMulAdd
              long ylong = 1000000000 & 0XFFFFFFFFL;
              long zlong = groupVal & 0XFFFFFFFFL;
  
              long product;
              long carry = 0;
              for (int i = 3; i >= 0; i--) {
                  switch (i) {
                      case 0:
                          product = ylong * (mag0 & 0XFFFFFFFFL) + carry;
                          mag0 = (int) product;
                          break;
                      case 1:
                          product = ylong * (mag1 & 0XFFFFFFFFL) + carry;
                          mag1 = (int) product;
                          break;
                      case 2:
                          product = ylong * (mag2 & 0XFFFFFFFFL) + carry;
                          mag2 = (int) product;
                          break;
                      case 3:
                          product = ylong * (mag3 & 0XFFFFFFFFL) + carry;
                          mag3 = (int) product;
                          break;
                      default:
                          throw new ArithmeticException("BigInteger would overflow supported range");
                  }
                  carry = product >>> 32;
              }
  
              long sum = (mag3 & 0XFFFFFFFFL) + zlong;
              mag3 = (int) sum;
  
              // Perform the addition
              carry = sum >>> 32;
              for (int i = 2; i >= 0; i--) {
                  switch (i) {
                      case 0:
                          sum = (mag0 & 0XFFFFFFFFL) + carry;
                          mag0 = (int) sum;
                          break;
                      case 1:
                          sum = (mag1 & 0XFFFFFFFFL) + carry;
                          mag1 = (int) sum;
                          break;
                      case 2:
                          sum = (mag2 & 0XFFFFFFFFL) + carry;
                          mag2 = (int) sum;
                          break;
                      case 3:
                          sum = (mag3 & 0XFFFFFFFFL) + carry;
                          mag3 = (int) sum;
                          break;
                      default:
                          throw new ArithmeticException("BigInteger would overflow supported range");
                  }
                  carry = sum >>> 32;
              }
          }
      }
  
      public interface AutoTypeBeforeHandler
              extends Filter {
          default Class<?> apply(long typeNameHash, Class<?> expectClass, long features) {
              return null;
          }
  
          Class<?> apply(String typeName, Class<?> expectClass, long features);
      }
  
      public static AutoTypeBeforeHandler autoTypeFilter(String... names) {
          return new ContextAutoTypeBeforeHandler(names);
      }
  
      public static AutoTypeBeforeHandler autoTypeFilter(boolean includeBasic, String... names) {
          return new ContextAutoTypeBeforeHandler(includeBasic, names);
      }
  
      public static AutoTypeBeforeHandler autoTypeFilter(Class... types) {
          return new ContextAutoTypeBeforeHandler(types);
      }
  
      public static AutoTypeBeforeHandler autoTypeFilter(boolean includeBasic, Class... types) {
          return new ContextAutoTypeBeforeHandler(includeBasic, types);
      }
  
      /**
       * Context holds the configuration and state information for JSON reading operations.
       * It controls various aspects of the deserialization process including formatting,
       * features, providers, and other settings that affect how JSON data is parsed and
       * converted to Java objects.
       *
       * <p>The Context class is responsible for:</p>
       * <ul>
       *   <li>Managing reader features that control deserialization behavior</li>
       *   <li>Handling date/time formatting and timezone settings</li>
       *   <li>Providing object and array suppliers for custom collection creation</li>
       *   <li>Managing auto-type handlers for security and customization</li>
       *   <li>Storing parser configuration such as max nesting level and buffer size</li>
       * </ul>
       *
       * <p>Context instances can be created in several ways:</p>
       * <pre>
       * // Using default configuration
       * JSONReader.Context context = new JSONReader.Context();
       *
       * // With specific features enabled
       * JSONReader.Context context = new JSONReader.Context(
       *     JSONReader.Feature.FieldBased,
       *     JSONReader.Feature.TrimString
       * );
       *
       * // With custom date format
       * JSONReader.Context context = new JSONReader.Context("yyyy-MM-dd HH:mm:ss");
       *
       * // With custom provider and features
       * ObjectReaderProvider provider = JSONFactory.getDefaultObjectReaderProvider();
       * JSONReader.Context context = new JSONReader.Context(provider,
       *     JSONReader.Feature.FieldBased
       * );
       * </pre>
       *
       * <p>Once created, a Context can be configured further:</p>
       * <pre>
       * context.setZoneId(ZoneId.of("UTC"));
       * context.setLocale(Locale.US);
       * context.setMaxLevel(1000);
       * context.setBufferSize(64 * 1024);
       * context.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
       * </pre>
       *
       * <p>Context instances are typically used when creating JSONReader instances:</p>
       * <pre>
       * JSONReader.Context context = new JSONReader.Context();
       * context.config(JSONReader.Feature.FieldBased);
       *
       * try (JSONReader reader = JSONReader.of(json, context)) {
       *     MyObject obj = reader.read(MyObject.class);
       * }
       * </pre>
       *
       * <p>Note that Context instances are not thread-safe and should not be shared
       * between multiple concurrent reading operations. Each JSONReader should have
       * its own Context instance or use the default context provided by factory methods.</p>
       *
       * @see JSONReader
       * @see JSONReader.Feature
       * @see ObjectReaderProvider
       * @since 2.0.0
       */
      public static final class Context {
          String dateFormat;
          boolean formatComplex;
          boolean formatyyyyMMddhhmmss19;
          boolean formatyyyyMMddhhmmssT19;
          boolean yyyyMMddhhmm16;
          boolean formatyyyyMMdd8;
          boolean formatMillis;
          boolean formatUnixTime;
          boolean formatISO8601;
          boolean formatHasDay;
          boolean formatHasHour;
          boolean useSimpleFormatter;
          int maxLevel = 2048;
          int bufferSize = 1024 * 512;
          DateTimeFormatter dateFormatter;
          ZoneId zoneId;
          long features;
          Locale locale;
          TimeZone timeZone;
          Supplier<Map> objectSupplier;
          Supplier<List> arraySupplier;
          AutoTypeBeforeHandler autoTypeBeforeHandler;
          ExtraProcessor extraProcessor;
  
          final ObjectReaderProvider provider;
          final SymbolTable symbolTable;
  
          /**
           * Creates a new Context with the specified object reader provider.
           *
           * @param provider the object reader provider to use
           */
          public Context(ObjectReaderProvider provider) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
          }
  
          /**
           * Creates a new Context with the specified object reader provider and features.
           *
           * @param provider the object reader provider to use
           * @param features the initial features bitmask
           */
          public Context(ObjectReaderProvider provider, long features) {
              this.features = features;
              this.provider = provider;
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
          }
  
          /**
           * Creates a new Context with the specified features.
           *
           * @param features the features to enable
           */
          public Context(Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = JSONFactory.getDefaultObjectReaderProvider();
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Creates a new Context with the specified date format and features.
           *
           * @param dateFormat the date format pattern to use
           * @param features the features to enable
           */
          public Context(String dateFormat, Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = JSONFactory.getDefaultObjectReaderProvider();
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
              setDateFormat(dateFormat);
          }
  
          /**
           * Creates a new Context with the specified object reader provider and features.
           *
           * @param provider the object reader provider to use
           * @param features the features to enable
           */
          public Context(ObjectReaderProvider provider, Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Creates a new Context with the specified object reader provider, filter, and features.
           *
           * @param provider the object reader provider to use
           * @param filter the filter to configure
           * @param features the features to enable
           */
          public Context(ObjectReaderProvider provider, Filter filter, Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.objectSupplier = JSONFactory.defaultObjectSupplier;
              this.arraySupplier = JSONFactory.defaultArraySupplier;
              this.symbolTable = null;
              this.zoneId = defaultReaderZoneId;
  
              config(filter);
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Creates a new Context with the specified object reader provider and symbol table.
           *
           * @param provider the object reader provider to use
           * @param symbolTable the symbol table to use
           */
          public Context(ObjectReaderProvider provider, SymbolTable symbolTable) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.symbolTable = symbolTable;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
          }
  
          /**
           * Creates a new Context with the specified object reader provider, symbol table, and features.
           *
           * @param provider the object reader provider to use
           * @param symbolTable the symbol table to use
           * @param features the features to enable
           */
          public Context(ObjectReaderProvider provider, SymbolTable symbolTable, Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.symbolTable = symbolTable;
              this.zoneId = defaultReaderZoneId;
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Creates a new Context with the specified object reader provider, symbol table, filters, and features.
           *
           * @param provider the object reader provider to use
           * @param symbolTable the symbol table to use
           * @param filters the filters to configure
           * @param features the features to enable
           */
          public Context(ObjectReaderProvider provider, SymbolTable symbolTable, Filter[] filters, Feature... features) {
              this.features = defaultReaderFeatures;
              this.provider = provider;
              this.symbolTable = symbolTable;
              this.zoneId = defaultReaderZoneId;
  
              config(filters);
  
              String format = defaultReaderFormat;
              if (format != null) {
                  setDateFormat(format);
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Checks if the context is configured to format Unix time.
           *
           * @return true if Unix time formatting is enabled, false otherwise
           */
          public boolean isFormatUnixTime() {
              return formatUnixTime;
          }
  
          /**
           * Checks if the context is configured to format dates in yyyyMMddHHmmss format (19 characters).
           *
           * @return true if this format is enabled, false otherwise
           */
          public boolean isFormatyyyyMMddhhmmss19() {
              return formatyyyyMMddhhmmss19;
          }
  
          /**
           * Checks if the context is configured to format dates in yyyy-MM-dd'T'HH:mm:ss format (19 characters).
           *
           * @return true if this format is enabled, false otherwise
           */
          public boolean isFormatyyyyMMddhhmmssT19() {
              return formatyyyyMMddhhmmssT19;
          }
  
          /**
           * Checks if the context is configured to format dates in yyyyMMdd format (8 characters).
           *
           * @return true if this format is enabled, false otherwise
           */
          public boolean isFormatyyyyMMdd8() {
              return formatyyyyMMdd8;
          }
  
          /**
           * Checks if the context is configured to format milliseconds.
           *
           * @return true if millisecond formatting is enabled, false otherwise
           */
          public boolean isFormatMillis() {
              return formatMillis;
          }
  
          /**
           * Checks if the context is configured to format dates in ISO8601 format.
           *
           * @return true if ISO8601 formatting is enabled, false otherwise
           */
          public boolean isFormatISO8601() {
              return formatISO8601;
          }
  
          /**
           * Checks if the context is configured to format dates with hour information.
           *
           * @return true if hour formatting is enabled, false otherwise
           */
          public boolean isFormatHasHour() {
              return formatHasHour;
          }
  
          /**
           * Gets an ObjectReader for the specified type.
           *
           * @param type The type for which to get an ObjectReader
           * @return An ObjectReader for the specified type
           */
          public ObjectReader getObjectReader(Type type) {
              boolean fieldBased = (features & Feature.FieldBased.mask) != 0;
              return provider.getObjectReader(type, fieldBased);
          }
  
          /**
           * Gets the ObjectReaderProvider used by this context.
           *
           * @return The ObjectReaderProvider
           */
          public ObjectReaderProvider getProvider() {
              return provider;
          }
  
          /**
           * Gets an ObjectReader for the specified type hash code.
           *
           * @param hashCode The hash code of the type
           * @return An ObjectReader for the specified type hash code, or null if not found
           */
          public ObjectReader getObjectReaderAutoType(long hashCode) {
              return provider.getObjectReader(hashCode);
          }
  
          /**
           * Gets an ObjectReader for the specified type name and expected class.
           *
           * @param typeName The type name
           * @param expectClass The expected class
           * @return An ObjectReader for the specified type, or null if not found
           */
          public ObjectReader getObjectReaderAutoType(String typeName, Class expectClass) {
              if (autoTypeBeforeHandler != null) {
                  Class<?> autoTypeClass = autoTypeBeforeHandler.apply(typeName, expectClass, features);
                  if (autoTypeClass != null) {
                      boolean fieldBased = (features & Feature.FieldBased.mask) != 0;
                      return provider.getObjectReader(autoTypeClass, fieldBased);
                  }
              }
  
              return provider.getObjectReader(typeName, expectClass, features);
          }
  
          /**
           * Gets the AutoTypeBeforeHandler configured for this context.
           *
           * @return The AutoTypeBeforeHandler, or null if not configured
           */
          public AutoTypeBeforeHandler getContextAutoTypeBeforeHandler() {
              return autoTypeBeforeHandler;
          }
  
          /**
           * Gets an ObjectReader for the specified type name, expected class, and additional features.
           *
           * @param typeName The type name
           * @param expectClass The expected class
           * @param features Additional features to consider
           * @return An ObjectReader for the specified type, or null if not found
           */
          public ObjectReader getObjectReaderAutoType(String typeName, Class expectClass, long features) {
              if (autoTypeBeforeHandler != null) {
                  Class<?> autoTypeClass = autoTypeBeforeHandler.apply(typeName, expectClass, features);
                  if (autoTypeClass != null) {
                      boolean fieldBased = (features & Feature.FieldBased.mask) != 0;
                      return provider.getObjectReader(autoTypeClass, fieldBased);
                  }
              }
  
              return provider.getObjectReader(typeName, expectClass, this.features | features);
          }
  
          /**
           * Gets the ExtraProcessor configured for this context.
           *
           * @return The ExtraProcessor, or null if not configured
           */
          public ExtraProcessor getExtraProcessor() {
              return extraProcessor;
          }
  
          /**
           * Sets the ExtraProcessor for this context.
           *
           * @param extraProcessor The ExtraProcessor to set
           */
          public void setExtraProcessor(ExtraProcessor extraProcessor) {
              this.extraProcessor = extraProcessor;
          }
  
          /**
           * Gets the object supplier configured for this context.
           *
           * @return The object supplier
           */
          public Supplier<Map> getObjectSupplier() {
              return objectSupplier;
          }
  
          /**
           * Sets the object supplier for this context.
           *
           * @param objectSupplier The object supplier to set
           */
          public void setObjectSupplier(Supplier<Map> objectSupplier) {
              this.objectSupplier = objectSupplier;
          }
  
          /**
           * Gets the array supplier configured for this context.
           *
           * @return The array supplier
           */
          public Supplier<List> getArraySupplier() {
              return arraySupplier;
          }
  
          /**
           * Sets the array supplier for this context.
           *
           * @param arraySupplier The array supplier to set
           */
          public void setArraySupplier(Supplier<List> arraySupplier) {
              this.arraySupplier = arraySupplier;
          }
  
          /**
           * Gets the date formatter configured for this context.
           *
           * @return The DateTimeFormatter, or null if not configured
           */
          public DateTimeFormatter getDateFormatter() {
              if (dateFormatter == null && dateFormat != null && !formatMillis && !formatISO8601 && !formatUnixTime) {
                  dateFormatter = locale == null
                          ? DateTimeFormatter.ofPattern(dateFormat)
                          : DateTimeFormatter.ofPattern(dateFormat, locale);
              }
              return dateFormatter;
          }
  
          /**
           * Sets the date formatter for this context.
           *
           * @param dateFormatter The DateTimeFormatter to set
           */
          public void setDateFormatter(DateTimeFormatter dateFormatter) {
              this.dateFormatter = dateFormatter;
          }
  
          /**
           * Gets the date format pattern configured for this context.
           *
           * @return The date format pattern, or null if not set
           */
          public String getDateFormat() {
              return dateFormat;
          }
  
          /**
           * Sets the date format pattern for this context.
           *
           * @param format The date format pattern to set
           */
          public void setDateFormat(String format) {
              if (format != null) {
                  if (format.isEmpty()) {
                      format = null;
                  }
              }
  
              boolean formatUnixTime = false, formatISO8601 = false, formatMillis = false, hasDay = false, hasHour = false, useSimpleFormatter = false;
              if (format != null) {
                  switch (format) {
                      case "unixtime":
                          formatUnixTime = true;
                          break;
                      case "iso8601":
                          formatISO8601 = true;
                          break;
                      case "millis":
                          formatMillis = true;
                          break;
                      case "yyyyMMddHHmmssSSSZ":
                          useSimpleFormatter = true;
                          break;
                      case "yyyy-MM-dd HH:mm:ss":
                      case "yyyy-MM-ddTHH:mm:ss":
                          formatyyyyMMddhhmmss19 = true;
                          hasDay = true;
                          hasHour = true;
                          break;
                      case "yyyy-MM-dd'T'HH:mm:ss":
                          formatyyyyMMddhhmmssT19 = true;
                          hasDay = true;
                          hasHour = true;
                          break;
                      case "yyyyMMdd":
                      case "yyyy-MM-dd":
                          formatyyyyMMdd8 = true;
                          hasDay = true;
                          hasHour = false;
                          break;
                      case "yyyy-MM-dd HH:mm":
                          yyyyMMddhhmm16 = true;
                          break;
                      default:
                          hasDay = format.indexOf('d') != -1;
                          hasHour = format.indexOf('H') != -1
                                  || format.indexOf('h') != -1
                                  || format.indexOf('K') != -1
                                  || format.indexOf('k') != -1;
                          break;
                  }
  
                  this.formatComplex = !(formatyyyyMMddhhmmss19 | formatyyyyMMddhhmmssT19 | formatyyyyMMdd8 | formatISO8601);
                  // this.yyyyMMddhhmm16 = "yyyy-MM-dd HH:mm".equals(format);
              }
  
              if (!Objects.equals(this.dateFormat, format)) {
                  this.dateFormatter = null;
              }
              this.dateFormat = format;
              this.formatUnixTime = formatUnixTime;
              this.formatMillis = formatMillis;
              this.formatISO8601 = formatISO8601;
  
              this.formatHasDay = hasDay;
              this.formatHasHour = hasHour;
              this.useSimpleFormatter = useSimpleFormatter;
          }
  
          /**
           * Gets the ZoneId configured for this context.
           *
           * @return The ZoneId
           */
          public ZoneId getZoneId() {
              if (zoneId == null) {
                  zoneId = DateUtils.DEFAULT_ZONE_ID;
              }
              return zoneId;
          }
  
          /**
           * Gets the features bitmask for this context.
           *
           * @return The features bitmask
           */
          public long getFeatures() {
              return features;
          }
  
          /**
           * Sets the features bitmask for this context.
           *
           * @param features The features bitmask to set
           * @since 2.0.51
           */
          public void setFeatures(long features) {
              this.features = features;
          }
  
          /**
           * Sets the ZoneId for this context.
           *
           * @param zoneId The ZoneId to set
           */
          public void setZoneId(ZoneId zoneId) {
              this.zoneId = zoneId;
          }
  
          /**
           * Gets the maximum nesting level allowed for this context.
           *
           * @return The maximum nesting level
           */
          public int getMaxLevel() {
              return maxLevel;
          }
  
          /**
           * Sets the maximum nesting level allowed for this context.
           *
           * @param maxLevel The maximum nesting level to set
           */
          public void setMaxLevel(int maxLevel) {
              this.maxLevel = maxLevel;
          }
  
          /**
           * Gets the buffer size configured for this context.
           *
           * @return The buffer size in bytes
           */
          public int getBufferSize() {
              return bufferSize;
          }
  
          /**
           * Sets the buffer size for this context.
           *
           * @param bufferSize The buffer size to set in bytes
           * @return This Context instance for method chaining
           * @throws IllegalArgumentException if bufferSize is negative
           */
          public Context setBufferSize(int bufferSize) {
              if (bufferSize < 0) {
                  throw new IllegalArgumentException("buffer size can not be less than zero");
              }
              this.bufferSize = bufferSize;
              return this;
          }
  
          /**
           * Gets the Locale configured for this context.
           *
           * @return The Locale
           */
          public Locale getLocale() {
              return locale;
          }
  
          /**
           * Sets the Locale for this context.
           *
           * @param locale The Locale to set
           */
          public void setLocale(Locale locale) {
              this.locale = locale;
          }
  
          /**
           * Gets the TimeZone configured for this context.
           *
           * @return The TimeZone
           */
          public TimeZone getTimeZone() {
              return timeZone;
          }
  
          /**
           * Sets the TimeZone for this context.
           *
           * @param timeZone The TimeZone to set
           */
          public void setTimeZone(TimeZone timeZone) {
              this.timeZone = timeZone;
          }
  
          /**
           * Configures features for this context.
           *
           * @param features The features to enable
           */
          public void config(Feature... features) {
              for (int i = 0; i < features.length; i++) {
                  this.features |= features[i].mask;
              }
          }
  
          /**
           * Configures a filter and features for this context.
           *
           * @param filter The filter to configure
           * @param features The features to enable
           */
          public void config(Filter filter, Feature... features) {
              if (filter instanceof AutoTypeBeforeHandler) {
                  autoTypeBeforeHandler = (AutoTypeBeforeHandler) filter;
              }
  
              if (filter instanceof ExtraProcessor) {
                  extraProcessor = (ExtraProcessor) filter;
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Configures a filter for this context.
           *
           * @param filter The filter to configure
           */
          public void config(Filter filter) {
              if (filter instanceof AutoTypeBeforeHandler) {
                  autoTypeBeforeHandler = (AutoTypeBeforeHandler) filter;
              }
  
              if (filter instanceof ExtraProcessor) {
                  extraProcessor = (ExtraProcessor) filter;
              }
          }
  
          /**
           * Configures filters and features for this context.
           *
           * @param filters The filters to configure
           * @param features The features to enable
           */
          public void config(Filter[] filters, Feature... features) {
              for (Filter filter : filters) {
                  if (filter instanceof AutoTypeBeforeHandler) {
                      autoTypeBeforeHandler = (AutoTypeBeforeHandler) filter;
                  }
  
                  if (filter instanceof ExtraProcessor) {
                      extraProcessor = (ExtraProcessor) filter;
                  }
              }
  
              for (Feature feature : features) {
                  this.features |= feature.mask;
              }
          }
  
          /**
           * Configures filters for this context.
           *
           * @param filters The filters to configure
           */
          public void config(Filter[] filters) {
              for (Filter filter : filters) {
                  if (filter instanceof AutoTypeBeforeHandler) {
                      autoTypeBeforeHandler = (AutoTypeBeforeHandler) filter;
                  }
  
                  if (filter instanceof ExtraProcessor) {
                      extraProcessor = (ExtraProcessor) filter;
                  }
              }
          }
  
          /**
           * Checks if the specified feature is enabled in this context.
           *
           * @param feature The feature to check
           * @return true if the feature is enabled, false otherwise
           */
          public boolean isEnabled(Feature feature) {
              return (this.features & feature.mask) != 0;
          }
  
          /**
           * Configures a specific feature for this context.
           *
           * @param feature The feature to configure
           * @param state true to enable the feature, false to disable it
           */
          public void config(Feature feature, boolean state) {
              if (state) {
                  features |= feature.mask;
              } else {
                  features &= ~feature.mask;
              }
          }
      }
  
      protected static final long MASK_FIELD_BASED = 1L;
      protected static final long MASK_IGNORE_NONE_SERIALIZABLE = 1L << 1;
      protected static final long MASK_ERROR_ON_NONE_SERIALIZABLE = 1L << 2;
      protected static final long MASK_SUPPORT_ARRAY_TO_BEAN = 1L << 3;
      protected static final long MASK_INIT_STRING_FIELD_AS_EMPTY = 1L << 4;
      protected static final long MASK_SUPPORT_AUTO_TYPE = 1L << 5;
      protected static final long MASK_SUPPORT_SMART_MATCH = 1L << 6;
      protected static final long MASK_TRIM_STRING = 1L << 14;
      protected static final long MASK_ALLOW_UN_QUOTED_FIELD_NAMES = 1L << 17;
      protected static final long MASK_EMPTY_STRING_AS_NULL = 1L << 27;
      protected static final long MASK_DISABLE_SINGLE_QUOTE = 1L << 31L;
      protected static final long MASK_DISABLE_REFERENCE_DETECT = 1L << 33;
  
      /**
       * Feature is used to control the behavior of JSON reading and parsing in FASTJSON2.
       * Each feature represents a specific configuration option that can be enabled or disabled
       * to customize how JSON data is processed during deserialization.
       *
       * <p>Features can be enabled in several ways:
       * <ul>
       *   <li>Using factory methods like {@link JSONReader#of(String, Context)} with {@link JSONFactory#createReadContext(JSONReader.Feature...)}</li>
       *   <li>Using {@link Context#config(Feature...)} method</li>
       *   <li>Using {@link JSONFactory#getDefaultReaderFeatures()} for global configuration</li>
       * </ul>
       *
       *
       * <p>Example usage:
       * <pre>
       * // Enable FieldBased feature for this reader only
       * try (JSONReader reader = JSONReader.of(json, JSONReader.Feature.FieldBased)) {
       *     MyObject obj = reader.read(MyObject.class);
       * }
       *
       * // Enable multiple features
       * try (JSONReader reader = JSONReader.of(json,
       *         JSONReader.Feature.FieldBased,
       *         JSONReader.Feature.TrimString)) {
       *     MyObject obj = reader.read(MyObject.class);
       * }
       *
       * // Using context configuration
       * JSONReader.Context context = new JSONReader.Context();
       * context.config(JSONReader.Feature.FieldBased);
       * try (JSONReader reader = JSONReader.of(json, context)) {
       *     MyObject obj = reader.read(MyObject.class);
       * }
       * </pre>
       *
       *
       * <p>Features are implemented as bitmask flags for efficient storage and checking.
       * Each feature has a unique mask value that is used internally to determine
       * whether the feature is enabled in a given configuration.</p>
       *
       * @see JSONReader.Context
       * @see JSONFactory
       * @since 2.0.0
       */
      public enum Feature {
          /**
           * Feature that determines whether to use field-based deserialization instead of getter/setter-based deserialization.
           * When enabled, fields are directly accessed rather than using getter and setter methods.
           * This can improve performance but may bypass validation logic in setters.
           *
           * <p>By default, this feature is disabled, meaning that getter/setter-based deserialization is used.</p>
           *
           * @since 2.0.0
           */
          FieldBased(MASK_FIELD_BASED),
  
          /**
           * Feature that determines whether to ignore non-serializable classes during deserialization.
           * When enabled, classes that do not implement {@link java.io.Serializable} will be ignored
           * rather than causing an exception to be thrown.
           *
           * <p>By default, this feature is disabled, meaning that non-serializable classes are not ignored.</p>
           *
           * @since 2.0.0
           */
          IgnoreNoneSerializable(MASK_IGNORE_NONE_SERIALIZABLE),
  
          /**
           * Feature that determines whether to throw an exception when encountering non-serializable classes
           * during deserialization.
           * When enabled, an exception will be thrown if a class does not implement {@link java.io.Serializable}.
           *
           * <p>By default, this feature is disabled, meaning that no exception is thrown for non-serializable classes.</p>
           *
           * @since 2.0.14
           */
          ErrorOnNoneSerializable(MASK_ERROR_ON_NONE_SERIALIZABLE),
  
          /**
           * Feature that determines whether to support deserializing JSON arrays into Java beans.
           * When enabled, JSON arrays can be mapped to Java bean properties, with each array element
           * corresponding to a property in the bean.
           *
           * <p>By default, this feature is disabled, meaning that array-to-bean conversion is not supported.</p>
           *
           * @since 2.0.0
           */
          SupportArrayToBean(MASK_SUPPORT_ARRAY_TO_BEAN),
  
          /**
           * Feature that determines whether to initialize string fields as empty strings instead of null values.
           * When enabled, string fields will be initialized with empty strings ("") rather than null.
           *
           * <p>By default, this feature is disabled, meaning that string fields are initialized with null values.</p>
           *
           * @since 2.0.0
           */
          InitStringFieldAsEmpty(MASK_INIT_STRING_FIELD_AS_EMPTY),
  
          /**
           * Feature that enables automatic type detection during deserialization.
           * It is not safe to explicitly turn on autoType, it is recommended to use AutoTypeBeforeHandler.
           *
           * <p>This feature is deprecated and should not be used in production code.</p>
           *
           * @deprecated It is not safe to explicitly turn on autoType, it is recommended to use AutoTypeBeforeHandler
           * @since 2.0.0
           */
          @Deprecated
          SupportAutoType(MASK_SUPPORT_AUTO_TYPE),
  
          /**
           * Feature that enables smart matching of field names during deserialization.
           * When enabled, field names in JSON can be matched to Java bean properties in a case-insensitive
           * manner or with other smart matching rules.
           *
           * <p>By default, this feature is disabled, meaning that exact field name matching is required.</p>
           *
           * @since 2.0.0
           */
          SupportSmartMatch(MASK_SUPPORT_SMART_MATCH),
  
          /**
           * Feature that determines whether to use native Java objects (HashMap, ArrayList) instead of
           * FASTJSON's JSONObject and JSONArray during deserialization.
           * When enabled, standard Java collections are used rather than FASTJSON-specific ones.
           *
           * <p>By default, this feature is disabled, meaning that FASTJSON's JSONObject and JSONArray are used.</p>
           *
           * @since 2.0.0
           */
          UseNativeObject(1 << 7),
  
          /**
           * Feature that enables support for Class.forName() during deserialization.
           * When enabled, the deserializer can use Class.forName() to load classes by name.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.0
           */
          SupportClassForName(1 << 8),
  
          /**
           * Feature that determines whether to ignore null values when setting properties.
           * When enabled, null values in JSON will not be set on Java bean properties,
           * preserving their default values.
           *
           * <p>By default, this feature is disabled, meaning that null values are set on properties.</p>
           *
           * @since 2.0.0
           */
          IgnoreSetNullValue(1 << 9),
  
          /**
           * Feature that determines whether to use default constructors as much as possible during deserialization.
           * When enabled, the deserializer will prefer to use default (no-argument) constructors when creating objects.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.0
           */
          UseDefaultConstructorAsPossible(1 << 10),
  
          /**
           * Feature that determines whether to deserialize floating-point numbers as BigDecimal
           * when the target type is float.
           * When enabled, float values will be represented with higher precision using BigDecimal.
           *
           * <p>By default, this feature is disabled, meaning that standard float precision is used.</p>
           *
           * @since 2.0.0
           */
          UseBigDecimalForFloats(1 << 11),
  
          /**
           * Feature that determines whether to deserialize floating-point numbers as BigDecimal
           * when the target type is double.
           * When enabled, double values will be represented with higher precision using BigDecimal.
           *
           * <p>By default, this feature is disabled, meaning that standard double precision is used.</p>
           *
           * @since 2.0.0
           */
          UseBigDecimalForDoubles(1 << 12),
  
          /**
           * Feature that determines whether to throw an exception when an enum value in JSON does not
           * match any of the defined enum constants.
           * When enabled, an exception will be thrown if an unknown enum value is encountered.
           *
           * <p>By default, this feature is disabled, meaning that unknown enum values are ignored.</p>
           *
           * @since 2.0.0
           */
          ErrorOnEnumNotMatch(1 << 13),
  
          /**
           * Feature that determines whether to trim whitespace from string values during deserialization.
           * When enabled, leading and trailing whitespace will be removed from string values.
           *
           * <p>By default, this feature is disabled, meaning that string values are not trimmed.</p>
           *
           * @since 2.0.0
           */
          TrimString(MASK_TRIM_STRING),
  
          /**
           * Feature that determines whether to throw an exception when autoType is not supported.
           * When enabled, an exception will be thrown if autoType functionality is not available.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.0
           */
          ErrorOnNotSupportAutoType(1 << 15),
  
          /**
           * Feature that determines how to handle duplicate keys in JSON objects.
           * When enabled, duplicate keys will be stored as arrays rather than overwriting previous values.
           *
           * <p>By default, this feature is disabled, meaning that duplicate keys overwrite previous values.</p>
           *
           * @since 2.0.0
           */
          DuplicateKeyValueAsArray(1 << 16),
  
          /**
           * Feature that determines whether to allow unquoted field names in JSON.
           * When enabled, field names in JSON objects do not need to be enclosed in quotes.
           *
           * <p>By default, this feature is disabled, meaning that field names must be quoted.</p>
           *
           * @since 2.0.0
           */
          AllowUnQuotedFieldNames(MASK_ALLOW_UN_QUOTED_FIELD_NAMES),
  
          /**
           * Feature that determines whether to treat non-string keys as strings during deserialization.
           * When enabled, keys in JSON objects that are not strings will be converted to string representation.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.0
           */
          NonStringKeyAsString(1 << 18),
  
          /**
           * Feature that determines whether to treat Base64-encoded strings as byte arrays during deserialization.
           * When enabled, strings that contain Base64-encoded data will be automatically decoded to byte arrays.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.13
           */
          Base64StringAsByteArray(1 << 19),
  
          /**
           * Feature that determines whether to ignore checking for resource cleanup.
           * When enabled, the deserializer will not perform checks to ensure proper resource cleanup.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.16
           */
          IgnoreCheckClose(1 << 20),
  
          /**
           * Feature that determines whether to throw an exception when null values are encountered
           * for primitive types during deserialization.
           * When enabled, an exception will be thrown if a null value is found for a primitive type field.
           *
           * <p>By default, this feature is disabled, meaning that primitive types are initialized with default values.</p>
           *
           * @since 2.0.20
           */
          ErrorOnNullForPrimitives(1 << 21),
  
          /**
           * Feature that determines whether to return null on error during deserialization.
           * When enabled, errors during deserialization will result in null values rather than exceptions.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.20
           */
          NullOnError(1 << 22),
  
          /**
           * Feature that determines whether to ignore autoType mismatches during deserialization.
           * When enabled, mismatches between expected and actual types in autoType scenarios will be ignored.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.21
           */
          IgnoreAutoTypeNotMatch(1 << 23),
  
          /**
           * Feature that determines whether to cast non-zero numbers to boolean true during deserialization.
           * When enabled, any non-zero numeric value will be treated as true when converting to boolean.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.24
           */
          NonZeroNumberCastToBooleanAsTrue(1 << 24),
  
          /**
           * Feature that determines whether to ignore null property values during deserialization.
           * When enabled, properties with null values in JSON will be ignored rather than set to null.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.40
           */
          IgnoreNullPropertyValue(1 << 25),
  
          /**
           * Feature that determines whether to throw an exception when unknown properties are encountered
           * during deserialization.
           * When enabled, an exception will be thrown if JSON contains properties that do not exist in the target class.
           *
           * <p>By default, this feature is disabled, meaning that unknown properties are ignored.</p>
           *
           * @since 2.0.42
           */
          ErrorOnUnknownProperties(1 << 26),
  
          /**
           * Feature that determines whether to convert empty strings to null values during deserialization.
           * When enabled, empty string values ("") in JSON will be converted to null values.
           *
           * <p>By default, this feature is disabled, meaning that empty strings are preserved as empty strings.</p>
           *
           * @since 2.0.48
           */
          EmptyStringAsNull(MASK_EMPTY_STRING_AS_NULL),
  
          /**
           * Feature that determines whether to avoid throwing exceptions on number overflow during deserialization.
           * When enabled, numeric overflow conditions will not cause exceptions to be thrown.
           *
           * <p>By default, this feature is disabled, meaning that number overflow will cause exceptions.</p>
           *
           * @since 2.0.48
           */
          NonErrorOnNumberOverflow(1 << 28),
  
          /**
           * Feature that determines whether JSON integral (non-floating-point)
           * numbers are to be deserialized into {@link java.math.BigInteger}s
           * if only generic type description (either {@link Object} or
           * {@link Number}, or within untyped {@link java.util.Map}
           * or {@link java.util.Collection} context) is available.
           * If enabled such values will be deserialized as
           * {@link java.math.BigInteger}s;
           * if disabled, will be deserialized as "smallest" available type,
           * which is either {@link Integer}, {@link Long} or
           * {@link java.math.BigInteger}, depending on number of digits.
           * <p>
           * Feature is disabled by default, meaning that "untyped" integral
           * numbers will by default be deserialized using whatever
           * is the most compact integral type, to optimize efficiency.
           * @since 2.0.51
           */
          UseBigIntegerForInts(1 << 29),
  
          /**
           * Feature that determines how "small" JSON integral (non-floating-point)
           * numbers -- ones that fit in 32-bit signed integer (`int`) -- are bound
           * when target type is loosely typed as {@link Object} or {@link Number}
           * (or within untyped {@link java.util.Map} or {@link java.util.Collection} context).
           * If enabled, such values will be deserialized as {@link java.lang.Long};
           * if disabled, they will be deserialized as "smallest" available type,
           * {@link Integer}.
           *<p>
           * Note: if {@link #UseBigIntegerForInts} is enabled, it has precedence
           * over this setting, forcing use of {@link java.math.BigInteger} for all
           * integral values.
           *<p>
           * Feature is disabled by default, meaning that "untyped" integral
           * numbers will by default be deserialized using {@link java.lang.Integer}
           * if value fits.
           *
           * @since 2.0.51
           */
          UseLongForInts(1 << 30),
  
          /**
           * Feature that disables the support for single quote.
           * When enabled, single quotes are not allowed as string delimiters in JSON.
           *
           * <p>By default, this feature is disabled, meaning that single quotes are supported.</p>
           *
           * @since 2.0.53
           */
          DisableSingleQuote(MASK_DISABLE_SINGLE_QUOTE),
  
          /**
           * Feature that determines whether to deserialize decimal numbers as double values.
           * When enabled, decimal values will be represented as double precision floating-point numbers.
           *
           * <p>By default, this feature is disabled.</p>
           *
           * @since 2.0.53
           */
          UseDoubleForDecimals(1L << 32L),
  
          /**
           * Feature that disables reference detection during deserialization.
           * When enabled, JSON references (such as those using $ref) will not be processed.
           *
           * <p>By default, this feature is disabled, meaning that reference detection is enabled.</p>
           *
           * @since 2.0.56
           */
          DisableReferenceDetect(MASK_DISABLE_REFERENCE_DETECT);
  
          public final long mask;
  
          Feature(long mask) {
              this.mask = mask;
          }
  
          /**
           * Combines the masks of the specified features into a single bitmask.
           *
           * @param features The features to combine
           * @return A bitmask representing the combined features, or 0 if features is null
           */
          public static long of(Feature[] features) {
              if (features == null) {
                  return 0;
              }
  
              long value = 0;
  
              for (Feature feature : features) {
                  value |= feature.mask;
              }
  
              return value;
          }
  
          /**
           * Checks if this feature is enabled in the specified features bitmask.
           *
           * @param features The features bitmask to check
           * @return true if this feature is enabled, false otherwise
           */
          public boolean isEnabled(long features) {
              return (features & mask) != 0;
          }
  
          /**
           * Checks if the specified feature is enabled in the given features bitmask.
           *
           * @param features The features bitmask to check
           * @param feature The feature to check for
           * @return true if the specified feature is enabled, false otherwise
           */
          public static boolean isEnabled(long features, Feature feature) {
              return (features & feature.mask) != 0;
          }
      }
  
      static final class ResolveTask {
          final FieldReader fieldReader;
          final Object object;
          final Object name;
          final JSONPath reference;
  
          ResolveTask(FieldReader fieldReader, Object object, Object name, JSONPath reference) {
              this.fieldReader = fieldReader;
              this.object = object;
              this.name = name;
              this.reference = reference;
          }
  
          @Override
          public String toString() {
              return reference.toString();
          }
      }
  
      public SavePoint mark() {
          return new SavePoint(this.offset, this.ch);
      }
  
      public void reset(SavePoint savePoint) {
          this.offset = savePoint.offset;
          this.ch = (char) savePoint.current;
      }
  
      final boolean checkNameBegin(int quote) {
          long features = context.features;
          if (quote == '\'' && ((features & MASK_DISABLE_SINGLE_QUOTE) != 0)) {
              throw notSupportName();
          }
          if (quote != '"' && quote != '\'') {
              if ((features & MASK_ALLOW_UN_QUOTED_FIELD_NAMES) != 0) {
                  readFieldNameHashCodeUnquote();
                  return true;
              }
              throw notSupportName();
          }
          return false;
      }
  
      final JSONException notSupportName() {
          return new JSONException(info("not support unquoted name"));
      }
  
      final JSONException valueError() {
          return new JSONException(info("illegal value"));
      }
  
      final JSONException error(String message) {
          return new JSONException(info(message));
      }
  
      final JSONException error(String message, Exception cause) {
          return new JSONException(info(message), cause);
      }
  
      final JSONException error() {
          throw new JSONValidException("error, offset " + offset + ", char " + (char) ch);
      }
  
      final JSONException error(int offset, int ch) {
          throw new JSONValidException("error, offset " + offset + ", char " + (char) ch);
      }
  
      static JSONException syntaxError(int ch) {
          return new JSONException("syntax error, expect ',', but '" + (char) ch + "'");
      }
  
      static JSONException syntaxError(int offset, int ch) {
          return new JSONException("syntax error, offset " + offset + ", char " + (char) ch);
      }
  
      static JSONException numberError(int offset, int ch) {
          return new JSONException("illegal number, offset " + offset + ", char " + (char) ch);
      }
  
      JSONException numberError() {
          return new JSONException("illegal number, offset " + offset + ", char " + ch);
      }
  
      public final String info() {
          return info(null);
      }
  
      public String info(String message) {
          if (message == null || message.isEmpty()) {
              return "offset " + offset;
          }
          return message + ", offset " + offset;
      }
  
      static boolean isFirstIdentifier(int ch) {
          return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || ch == '$' || (ch >= '0' && ch <= '9') || ch > 0x7F;
      }
  
      public static class SavePoint {
          protected final int offset;
          protected final int current;
  
          protected SavePoint(int offset, int current) {
              this.offset = offset;
              this.current = current;
          }
      }
  
      static final class BigIntegerCreator
              implements BiFunction<Integer, int[], BigInteger> {
          static final BiFunction<Integer, int[], BigInteger> BIG_INTEGER_CREATOR;
  
          static {
              BiFunction<Integer, int[], BigInteger> bigIntegerCreator = null;
              if (!ANDROID && !GRAAL) {
                  try {
                      MethodHandles.Lookup caller = JDKUtils.trustedLookup(BigInteger.class);
                      MethodHandle handle = caller.findConstructor(
                              BigInteger.class, MethodType.methodType(void.class, int.class, int[].class)
                      );
                      CallSite callSite = LambdaMetafactory.metafactory(
                              caller,
                              "apply",
                              MethodType.methodType(BiFunction.class),
                              handle.type().generic(),
                              handle,
                              MethodType.methodType(BigInteger.class, Integer.class, int[].class)
                      );
                      bigIntegerCreator = (BiFunction<Integer, int[], BigInteger>) callSite.getTarget().invokeExact();
                  } catch (Throwable ignored) {
                      // ignored
                  }
              }
              if (bigIntegerCreator == null) {
                  bigIntegerCreator = new BigIntegerCreator();
              }
              BIG_INTEGER_CREATOR = bigIntegerCreator;
          }
  
          @Override
          public BigInteger apply(Integer integer, int[] mag) {
              int signum = integer;
              final int bitLength;
              if (mag.length == 0) {
                  bitLength = 0; // offset by one to initialize
              } else {
                  // Calculate the bit length of the magnitude
                  int bitLengthForInt = 32 - Integer.numberOfLeadingZeros(mag[0]);
                  int magBitLength = ((mag.length - 1) << 5) + bitLengthForInt;
                  if (signum < 0) {
                      // Check if magnitude is a power of two
                      boolean pow2 = (Integer.bitCount(mag[0]) == 1);
                      for (int i = 1; i < mag.length && pow2; i++) {
                          pow2 = (mag[i] == 0);
                      }
                      bitLength = (pow2 ? magBitLength - 1 : magBitLength);
                  } else {
                      bitLength = magBitLength;
                  }
              }
              int byteLen = bitLength / 8 + 1;
  
              byte[] bytes = new byte[byteLen];
              for (int i = byteLen - 1, bytesCopied = 4, nextInt = 0, intIndex = 0; i >= 0; i--) {
                  if (bytesCopied == 4) {
                      // nextInt = getInt(intIndex++
                      int n = intIndex++;
                      if (n < 0) {
                          nextInt = 0;
                      } else if (n >= mag.length) {
                          nextInt = signum < 0 ? -1 : 0;
                      } else {
                          int magInt = mag[mag.length - n - 1];
                          if (signum >= 0) {
                              nextInt = magInt;
                          } else {
                              int firstNonzeroIntNum;
                              {
                                  int j;
                                  int mlen = mag.length;
                                  for (j = mlen - 1; j >= 0 && mag[j] == 0; j--) {
                                      // empty
                                  }
                                  firstNonzeroIntNum = mlen - j - 1;
                              }
  
                              if (n <= firstNonzeroIntNum) {
                                  nextInt = -magInt;
                              } else {
                                  nextInt = ~magInt;
                              }
                          }
                      }
  
                      bytesCopied = 1;
                  } else {
                      nextInt >>>= 8;
                      bytesCopied++;
                  }
                  bytes[i] = (byte) nextInt;
              }
  
              return new BigInteger(bytes);
          }
      }
  
      /**
       * Gets an ObjectReader for the specified type hash, expected class, and features.
       * This method handles auto-type detection and applies any configured auto-type before handlers.
       *
       * @param typeHash The hash code of the type
       * @param expectClass The expected class
       * @param features Additional features to consider
       * @return An ObjectReader for the specified type, or null if not found
       */
      public ObjectReader getObjectReaderAutoType(long typeHash, Class expectClass, long features) {
          ObjectReader autoTypeObjectReader = context.getObjectReaderAutoType(typeHash);
          if (autoTypeObjectReader != null) {
              return autoTypeObjectReader;
          }
  
          String typeName = getString();
          if (context.autoTypeBeforeHandler != null) {
              Class<?> autoTypeClass = context.autoTypeBeforeHandler.apply(typeName, expectClass, features);
              if (autoTypeClass != null) {
                  boolean fieldBased = (features & Feature.FieldBased.mask) != 0;
                  return context.provider.getObjectReader(autoTypeClass, fieldBased);
              }
          }
  
          return context.provider.getObjectReader(typeName, expectClass, context.features | features);
      }
  
      protected final String readStringNotMatch() {
          switch (ch) {
              case '[':
                  List array = readArray();
                  if (array.size() == 1) {
                      Object item = array.get(0);
                      if (item == null) {
                          return null;
                      }
                      if (item instanceof String) {
                          return item.toString();
                      }
                  }
                  return toString(array);
              case '{':
                  return toString(
                          readObject());
              case '-':
              case '+':
              case '0':
              case '1':
              case '2':
              case '3':
              case '4':
              case '5':
              case '6':
              case '7':
              case '8':
              case '9':
                  readNumber0();
                  Number number = getNumber();
                  return number.toString();
              case 't':
              case 'f':
                  boolValue = readBoolValue();
                  return boolValue ? "true" : "false";
              case 'n': {
                  readNull();
                  return null;
              }
              default:
                  throw new JSONException(info("illegal input : " + ch));
          }
      }
  
      protected static String stringValue(String str, long features) {
          if ((features & MASK_TRIM_STRING) != 0) {
              str = str.trim();
          }
          if ((features & MASK_EMPTY_STRING_AS_NULL) != 0 && str.isEmpty()) {
              return null;
          }
          return str;
      }
  }
  