JsonBuilderFactory.java

package redis.clients.jedis.json;

import static redis.clients.jedis.BuilderFactory.STRING;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import redis.clients.jedis.Builder;
import redis.clients.jedis.exceptions.JedisException;

public final class JsonBuilderFactory {

  public static final Builder<Class<?>> JSON_TYPE = new Builder<Class<?>>() {
    @Override
    public Class<?> build(Object data) {
      if (data == null) return null;
      String str = STRING.build(data);
      switch (str) {
        case "null":
          return null;
        case "boolean":
          return boolean.class;
        case "integer":
          return int.class;
        case "number":
          return float.class;
        case "string":
          return String.class;
        case "object":
          return Object.class;
        case "array":
          return List.class;
        default:
          throw new JedisException("Unknown type: " + str);
      }
    }

    @Override
    public String toString() {
      return "Class<?>";
    }
  };

  public static final Builder<List<Class<?>>> JSON_TYPE_LIST = new Builder<List<Class<?>>>() {
    @Override
    public List<Class<?>> build(Object data) {
      List<Object> list = (List<Object>) data;
      List<Class<?>> classes = new ArrayList<>(list.size());
      for (Object elem : list) {
        try {
          classes.add(JSON_TYPE.build(elem));
        } catch (JedisException je) {
          classes.add(null);
        }
      }
      return classes;
    }
  };

  public static final Builder<List<List<Class<?>>>> JSON_TYPE_RESPONSE_RESP3 = new Builder<List<List<Class<?>>>>() {
    @Override
    public List<List<Class<?>>> build(Object data) {
      return ((List<Object>) data).stream().map(JSON_TYPE_LIST::build).collect(Collectors.toList());
    }
  };

  public static final Builder<List<Class<?>>> JSON_TYPE_RESPONSE_RESP3_COMPATIBLE = new Builder<List<Class<?>>>() {
    @Override
    public List<Class<?>> build(Object data) {
      List<List<Class<?>>> fullReply = JSON_TYPE_RESPONSE_RESP3.build(data);
      return fullReply == null ? null : fullReply.get(0);
    }
  };

  public static final Builder<Object> JSON_OBJECT = new Builder<Object>() {
    @Override
    public Object build(Object data) {
      if (data == null) {
        return null;
      }

      if (!(data instanceof byte[])) {
        return data;
      }
      String str = STRING.build(data);
      if (str.charAt(0) == '{') {
        try {
          return new JSONObject(str);
        } catch (Exception ex) {
        }
      } else if (str.charAt(0) == '[') {
        try {
          return new JSONArray(str);
        } catch (Exception ex) {
        }
      }
      return str;
    }
  };

  public static final Builder<JSONArray> JSON_ARRAY = new Builder<JSONArray>() {
    @Override
    public JSONArray build(Object data) {
      if (data == null) {
        return null;
      }
      String str = STRING.build(data);
      try {
        return new JSONArray(str);
      } catch (JSONException ex) { // This is not necessary but we are doing this
        // just to make it safe for com.vaadin.external.google:android-json library
        throw new JedisException(ex);
      }
    }
  };

  /**
   * Builder that preserves numeric types (Long for integers, Double for decimals).
   */
  public static final Builder<List<Number>> JSON_NUMBER_LIST = new Builder<List<Number>>() {
    @Override
    @SuppressWarnings("unchecked")
    public List<Number> build(Object data) {
      if (data == null) return null;
      List<Object> list = (List<Object>) data;
      List<Number> result = new ArrayList<>(list.size());
      for (Object element : list) {
        if (element == null) {
          result.add(null);
        } else if (element instanceof Long) {
          result.add((Long) element);
        } else if (element instanceof Double) {
          result.add((Double) element);
        } else if (element instanceof Number) {
          result.add((Number) element);
        } else {
          // Parse from string (RESP2 fallback)
          String str = STRING.build(element);
          if (str.contains(".") || str.contains("e") || str.contains("E")) {
            result.add(Double.parseDouble(str));
          } else {
            result.add(Long.parseLong(str));
          }
        }
      }
      return result;
    }

    @Override
    public String toString() {
      return "List<Number>";
    }
  };

  public static final Builder<Object> JSON_ARRAY_OR_NUMBER_LIST = new Builder<Object>() {
    @Override
    public Object build(Object data) {
      if (data == null) return null;
      if (data instanceof List) return JSON_NUMBER_LIST.build(data);
      return JSON_ARRAY.build(data);
    }
  };

  public static final Builder<List<JSONArray>> JSON_ARRAY_LIST = new Builder<List<JSONArray>>() {
    @Override
    public List<JSONArray> build(Object data) {
      if (data == null) {
        return null;
      }
      List<Object> list = (List<Object>) data;
      return list.stream().map(o -> JSON_ARRAY.build(o)).collect(Collectors.toList());
    }
  };

  private JsonBuilderFactory() {
    throw new InstantiationError("Must not instantiate this class");
  }
}