【问题标题】:How do I validate incoming JSON data inside a REST service?如何验证 REST 服务中的传入 JSON 数据?
【发布时间】:2013-08-11 21:00:21
【问题描述】:

rest 服务需要根据 json 模式验证所有传入的 json 数据。 json 模式是公共可访问的,可以通过 http 请求检索。

我正在使用 jackson-framwork 在 java 和 json 之间进行编组和解组。到目前为止,我找不到任何使用 jackson 验证架构数据的可能性。

我还尝试了JsonTools 框架,它显然提供了这样的验证功能。但不幸的是,我无法让验证工作。 Why JsonTool schema validation isn't working?

我怎样才能进行这样的验证?

【问题讨论】:

  • 我终于弄清楚了如何为 REST 服务执行 json 验证。一旦这个问题不再是on hold,我将发布完整的示例作为答案
  • 嗨。仅供参考,否决票将是因为 stackoverflow 不被认为是基于意见的问题的最佳论坛,例如什么是做 X 的最佳库。查看help section
  • @theon 谢谢你的提示。我现在更新了这个问题。如果问题需要进一步考虑,请告诉我。
  • 太棒了。这样看起来更好。

标签: java json validation rest jsonschema


【解决方案1】:

看起来您并没有绑定到 JSONSchema,尽管它似乎是您的默认选择。口味不同,但通常看起来更复杂。此外,就个人而言,我希望将数据和验证规则放在同一个地方。当在 java 代码而不是任何类型的配置文件中使用时,自定义验证器似乎更自然地适合。

这是这种方法的外观。比如说,您有以下 json 对象代表一些付款(无论是请求还是响应),但为简洁起见仅包含 discount 块:

{
    "discount":{
        "valid_until":"2032-05-04 00:00:00+07",
        "promo_code":"VASYA1988"
    }
}

验证码如下所示:

/*1 */    public class ValidatedJsonObjectRepresentingRequestOrResponse implements Validatable<JsonObjectRepresentingRequestOrResponse>
          {
              private String jsonString;
              private Connection dbConnection;

/*6 */        public ValidatedJsonObjectRepresentingRequestOrResponse(String jsonString, Connection dbConnection)
              {
                  this.jsonString = jsonString;
                  this.dbConnection = dbConnection;
              }

              @Override
/*13*/        public Result<JsonObjectRepresentingRequestOrResponse> result() throws Exception
              {
                  return
/*16*/                new FastFail<>(
/*17*/                    new WellFormedJson(
/*18*/                        new Unnamed<>(Either.right(new Present<>(this.jsonRequestString)))
/*19*/                    ),
/*20*/                    requestJsonObject ->
/*21*/                        new UnnamedBlocOfNameds<>(
                                  List.of(
/*23*/                                new FastFail<>(
/*24*/                                    new IsJsonObject(
/*25*/                                        new Required(
/*26*/                                            new IndexedValue("discount", requestJsonObject)
                                              )
                                          ),
/*29*/                                    discountObject ->
/*30*/                                        new NamedBlocOfNameds<>(
/*31*/                                            "discount",
/*32*/                                            List.of(
/*33*/                                                new PromoCodeIsNotExpired(
/*34*/                                                    new AsString(
/*35*/                                                        new Required(
/*36*/                                                            new IndexedValue("valid_until", discountObject)
                                                              )
                                                          )
                                                      ),
/*40*/                                                new PromoCodeIsNotAlreadyRedeemed(
/*41*/                                                    new PromoCodeContainsBothLettersAndDigits(
/*42*/                                                        new Required(
/*43*/                                                            new IndexedValue("promo_code", discountObject)
                                                              )
                                                          ),
/*46*/                                                    this.dbConnection
                                                      )
                                                  ),
/*49*/                                            Discount.class
                                              )
                                      )
                                  ),
/*53*/                            JsonObjectRepresentingRequestOrResponse.class
                              )
                      )
                          .result();
              }
          }

让我们逐行看看这里发生了什么:

Line 1ValidatedJsonObjectRepresentingRequestOrResponse 的声明。
Line 6 其构造函数接受原始 json 字符串。它可能是传入的请求或收到的响应,或者几乎是其他任何东西。
Line 13:在调用此方法时开始验证。
Lines 16:更高级别的验证对象是FastFail 块.如果第一个参数无效,则立即返回错误。
Lines 17-19: 检查 json 是否格式正确。如果是后者,验证会快速失败并返回相应的错误。
Line 20: 如果 json 格式正确,则调用闭包,并将 json 数据作为其单个参数传递。
Line 21: json数据得到验证。它的结构是命名块的未命名块。它对应一个 JSON 对象。
Line 26:第一个(也是唯一的)块称为 discount
Line 25:它是必需的。
Line 24:它必须是一个 json对象。
Line 23:如果不是,将立即返回错误,因为它是FailFast 对象。
Line 29:否则,将调用闭包。
Line 30:@987654343 @ 块是由其他命名条目(对象或标量)组成的命名块。
Line 36:第一个称为 valid_until
Line 35:它是必需的。
Line 34:并表示作为一个字符串,如果它真的是一个字符串。如果没有,则返回错误。
Line 33:最后,检查它是否过期。
Line 43:第二个参数称为promo_code
Line 42:也是必需的.
Line 41: 必须同时包含字母和数字。
Line 40: 并且它不应该已经被兑换了。这个事实肯定会保存在我们的数据库中,因此 ...
Line 46: ...this.dbConnection 参数。
Line 49: 如果所有先前的验证检查都成功,则创建一个 Discount 类的对象。Line 53: 最后,JsonObjectRepresentingRequestOrResponse 被创建并返回。

验证成功后调用代码如下所示:

Result<JsonObjectRepresentingRequestOrResponse> result = new ValidatedJsonObjectRepresentingRequestOrResponse(jsonRequestString).result();
result.isSuccessful();
result.value().raw().discount().promoCode(); // VASYA1988

这个例子取自here。在这里你可以找到一个成熟的json request validation example

【讨论】:

    【解决方案2】:
    import com.github.fge.jsonschema.core.report.ProcessingReport;
    import com.github.fge.jsonschema.main.JsonSchema;
    import com.github.fge.jsonschema.main.JsonSchemaFactory;
    import com.github.fge.jackson.JsonLoader;
    import com.fasterxml.jackson.databind.JsonNode;
    
    public class ValidationJSON {
        public static void main(String[] arr){
           String jsonData = "{\"name\": \"prem\"}";
           String jsonSchema = ""; //Schema we can generate online using http://jsonschema.net/
           final JsonNode data = JsonLoader.fromString(jsonData);
           final JsonNode schema = JsonLoader.fromString(jsonSchema);
    
           final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
           JsonValidator validator = factory.getValidator();
    
           ProcessingReport report = validator.validate(schema, data);
           System.out.println(report.isSuccess());
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      我搜索了将传入的 json 数据强制验证到 RESTful 服务的最佳实践。我的建议是使用MessageBodyReaderreadFrom 方法中执行验证。下面有一个消息体阅读器示例,为了简单起见,它是非通用的。

      我也有兴趣找到进行 json 数据验证的最佳框架。因为我使用 jackson 框架(版本 1.8.5)在 json 和 java 之间进行编组和解组,所以如果这个框架能够提供 json 数据验证功能会很好。不幸的是,我找不到与杰克逊一起做这件事的任何可能性。最后,我使用https://github.com 提供的 json-schema-validator 让它工作。我使用的版本是2.1.7

      import java.io.BufferedReader;
      import java.io.ByteArrayInputStream;
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.InputStreamReader;
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Type;
      
      import javax.servlet.ServletContext;
      import javax.ws.rs.Consumes;
      import javax.ws.rs.WebApplicationException;
      import javax.ws.rs.core.Context;
      import javax.ws.rs.core.MediaType;
      import javax.ws.rs.core.MultivaluedMap;
      import javax.ws.rs.ext.MessageBodyReader;
      import javax.ws.rs.ext.Provider;
      
      import org.codehaus.jackson.map.ObjectMapper;
      
      import at.fhj.ase.dao.data.Address;
      import at.fhj.ase.xmlvalidation.msbreader.MessageBodyReaderValidationException;
      
      import com.fasterxml.jackson.databind.JsonNode;
      import com.github.fge.jackson.JsonLoader;
      import com.github.fge.jsonschema.exceptions.ProcessingException;
      import com.github.fge.jsonschema.main.JsonSchemaFactory;
      import com.github.fge.jsonschema.main.JsonValidator;
      import com.github.fge.jsonschema.report.ProcessingReport;
      
      @Provider
      @Consumes(MediaType.APPLICATION_JSON)
      public class AddressJsonValidationReader implements MessageBodyReader<Address> {
      
          private final String jsonSchemaFileAsString;
      
          public AddressJsonValidationReader(@Context ServletContext servletContext) {
              this.jsonSchemaFileAsString = servletContext
                      .getRealPath("/json/Address.json");
          }
      
          @Override
          public boolean isReadable(Class<?> type, Type genericType,
                  Annotation[] annotations, MediaType mediaType) {
              if (type == Address.class) {
                  return true;
              }
              return false;
          }
      
          @Override
          public Address readFrom(Class<Address> type, Type genericType,
                  Annotation[] annotations, MediaType mediaType,
                  MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
                  throws IOException, WebApplicationException {
      
              final String jsonData = getStringFromInputStream(entityStream);
              System.out.println(jsonData);
      
              InputStream isSchema = new FileInputStream(jsonSchemaFileAsString);
              String jsonSchema = getStringFromInputStream(isSchema);
      
              /*
               * Perform JSON data validation against schema
               */
              validateJsonData(jsonSchema, jsonData);
      
              /*
               * Convert stream to data entity
               */
              ObjectMapper m = new ObjectMapper();
              Address addr = m.readValue(stringToStream(jsonData), Address.class);
      
              return addr;
          }
      
          /**
           * Validate the given JSON data against the given JSON schema
           * 
           * @param jsonSchema
           *            as String
           * @param jsonData
           *            as String
           * @throws MessageBodyReaderValidationException
           *             in case of an error during validation process
           */
          private void validateJsonData(final String jsonSchema, final String jsonData)
                  throws MessageBodyReaderValidationException {
              try {
                  final JsonNode d = JsonLoader.fromString(jsonData);
                  final JsonNode s = JsonLoader.fromString(jsonSchema);
      
                  final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
                  JsonValidator v = factory.getValidator();
      
                  ProcessingReport report = v.validate(s, d);
                  System.out.println(report);
                  if (!report.toString().contains("success")) {
                      throw new MessageBodyReaderValidationException(
                              report.toString());
                  }
      
              } catch (IOException e) {
                  throw new MessageBodyReaderValidationException(
                          "Failed to validate json data", e);
              } catch (ProcessingException e) {
                  throw new MessageBodyReaderValidationException(
                          "Failed to validate json data", e);
              }
          }
      
          /**
           * Taken from <a href=
           * "http://www.mkyong.com/java/how-to-convert-inputstream-to-string-in-java/"
           * >www.mkyong.com</a>
           * 
           * @param is
           *            {@link InputStream}
           * @return Stream content as String
           */
          private String getStringFromInputStream(InputStream is) {
              BufferedReader br = null;
              StringBuilder sb = new StringBuilder();
      
              String line;
              try {
      
                  br = new BufferedReader(new InputStreamReader(is));
                  while ((line = br.readLine()) != null) {
                      sb.append(line);
                  }
      
              } catch (IOException e) {
                  e.printStackTrace();
              } finally {
                  if (br != null) {
                      try {
                          br.close();
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
              }
      
              return sb.toString();
          }
      
          private InputStream stringToStream(final String str) throws UnsupportedEncodingException {
              return new ByteArrayInputStream(str.getBytes("UTF-8"));
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-10-30
        • 2015-03-19
        • 1970-01-01
        • 1970-01-01
        • 2019-12-10
        • 2019-02-09
        • 1970-01-01
        相关资源
        最近更新 更多