@Override
 public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext)
     throws MapperParsingException {
   StringFieldMapper.Builder builder = stringField(name);
   parseField(builder, name, node, parserContext);
   for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
       iterator.hasNext(); ) {
     Map.Entry<String, Object> entry = iterator.next();
     String propName = Strings.toUnderscoreCase(entry.getKey());
     Object propNode = entry.getValue();
     if (propName.equals("null_value")) {
       if (propNode == null) {
         throw new MapperParsingException("Property [null_value] cannot be null.");
       }
       builder.nullValue(propNode.toString());
       iterator.remove();
     } else if (propName.equals("search_quote_analyzer")) {
       NamedAnalyzer analyzer = parserContext.analysisService().analyzer(propNode.toString());
       if (analyzer == null) {
         throw new MapperParsingException(
             "Analyzer [" + propNode.toString() + "] not found for field [" + name + "]");
       }
       builder.searchQuotedAnalyzer(analyzer);
       iterator.remove();
     } else if (propName.equals("position_offset_gap")) {
       builder.positionOffsetGap(XContentMapValues.nodeIntegerValue(propNode, -1));
       // we need to update to actual analyzers if they are not set in this case...
       // so we can inject the position offset gap...
       if (builder.indexAnalyzer == null) {
         builder.indexAnalyzer = parserContext.analysisService().defaultIndexAnalyzer();
       }
       if (builder.searchAnalyzer == null) {
         builder.searchAnalyzer = parserContext.analysisService().defaultSearchAnalyzer();
       }
       if (builder.searchQuotedAnalyzer == null) {
         builder.searchQuotedAnalyzer =
             parserContext.analysisService().defaultSearchQuoteAnalyzer();
       }
       iterator.remove();
     } else if (propName.equals("ignore_above")) {
       builder.ignoreAbove(XContentMapValues.nodeIntegerValue(propNode, -1));
       iterator.remove();
     } else if (parseMultiField(builder, name, parserContext, propName, propNode)) {
       iterator.remove();
     }
   }
   return builder;
 }
 @Override
 public Mapper.Builder parse(
     String fieldName, Map<String, Object> node, ParserContext parserContext)
     throws MapperParsingException {
   StringFieldMapper.Builder builder = new StringFieldMapper.Builder(fieldName);
   // hack for the fact that string can't just accept true/false for
   // the index property and still accepts no/not_analyzed/analyzed
   final Object index = node.remove("index");
   if (index != null) {
     final String normalizedIndex = Strings.toUnderscoreCase(index.toString());
     switch (normalizedIndex) {
       case "analyzed":
         builder.tokenized(true);
         node.put("index", true);
         break;
       case "not_analyzed":
         builder.tokenized(false);
         node.put("index", true);
         break;
       case "no":
         node.put("index", false);
         break;
       default:
         throw new IllegalArgumentException(
             "Can't parse [index] value ["
                 + index
                 + "] for field ["
                 + fieldName
                 + "], expected [true], [false], [no], [not_analyzed] or [analyzed]");
     }
   }
   parseTextField(builder, fieldName, node, parserContext);
   for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator();
       iterator.hasNext(); ) {
     Map.Entry<String, Object> entry = iterator.next();
     String propName = Strings.toUnderscoreCase(entry.getKey());
     Object propNode = entry.getValue();
     if (propName.equals("null_value")) {
       if (propNode == null) {
         throw new MapperParsingException("Property [null_value] cannot be null.");
       }
       builder.nullValue(propNode.toString());
       iterator.remove();
     } else if (propName.equals("position_increment_gap")) {
       int newPositionIncrementGap = XContentMapValues.nodeIntegerValue(propNode, -1);
       if (newPositionIncrementGap < 0) {
         throw new MapperParsingException("positions_increment_gap less than 0 aren't allowed.");
       }
       builder.positionIncrementGap(newPositionIncrementGap);
       // we need to update to actual analyzers if they are not set in this case...
       // so we can inject the position increment gap...
       if (builder.fieldType().indexAnalyzer() == null) {
         builder
             .fieldType()
             .setIndexAnalyzer(parserContext.analysisService().defaultIndexAnalyzer());
       }
       if (builder.fieldType().searchAnalyzer() == null) {
         builder
             .fieldType()
             .setSearchAnalyzer(parserContext.analysisService().defaultSearchAnalyzer());
       }
       if (builder.fieldType().searchQuoteAnalyzer() == null) {
         builder
             .fieldType()
             .setSearchQuoteAnalyzer(
                 parserContext.analysisService().defaultSearchQuoteAnalyzer());
       }
       iterator.remove();
     } else if (propName.equals("ignore_above")) {
       builder.ignoreAbove(XContentMapValues.nodeIntegerValue(propNode, -1));
       iterator.remove();
     } else if (parseMultiField(builder, fieldName, parserContext, propName, propNode)) {
       iterator.remove();
     }
   }
   return builder;
 }