private void parseProperties(
        ObjectMapper.Builder objBuilder,
        Map<String, Object> propsNode,
        ParserContext parserContext) {
      for (Map.Entry<String, Object> entry : propsNode.entrySet()) {
        String propName = entry.getKey();
        Map<String, Object> propNode = (Map<String, Object>) entry.getValue();

        String type;
        Object typeNode = propNode.get("type");
        if (typeNode != null) {
          type = typeNode.toString();
        } else {
          // lets see if we can derive this...
          if (propNode.get("properties") != null) {
            type = ObjectMapper.CONTENT_TYPE;
          } else if (propNode.get("fields") != null) {
            type = MultiFieldMapper.CONTENT_TYPE;
          } else {
            throw new MapperParsingException("No type specified for property [" + propName + "]");
          }
        }

        Mapper.TypeParser typeParser = parserContext.typeParser(type);
        if (typeParser == null) {
          throw new MapperParsingException(
              "No handler for type [" + type + "] declared on field [" + propName + "]");
        }
        objBuilder.add(typeParser.parse(propName, propNode, parserContext));
      }
    }
    private Mapper.Builder<?, ?> findMapperBuilder(
        Map<String, Object> propNode, String propName, ParserContext parserContext) {
      String type;
      Object typeNode = propNode.get("type");
      if (typeNode != null) {
        type = typeNode.toString();
      } else {
        type = "string";
      }
      Mapper.TypeParser typeParser = parserContext.typeParser(type);
      Mapper.Builder<?, ?> mapperBuilder =
          typeParser.parse(propName, (Map<String, Object>) propNode, parserContext);

      return mapperBuilder;
    }
    protected static void parseProperties(
        ObjectMapper.Builder objBuilder,
        Map<String, Object> propsNode,
        ParserContext parserContext) {
      for (Map.Entry<String, Object> entry : propsNode.entrySet()) {
        String propName = entry.getKey();
        // Should accept empty arrays, as a work around for when the user can't provide an empty
        // Map. (PHP for example)
        boolean isEmptyList =
            entry.getValue() instanceof List && ((List<?>) entry.getValue()).isEmpty();

        if (entry.getValue() instanceof Map) {
          @SuppressWarnings("unchecked")
          Map<String, Object> propNode = (Map<String, Object>) entry.getValue();
          String type;
          Object typeNode = propNode.get("type");
          if (typeNode != null) {
            type = typeNode.toString();
          } else {
            // lets see if we can derive this...
            if (propNode.get("properties") != null) {
              type = ObjectMapper.CONTENT_TYPE;
            } else if (propNode.size() == 1 && propNode.get("enabled") != null) {
              // if there is a single property with the enabled flag on it, make it an object
              // (usually, setting enabled to false to not index any type, including core values,
              // which
              // non enabled object type supports).
              type = ObjectMapper.CONTENT_TYPE;
            } else {
              throw new MapperParsingException("No type specified for property [" + propName + "]");
            }
          }

          Mapper.TypeParser typeParser = parserContext.typeParser(type);
          if (typeParser == null) {
            throw new MapperParsingException(
                "No handler for type [" + type + "] declared on field [" + propName + "]");
          }
          objBuilder.add(typeParser.parse(propName, propNode, parserContext));
        } else if (!isEmptyList) {
          throw new MapperParsingException(
              "Expected map for property [fields] on field ["
                  + propName
                  + "] but got a "
                  + propName.getClass());
        }
      }
    }
    private void parseProperties(
        ObjectMapper.Builder objBuilder,
        Map<String, Object> propsNode,
        ParserContext parserContext) {
      for (Map.Entry<String, Object> entry : propsNode.entrySet()) {
        String propName = entry.getKey();
        Map<String, Object> propNode = (Map<String, Object>) entry.getValue();

        String type;
        Object typeNode = propNode.get("type");
        if (typeNode != null) {
          type = typeNode.toString();
        } else {
          // lets see if we can derive this...
          if (propNode.get("properties") != null) {
            type = ObjectMapper.CONTENT_TYPE;
          } else if (propNode.get("fields") != null) {
            type = MultiFieldMapper.CONTENT_TYPE;
          } else if (propNode.size() == 1 && propNode.get("enabled") != null) {
            // if there is a single property with the enabled flag on it, make it an object
            // (usually, setting enabled to false to not index any type, including core values,
            // which
            // non enabled object type supports).
            type = ObjectMapper.CONTENT_TYPE;
          } else {
            throw new MapperParsingException("No type specified for property [" + propName + "]");
          }
        }

        Mapper.TypeParser typeParser = parserContext.typeParser(type);
        if (typeParser == null) {
          throw new MapperParsingException(
              "No handler for type [" + type + "] declared on field [" + propName + "]");
        }
        objBuilder.add(typeParser.parse(propName, propNode, parserContext));
      }
    }
    @SuppressWarnings({"unchecked"})
    @Override
    public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext)
        throws MapperParsingException {
      AttachmentMapper.Builder builder = new AttachmentMapper.Builder(name);

      for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
          iterator.hasNext(); ) {
        Map.Entry<String, Object> entry = iterator.next();
        String fieldName = entry.getKey();
        Object fieldNode = entry.getValue();
        if (fieldName.equals("path")
            && parserContext.indexVersionCreated().before(Version.V_2_0_0_beta1)) {
          builder.pathType(parsePathType(name, fieldNode.toString()));
          iterator.remove();
        } else if (fieldName.equals("fields")) {
          Map<String, Object> fieldsNode = (Map<String, Object>) fieldNode;
          for (Iterator<Map.Entry<String, Object>> fieldsIterator =
                  fieldsNode.entrySet().iterator();
              fieldsIterator.hasNext(); ) {
            Map.Entry<String, Object> entry1 = fieldsIterator.next();
            String propName = entry1.getKey();
            Map<String, Object> propNode = (Map<String, Object>) entry1.getValue();

            Mapper.Builder<?, ?> mapperBuilder =
                findMapperBuilder(propNode, propName, parserContext);
            if (parseMultiField(
                (FieldMapper.Builder) mapperBuilder,
                fieldName,
                parserContext,
                propName,
                propNode)) {
              fieldsIterator.remove();
            } else if (propName.equals(name)
                && parserContext.indexVersionCreated().before(Version.V_2_0_0_beta1)) {
              builder.content(mapperBuilder);
              fieldsIterator.remove();
            } else {
              switch (propName) {
                case FieldNames.CONTENT:
                  builder.content(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.DATE:
                  builder.date(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.AUTHOR:
                  builder.author(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.CONTENT_LENGTH:
                  builder.contentLength(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.CONTENT_TYPE:
                  builder.contentType(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.KEYWORDS:
                  builder.keywords(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.LANGUAGE:
                  builder.language(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.TITLE:
                  builder.title(mapperBuilder);
                  fieldsIterator.remove();
                  break;
                case FieldNames.NAME:
                  builder.name(mapperBuilder);
                  fieldsIterator.remove();
                  break;
              }
            }
          }
          DocumentMapperParser.checkNoRemainingFields(
              fieldName, fieldsNode, parserContext.indexVersionCreated());
          iterator.remove();
        }
      }

      return builder;
    }