brandur / json_schema

A JSON Schema V4 and Hyperschema V4 parser and validator.
MIT License
230 stars 45 forks source link

Validation of required in nested object #57

Open panSarin opened 8 years ago

panSarin commented 8 years ago

Hello . I wonder if I do something bad, or that is #TODO

Basically i have 2 definitons 1st is the definition of response that will return Hash in JSON 2nd is the defintion of elements in Hash - that definition containst required section that define which fields in each object in hash should be displayed there. And basically if i pass my whole schema to compare it with whole response from API that validation of required won't work. I debugged json_schema gem a little , and checked that schema object in

 def validate_data(schema, data, errors, path)
   schema.required # => nil.
 end

So do i do something wrong in my .json schema , or maybe i can;'t use validation on such lvl, and i have to use it like

  schema.definitions[nested_object].validate!(response['single_object'].with_indifferent_access)
brandur commented 8 years ago

Hi @panSarin!

The required field should work on any schema level. It's possible there's a bug here, but not one that I've heard about before.

Is it possible for you to assemble a schema/data sample that demonstrates the problem? I could probably give you a more clear answer then.

mrtibs2000 commented 8 years ago

Hi,

I'm also having trouble with nested validations, although I'm not sure I would describe the problem as @panSarin . I have the following schema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id" : "http://localhost:3000/schemas/address.json",
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "line1": { "type": "string" },
        "line2": { "type": "string" },
        "line3": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" },
        "zip_code": { "type": "string" },
        "phone_number": { "type": "string" }
      },
      "required": ["zip_code"]
    },
  "type": "object",
  "properties": {
    "from_address": { "$ref": "#/definitions/address" },
    "to_address": { "$ref": "#/definitions/address" }
  },
  "required": ["from_address", "to_address"]
}

The following data is validated as correct by the gem, but it is missing the zip_code "from_address" : { "line1" : "test" }, "to_address" : { "line1" : "test" } }

brandur commented 8 years ago

Hi @mrtibs2000,

I think you may have forgotten a curly brace somewhere in your schema above (specifically, the closing brace for definitions). When I put both your schema and data into files, I correctly see the data come out as invalid:

$ bundle exec validate-schema schema.json data.json
data.json#/from_address: failed schema #/properties/from_address: "zip_code" wasn't supplied.
data.json#/to_address: failed schema #/properties/to_address: "zip_code" wasn't supplied.

Here's the schema.json I used (corrected from yours above):

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "id" : "http://localhost:3000/schemas/address.json",
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "line1": { "type": "string" },
        "line2": { "type": "string" },
        "line3": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" },
        "zip_code": { "type": "string" },
        "phone_number": { "type": "string" }
      },
      "required": ["zip_code"]
    }
  },
  "type": "object",
  "properties": {
    "from_address": { "$ref": "#/definitions/address" },
    "to_address": { "$ref": "#/definitions/address" }
  },
  "required": ["from_address", "to_address"]
}
mrtibs2000 commented 8 years ago

Hi @brandur ,

Thanks for looking into this. I did have the right schema, I just didn't paste it properly here. The strange part is that when I have a Ruby script, it does work fine. It doesn't behave the same when I execute this from a Rails app. Here's what I have in my controller:

  before_action :parse_json, :validate_json

  def parse_json
    begin
      @json_data = JSON.parse(request.body.string)
    rescue
      render json: { errors: ["unable to parse json: #{request.body.string}"] }.to_json, status: 400
    end
  end

  def validate_json
    schema_data = JSON.parse(File.read(Rails.root.join('app').join('schema').join('price.json').to_s))
    schema = JsonSchema.parse!(schema_data)
    errors = schema.validate(@json_data)
    if !errors[0]
      render json: { errors: errors[1].map { |error| error.to_s } }.to_json, status: 422
    end
  end
brandur commented 8 years ago

Hey @mrtibs2000,

Based on the fact that the gem seems to be doing the right thing, it seems reasonable to conclude here that the inputs may not be making it there as we would hope. Can you throw a debugger into that validate_json method and just make sure that the schema and data are the values that you'd expect?

mrtibs2000 commented 8 years ago

Sorry to have wasted your time. I forgot to include this in my code: schema.expand_references! I would say close the issue, but I'm not sure if the original problem was resolved. Thanks!!!

brandur commented 8 years ago

Oops, yep that one is a bit of a gotcha! Good to hear you fixed it though, thanks :)

On Tuesday, April 12, 2016, Tiberiu Motoc notifications@github.com wrote:

Sorry to have wasted your time. I forgot to include this in my code: schema.expand_references! Thanks!!!

— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/brandur/json_schema/issues/57#issuecomment-209172747

panSarin commented 8 years ago

https://gist.github.com/panSarin/6a7efcdd503a315d21fcf38213d560cc So lets say that is my JSON (required 'test' field wont occur in response so it should raise error during validation)

And if my response looks like:

"user_settings"=>
  {
   "id"=>"573486adfc0022fae6000037",
   "ios_landing_page"=>"Hello",
   "allowed_touch_login"=>false,
    "units"=>[{"id"=>"5734860000e", "facility"=>"Facility"}]
}

Problem is that if i run

RSpec::Matchers.define :match_schema do |schema_file_name, object_name|
  match do |response|
    schema_path = "#{Dir.pwd}/jsons/#{schema_file_name}.json"
    schema_data = JSON.parse(File.read(schema_path))
    schema = JsonSchema.parse! schema_data
    schema.expand_references!
    schema.definitions[object_name].validate!(response.with_indifferent_access) # raise errors if not valid
    true # returns true if errors weren't raised so test pass
  end
end

on this response + object, it will pass because it doesnt check nested objects schema. I had to add something like:

EmptyResultError = Class.new(StandardError)
RSpec::Matchers.define :match_nested_schema do |schema_file_name, object_name, nested_object_name, response_key=nil|
  match do |response|
    schema_path = "#{Dir.pwd}/jsons/#{schema_file_name}.json"
    schema_data = JSON.parse(File.read(schema_path))
    schema = JsonSchema.parse! schema_data
    schema.expand_references!

    aggregated_object = schema.definitions[object_name]
    aggregated_object.validate!(response.with_indifferent_access) # raise errors if not valid

    response_key = aggregated_object.properties.keys.first if response_key.nil?
    elements = response[response_key]

    raise EmptyResultError if elements.nil?
    elements.each do |nested_object|
      schema.definitions[nested_object_name].validate!(nested_object.with_indifferent_access)
    end
    true # returns true if errors weren't raised so test pass
  end
end

so i can call it with passing which nested element i want to validate too.

So is it a bug , or i am missing something?;]

brandur commented 8 years ago

Hey @panSarin, you definitely shouldn't have to hack it that far. The gem will validate nested properties just fine, so something must be a little off here.

One thing I noticed in your schema is that although you have the test property in your required array, it doesn't have a definition in your properties list. I wonder if something like that could be throwing it off?

ssgaur commented 6 years ago
resource_group_schema = {
    "type":"object",
    "$schema":"http://json-schema.org/draft-03/schema",
    "properties" : 
    {
        "name":
        {
            "type":"string",
            "maxLength":64,
            "required":True
        },
        "filters": {
            "type":"array",
            "required":True,
            "properties": {
                "attribute_name": {
                    "type":"string",
                    "required":True,
                    "enum": ["a", "b"]
                },
            }
        }
    },
    "additionalProperties":False
}

Above valdiation for filters are not taking effect. If I do not send attribute_name still validation is success while it must fail What is worng here I am not able to understand with Draft3Validation ?

brandur commented 6 years ago

@ssgaur Hey, there should probably be a check on this somewhere in here, but given that draft 4 had been the overwhelmingly preferred spec for so long when I wrote this, the project should largely be considered only usable for draft 4 and up.

Given that draft 3 is quite dated at this point, it probably makes sense for you to write your schemas targeting a newer spec anyway. In 4, required switched to an array, and that's what you'll need to change here in order to produce the validation error you're looking for.