24

A rest service needs to validate all incoming json data against a json schema. The json schemas are public accessible and can be retrieved via http requests.

I'm using the jackson-framwork for marshaling and unmarshaling between java and json. So far I couldn't find any possibility to validate the data against the schema by using jackson.

I also tried the JsonTools framework which obviously comes up with such a validation functionality. But unfortunately it wasn't possible for me to get the validation to work. Why JsonTool schema validation isn't working?

How can I do such a validation?

4

3 回答 3

17

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

我也有兴趣找到进行 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"));
    }

}
于 2013-08-12T17:46:01.563 回答
8
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());
    }

}
于 2016-06-03T01:52:14.870 回答
0

看起来您并没有绑定到 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 1的声明ValidatedJsonObjectRepresentingRequestOrResponse
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: Discountblock 是一个命名块,由其他命名条目(对象或标量)组成。
Line 36: 第一个叫做valid_until
Line 35: 是必须的。
Line 34:如果它真的是一个字符串,则表示为一个字符串。如果不是,将返回一个错误。
Line 33: 最后,检查它没有过期。
Line 43: 第二个参数被调用promo_code
Line 42: 也是必须的。
Line 41: 它必须包含字母和数字。
Line 40: 而且它不应该已经被赎回了。这个事实肯定会保存在我们的数据库中,因此 ...<br> 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

这个例子取自这里。在这里您可以找到一个完整的json 请求验证示例

于 2020-05-07T07:49:03.213 回答