【问题标题】:Custom built JSON schema not validating properly自定义构建的 JSON 模式未正确验证
【发布时间】:2018-08-22 22:27:29
【问题描述】:

我有一个自定义构建的 JSON 架构,它只有几个顶层。这里的问题是它不能 100% 验证所有内容。例如,它只检测到 4 个字段中的 2 个,并且所需字段根本不起作用,附加属性等也不起作用。我将 this library 用于我的 json 架构。

{
    "users": {
        "PUT": {
          "definitions": {},
          "$schema": "http://json-schema.org/draft-07/schema#",
          "$id": "http://example.com/root.json",
          "type": "object",
          "title": "The Root Schema",
          "required": [
            "DisplayName",
            "Username",
            "Email",
            "Password"
          ],
          "properties": {
            "DisplayName": {
              "$id": "#/properties/DisplayName",
              "type": "string",
              "title": "The Displayname Schema",
              "default": "",
              "examples": [
                ""
              ],
              "minLength": 3,
              "maxLength": 24,
              "pattern": "^(.*)$"
            },
            "Username": {
              "$id": "#/properties/Username",
              "type": "string",
              "title": "The Username Schema",
              "default": "",
              "examples": [
                ""
              ],
              "minLength": 3,
              "maxLength": 15,
              "pattern": "^(.*)$"
            },
            "Email": {
              "$id": "#/properties/Email",
              "type": "string",
              "title": "The Email Schema",
              "default": "",
              "examples": [
                ""
              ],
              "minLength": 7,
              "pattern": "^(.*)$",
              "format": "email"
            },
            "Password": {
              "$id": "#/properties/Password",
              "type": "string",
              "title": "The Password Schema",
              "default": "",
              "examples": [
                ""
              ],
              "pattern": "^(.*)$"
            }
        },
        "additionalProperties": false
        }
    }
}

我正在解析这样的所有内容:

func Validate(data interface{}, r *http.Request) (interface{}, error) {
    // Convert the data struct to a readable JSON bytes
    JSONparams, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }

    // Split URL segments so we know what part of the API they are accessing
    modules := strings.Split(r.URL.String(), "/")
    modules = modules[(len(modules) - 1):]

    // Read the schema file
    fileSchema, _ := ioutil.ReadFile("config/schema/schema.json")
    var object interface{}

    // Unmarshal it so we can choose what schema we specifically want
    err = json.Unmarshal(fileSchema, &object)
    if err != nil {
        log.Fatal(err)
    }

    // Choose the preferred schema
    encodedJSON, err := json.Marshal(object.(map[string]interface{})[strings.Join(modules, "") + "s"].(map[string]interface{})[r.Method])
    if err != nil {
        log.Fatal(err)
    }

    // Load the JSON schema
    schema := gojsonschema.NewStringLoader(string(encodedJSON))

    // Load the JSON params
    document := gojsonschema.NewStringLoader(string(JSONparams))

    // Validate the document
    result, err := gojsonschema.Validate(schema, document)
    if err != nil {
        return nil, err
    }

    if !result.Valid() {
        // Map the errors into a new array
        var errors = make(map[string]string)
        for _, err := range result.Errors() {
            errors[err.Field()] = err.Description()
        }

        // Convert the array to an interface that we can convert to JSON
        resultMap := map[string]interface{}{
            "success": false,
            "result": map[string]interface{}{},
            "errors": errors,
        }

        // Convert the interface to a JSON object
        errorObject, err := json.Marshal(resultMap)
        if err != nil {
            return nil, err
        }

        return errorObject, nil
    }

    return nil, nil
}

type CreateParams struct {
    DisplayName     string
    Username        string
    Email           string
    Password        string
}

var (
    response interface{}
    status int = 0
)

func Create(w http.ResponseWriter, r *http.Request) {
    status = 0

    // Parse the request so we can access the query parameters
    r.ParseForm()

    // Assign them to the interface variables
    data := &CreateParams{
        DisplayName: r.Form.Get("DisplayName"),
        Username: r.Form.Get("Username"),
        Email: r.Form.Get("Email"),
        Password: r.Form.Get("Password"),
    }

    // Validate the JSON data
    errors, err := schema.Validate(data, r)

    if err != nil {
        responseJSON  := map[string]interface{}{
            "success": false,
            "result": map[string]interface{}{},
        }

        log.Fatal(err.Error())

        response, err = json.Marshal(responseJSON)
        status = http.StatusInternalServerError
    }

    // Catch any errors generated by the validator and assign them to the response interface
    if errors != nil {
        response = errors
        status = http.StatusBadRequest
    }

    // Status has not been set yet, so it's safe to assume that everything went fine
    if status == 0 {
        responseJSON  := map[string]interface{}{
            "success": true,
            "result": map[string]interface{} {
                "DisplayName": data.DisplayName,
                "Username": data.Username,
                "Email": data.Email,
                "Password": nil,
            },
        }

        response, err = json.Marshal(responseJSON)
        status = http.StatusOK
    }

    // We are going to respond with JSON, so set the appropriate header
    w.Header().Set("Content-Type", "application/json")

    // Write the header and the response
    w.WriteHeader(status)
    w.Write(response.([]byte))
}

我这样做的原因是我正在构建一个 REST API,如果 api/auth/user 收到一个 PUT 请求,我希望能够为“用户”部分指定数据要求PUT 方法。

知道如何实现吗?

编辑: 我的 json 数据:

{
  "DisplayName": "1234",
  "Username": "1234",
  "Email": "test@gmail.com",
  "Password": "123456"
}

编辑 2: 该数据应该会因架构而失败。

{
  "DisplayName": "1", // min length is 3
  "Username": "", // this field is required but is empty here
  "Email": "testgmail.com", // not following the email format
  "Password": "123456111111111111111111111111111111111111111111111" // too long
}

【问题讨论】:

  • 嗨!如果您提供您尝试验证的完整 JSON 架构和完整 JSON 实例数据,我将能够提供最好的帮助。我对 go 不熟悉,但我可以很容易地告诉你是否存在架构问题。
  • @Relequestual 我已经用完整的架构和我的 JSON 数据更新了帖子。谢谢。
  • 我已经使用jsonschemavalidator.net 测试了您的架构和数据。您可以删除patterndefaultexamples,它们在这里没有做任何事情。此外,您不需要为每个字段指定 $id。我可以让required 按预期工作。我还可以使每个字段验证失败。我想你可能期望 JSON Schema 做一些它没有做的事情。您能否提供您希望验证失败的 JSON 数据示例?
  • @Relequestual 已更新。我还想指出,在尝试您提供的模式验证器时,required 似乎不会覆盖参数的最小长度规则。
  • 我不确定required 会如何覆盖min-length。必需只是意味着对象键必须存在,并且没有指定它的值。就您应该失败的示例而言:在您的架构中,“密码”没有设置最小长度。 “用户名”和“显示名称”按预期失败,未达到最小长度。对于“格式”,对这些的支持是可选的,并且取决于每个实现。

标签: go jsonschema


【解决方案1】:

如果我使用 gojsonschema 手动加载架构和数据,它会按预期工作。我怀疑由于您以某种复杂的方式加载架构,因此您放入的架构最终会与您期望的有所不同,但是由于您的代码示例都是基于 HTTP 的,因此我自己无法真正对其进行测试.

【讨论】:

  • 我最后打印了模式的值,并在模式验证器中使用它,在那里它工作得很好。我想这里肯定还有其他一些你不能用肉眼轻易发现的错误,比如编码问题或其他什么?
  • 我为你做了一个快速的 repo,如果你想自己尝试一下:github.com/akkelw/gojsonschema_debug
  • 问题是您从表单中获取值,将它们放入一个结构体中,然后将该结构体转换为 JSON。如果表单中的所有值都是空的,这仍然会导致以下 JSON:{"DisplayName":"","Username":"","Email":"","Password":""} 这意味着所有属性都存在,它们只是空的,因此所需的检查将通过,并且密码实际上是有效的,因为它通过正则表达式^(.*)$。您应该在结构字段中指定json:omitempty 以删除空字段。
  • 感谢您的帮助。我想知道这是否真的意味着任何不同,这回答了我的问题。
猜你喜欢
  • 2012-08-26
  • 1970-01-01
  • 1970-01-01
  • 2021-08-03
  • 2011-12-19
  • 1970-01-01
  • 2015-06-07
  • 2019-11-06
  • 1970-01-01
相关资源
最近更新 更多