@Nullable
 private <T> Collection<T> parseOptionalArray(
     boolean shouldUseNestedValueJson,
     JSONObject json,
     JsonWeakParser<T> jsonParser,
     String... path)
     throws JSONException {
   if (shouldUseNestedValueJson) {
     final JSONObject js = JsonParseUtil.getNestedOptionalObject(json, path);
     if (js == null) {
       return null;
     }
     return parseArray(js, jsonParser, VALUE_ATTR);
   } else {
     final JSONArray jsonArray = JsonParseUtil.getNestedOptionalArray(json, path);
     if (jsonArray == null) {
       return null;
     }
     final Collection<T> res = new ArrayList<T>(jsonArray.length());
     for (int i = 0; i < jsonArray.length(); i++) {
       res.add(jsonParser.parse(jsonArray.get(i)));
     }
     return res;
   }
 }
 @Nullable
 private String getOptionalFieldStringUnisex(
     boolean shouldUseNestedValueJson, JSONObject json, String attributeName)
     throws JSONException {
   final JSONObject fieldsJson = json.getJSONObject(FIELDS);
   if (shouldUseNestedValueJson) {
     final JSONObject fieldJson = fieldsJson.optJSONObject(attributeName);
     if (fieldJson != null) {
       return JsonParseUtil.getOptionalString(fieldJson, VALUE_ATTR); // pre 5.0 way
     } else {
       return null;
     }
   }
   return JsonParseUtil.getOptionalString(fieldsJson, attributeName);
 }
  @Nullable
  private <T> T getOptionalField(
      boolean shouldUseNestedValue, JSONObject s, final String fieldId, JsonParser<T> jsonParser)
      throws JSONException {
    final JSONObject fieldJson = JsonParseUtil.getNestedOptionalObject(s, FIELDS, fieldId);
    // for fields like assignee (when unassigned) value attribute may be missing completely
    if (fieldJson != null) {
      if (shouldUseNestedValue) {
        final JSONObject valueJsonObject = fieldJson.optJSONObject(VALUE_ATTR);
        if (valueJsonObject != null) {
          return jsonParser.parse(valueJsonObject);
        }

      } else {
        return jsonParser.parse(fieldJson);
      }
    }
    return null;
  }
  @Override
  public Issue parse(JSONObject s) throws JSONException {
    final Iterable<String> expandos = parseExpandos(s);
    final boolean isJira5x0OrNewer =
        Iterables.contains(expandos, SCHEMA_SECTION) || this.types != null;
    final boolean shouldUseNestedValueAttribute = !isJira5x0OrNewer;
    final Collection<Comment> comments;
    if (isJira5x0OrNewer) {
      final JSONObject commentsJson = s.getJSONObject(FIELDS).getJSONObject(COMMENT_FIELD.id);
      comments =
          parseArray(
              commentsJson,
              new JsonWeakParserForJsonObject<Comment>(commentJsonParser),
              "comments");

    } else {
      final Collection<Comment> commentsTmp =
          parseOptionalArray(
              shouldUseNestedValueAttribute,
              s,
              new JsonWeakParserForJsonObject<Comment>(commentJsonParser),
              FIELDS,
              COMMENT_FIELD.id);
      comments = commentsTmp != null ? commentsTmp : Lists.<Comment>newArrayList();
    }

    final String summary = getFieldStringValue(s, SUMMARY_FIELD.id);
    final String description =
        getOptionalFieldStringUnisex(shouldUseNestedValueAttribute, s, DESCRIPTION_FIELD.id);

    final Collection<Attachment> attachments =
        parseOptionalArray(
            shouldUseNestedValueAttribute,
            s,
            new JsonWeakParserForJsonObject<Attachment>(attachmentJsonParser),
            FIELDS,
            ATTACHMENT_FIELD.id);
    final Collection<Field> fields =
        isJira5x0OrNewer ? parseFieldsJira5x0(s) : parseFields(s.getJSONObject(FIELDS));

    final BasicIssueType issueType =
        issueTypeJsonParser.parse(getFieldUnisex(s, ISSUE_TYPE_FIELD.id));
    final DateTime creationDate =
        JsonParseUtil.parseDateTime(getFieldStringUnisex(s, CREATED_FIELD.id));
    final DateTime updateDate =
        JsonParseUtil.parseDateTime(getFieldStringUnisex(s, UPDATED_FIELD.id));

    final String dueDateString =
        getOptionalFieldStringUnisex(shouldUseNestedValueAttribute, s, DUE_DATE_FIELD.id);
    final DateTime dueDate =
        dueDateString == null ? null : JsonParseUtil.parseDateTimeOrDate(dueDateString);

    final BasicPriority priority =
        getOptionalField(shouldUseNestedValueAttribute, s, PRIORITY_FIELD.id, priorityJsonParser);
    final BasicResolution resolution =
        getOptionalField(
            shouldUseNestedValueAttribute, s, RESOLUTION_FIELD.id, resolutionJsonParser);
    final BasicUser assignee =
        getOptionalField(shouldUseNestedValueAttribute, s, ASSIGNEE_FIELD.id, userJsonParser);
    final BasicUser reporter =
        getOptionalField(shouldUseNestedValueAttribute, s, REPORTER_FIELD.id, userJsonParser);

    final String transitionsUri =
        getOptionalFieldStringUnisex(shouldUseNestedValueAttribute, s, TRANSITIONS_FIELD.id);
    final BasicProject project = projectJsonParser.parse(getFieldUnisex(s, PROJECT_FIELD.id));
    final Collection<IssueLink> issueLinks;
    if (isJira5x0OrNewer) {
      issueLinks =
          parseOptionalArray(
              shouldUseNestedValueAttribute,
              s,
              new JsonWeakParserForJsonObject<IssueLink>(issueLinkJsonParserV5),
              FIELDS,
              LINKS_FIELD.id);
    } else {
      issueLinks =
          parseOptionalArray(
              shouldUseNestedValueAttribute,
              s,
              new JsonWeakParserForJsonObject<IssueLink>(issueLinkJsonParser),
              FIELDS,
              LINKS_PRE_5_0_FIELD.id);
    }

    Collection<Subtask> subtasks = null;
    if (isJira5x0OrNewer) {
      subtasks =
          parseOptionalArray(
              shouldUseNestedValueAttribute,
              s,
              new JsonWeakParserForJsonObject<Subtask>(subtaskJsonParser),
              FIELDS,
              SUBTASKS_FIELD.id);
    }

    final BasicVotes votes =
        getOptionalField(shouldUseNestedValueAttribute, s, VOTES_FIELD.id, votesJsonParser);
    final BasicStatus status = statusJsonParser.parse(getFieldUnisex(s, STATUS_FIELD.id));

    final Collection<Version> fixVersions =
        parseOptionalArray(
            shouldUseNestedValueAttribute,
            s,
            new JsonWeakParserForJsonObject<Version>(versionJsonParser),
            FIELDS,
            FIX_VERSIONS_FIELD.id);
    final Collection<Version> affectedVersions =
        parseOptionalArray(
            shouldUseNestedValueAttribute,
            s,
            new JsonWeakParserForJsonObject<Version>(versionJsonParser),
            FIELDS,
            AFFECTS_VERSIONS_FIELD.id);
    final Collection<BasicComponent> components =
        parseOptionalArray(
            shouldUseNestedValueAttribute,
            s,
            new JsonWeakParserForJsonObject<BasicComponent>(basicComponentJsonParser),
            FIELDS,
            COMPONENTS_FIELD.id);

    final Collection<Worklog> worklogs;
    if (isJira5x0OrNewer) {
      if (JsonParseUtil.getNestedOptionalObject(s, FIELDS, WORKLOG_FIELD.id) != null) {
        worklogs =
            parseOptionalArray(
                shouldUseNestedValueAttribute,
                s,
                new JsonWeakParserForJsonObject<Worklog>(
                    new WorklogJsonParserV5(JsonParseUtil.getSelfUri(s))),
                FIELDS,
                WORKLOG_FIELD.id,
                WORKLOGS_FIELD.id);
      } else {
        worklogs = Collections.emptyList();
      }
    } else {
      worklogs =
          parseOptionalArray(
              shouldUseNestedValueAttribute,
              s,
              new JsonWeakParserForJsonObject<Worklog>(worklogJsonParser),
              FIELDS,
              WORKLOG_FIELD.id);
    }

    final BasicWatchers watchers =
        getOptionalField(
            shouldUseNestedValueAttribute,
            s,
            isJira5x0OrNewer ? WATCHER_FIELD.id : WATCHER_PRE_5_0_FIELD.id,
            watchersJsonParser);
    final TimeTracking timeTracking =
        getOptionalField(
            shouldUseNestedValueAttribute,
            s,
            TIMETRACKING_FIELD.id,
            isJira5x0OrNewer ? new TimeTrackingJsonParserV5() : new TimeTrackingJsonParser());

    final Set<String> labels =
        Sets.newHashSet(
            parseOptionalArrayNotNullable(
                shouldUseNestedValueAttribute,
                s,
                jsonWeakParserForString,
                FIELDS,
                LABELS_FIELD.id));

    final Collection<ChangelogGroup> changelog =
        parseOptionalArray(
            false,
            s,
            new JsonWeakParserForJsonObject<ChangelogGroup>(changelogJsonParser),
            "changelog",
            "histories");
    return new Issue(
        summary,
        JsonParseUtil.getSelfUri(s),
        s.getString("key"),
        project,
        issueType,
        status,
        description,
        priority,
        resolution,
        attachments,
        reporter,
        assignee,
        creationDate,
        updateDate,
        dueDate,
        affectedVersions,
        fixVersions,
        components,
        timeTracking,
        fields,
        comments,
        transitionsUri != null ? JsonParseUtil.parseURI(transitionsUri) : null,
        issueLinks,
        votes,
        worklogs,
        watchers,
        expandos,
        subtasks,
        changelog,
        labels);
  }