@Override
 public String read(JsonParser parser) throws IOException, JsonReadException {
   try {
     String v = parser.getText();
     String error = getKeyFormatError(v);
     if (error != null) {
       throw new JsonReadException(
           "bad format for app secret: " + error, parser.getTokenLocation());
     }
     parser.nextToken();
     return v;
   } catch (JsonParseException ex) {
     throw JsonReadException.fromJackson(ex);
   }
 }
        @Override
        public final DbxAppInfo read(JsonParser parser) throws IOException, JsonReadException {
          JsonLocation top = JsonReader.expectObjectStart(parser);

          String key = null;
          String secret = null;
          DbxHost host = null;

          while (parser.getCurrentToken() == JsonToken.FIELD_NAME) {
            String fieldName = parser.getCurrentName();
            parser.nextToken();

            try {
              if (fieldName.equals("key")) {
                key = KeyReader.readField(parser, fieldName, key);
              } else if (fieldName.equals("secret")) {
                secret = SecretReader.readField(parser, fieldName, secret);
              } else if (fieldName.equals("host")) {
                host = DbxHost.Reader.readField(parser, fieldName, host);
              } else {
                // Unknown field.  Skip over it.
                JsonReader.skipValue(parser);
              }
            } catch (JsonReadException ex) {
              throw ex.addFieldContext(fieldName);
            }
          }

          JsonReader.expectObjectEnd(parser);

          if (key == null) throw new JsonReadException("missing field \"key\"", top);
          if (secret == null) throw new JsonReadException("missing field \"secret\"", top);
          if (host == null) host = DbxHost.Default;

          return new DbxAppInfo(key, secret, host);
        }