qri-io / jsonschema

golang implementation of https://json-schema.org drafts 7 & 2019-09
MIT License
461 stars 54 forks source link

feat(type): identify custom struct as objects #54

Closed asido closed 5 years ago

asido commented 5 years ago

Motivation

Limiting type object to map[string]interface{} and nothing else is restrictive. It would be understandable if the only possible input is JSON encoded []byte. But now we can pass our data structures. Therefore I think the change makes sense.

I would also consider mapping every number in reflect.Kind to a valid type, but first let me know if this is a change you could think of merging.

Benchmark

I benchmarked the implementation using Go 1.12.7. The efficiency is identical.

// Old DataType()
BenchmarkTypeValidation-8           5000        225318 ns/op

// New DataType()
BenchmarkTypeValidation-8           5000        223040 ns/op
Code
func BenchmarkTypeValidation(b *testing.B) {
    var rs RootSchema
    if err := json.Unmarshal([]byte(jsonSchema), &rs); err != nil {
        b.Errorf("unable to unmarshal jsonschema: %v", err)
    }

    var data []interface{}
    if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
        b.Errorf("unable to unmarshal json data: %v", err)
    }

    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        var errs []ValError
        rs.Validate("", data, &errs)
        for _, err := range errs {
            b.Errorf("invalid json data: %v", err)
        }
    }
}
var jsonSchema = `
{
  "definitions": {},
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/root.json",
  "type": "array",
  "items": {
    "$id": "#/items",
    "type": "object",
    "properties": {
      "_id": {
        "$id": "#/items/properties/_id",
        "type": "string"
      },
      "index": {
        "$id": "#/items/properties/index",
        "type": "integer"
      },
      "guid": {
        "$id": "#/items/properties/guid",
        "type": "string"
      },
      "isActive": {
        "$id": "#/items/properties/isActive",
        "type": "boolean"
      },
      "balance": {
        "$id": "#/items/properties/balance",
        "type": "string"
      },
      "picture": {
        "$id": "#/items/properties/picture",
        "type": "string"
      },
      "age": {
        "$id": "#/items/properties/age",
        "type": "integer"
      },
      "eyeColor": {
        "$id": "#/items/properties/eyeColor",
        "type": "string"
      },
      "name": {
        "$id": "#/items/properties/name",
        "type": "object",
        "properties": {
          "first": {
            "$id": "#/items/properties/name/properties/first",
            "type": "string"
          },
          "last": {
            "$id": "#/items/properties/name/properties/last",
            "type": "string"
          }
        }
      },
      "company": {
        "$id": "#/items/properties/company",
        "type": "string"
      },
      "email": {
        "$id": "#/items/properties/email",
        "type": "string"
      },
      "phone": {
        "$id": "#/items/properties/phone",
        "type": "string"
      },
      "address": {
        "$id": "#/items/properties/address",
        "type": "string"
      },
      "about": {
        "$id": "#/items/properties/about",
        "type": "string"
      },
      "registered": {
        "$id": "#/items/properties/registered",
        "type": "string"
      },
      "latitude": {
        "$id": "#/items/properties/latitude",
        "type": "string"
      },
      "longitude": {
        "$id": "#/items/properties/longitude",
        "type": "string"
      },
      "tags": {
        "$id": "#/items/properties/tags",
        "type": "array",
        "items": {
          "$id": "#/items/properties/tags/items",
          "type": "string"
        }
      },
      "range": {
        "$id": "#/items/properties/range",
        "type": "array",
        "items": {
          "$id": "#/items/properties/range/items",
          "type": "integer"
        }
      },
      "friends": {
        "$id": "#/items/properties/friends",
        "type": "array",
        "items": {
          "$id": "#/items/properties/friends/items",
          "type": "object",
          "properties": {
            "id": {
              "$id": "#/items/properties/friends/items/properties/id",
              "type": "integer"
            },
            "name": {
              "$id": "#/items/properties/friends/items/properties/name",
              "type": "string"
            }
          }
        }
      },
      "greeting": {
        "$id": "#/items/properties/greeting",
        "type": "string"
      },
      "favoriteFruit": {
        "$id": "#/items/properties/favoriteFruit",
        "type": "string"
      }
    }
  }
}
`
var jsonData = `
[
  {
    "_id": "5d61319e267927d1568d252c",
    "index": 0,
    "guid": "806698a0-107f-4705-8578-635db974dda5",
    "isActive": false,
    "balance": "$3,450.34",
    "picture": "http://placehold.it/32x32",
    "age": 38,
    "eyeColor": "brown",
    "name": {
      "first": "Stacy",
      "last": "Hansen"
    },
    "company": "OBONES",
    "email": "stacy.hansen@obones.name",
    "phone": "+1 (910) 534-3387",
    "address": "961 Havens Place, Joppa, Montana, 4767",
    "about": "Sit aliqua tempor ullamco id commodo minim ut magna commodo reprehenderit sint. Consequat in commodo veniam ut fugiat reprehenderit ut cupidatat dolor esse veniam enim dolor. Occaecat exercitation amet culpa in fugiat enim incididunt duis excepteur non enim. Do ea sunt mollit ea magna exercitation anim. Laboris quis pariatur tempor irure. Duis reprehenderit culpa est labore aliquip irure cillum.",
    "registered": "Sunday, May 21, 2017 6:04 AM",
    "latitude": "29.235737",
    "longitude": "-172.020409",
    "tags": [
      "ullamco",
      "nulla",
      "anim",
      "ipsum",
      "ea"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Logan Baxter"
      },
      {
        "id": 1,
        "name": "Ida Osborn"
      },
      {
        "id": 2,
        "name": "Marie Woodard"
      }
    ],
    "greeting": "Hello, Stacy! You have 6 unread messages.",
    "favoriteFruit": "apple"
  },
  {
    "_id": "5d61319e0395dad4f03a0643",
    "index": 1,
    "guid": "578f5162-3cc5-4d21-82ee-99dee1939880",
    "isActive": false,
    "balance": "$3,457.18",
    "picture": "http://placehold.it/32x32",
    "age": 39,
    "eyeColor": "blue",
    "name": {
      "first": "Carter",
      "last": "Hatfield"
    },
    "company": "KEEG",
    "email": "carter.hatfield@keeg.me",
    "phone": "+1 (854) 563-3004",
    "address": "457 Cypress Avenue, Walker, Vermont, 5373",
    "about": "Cupidatat voluptate amet et amet occaecat ea elit irure ullamco reprehenderit. Dolor Lorem proident exercitation duis irure voluptate reprehenderit anim Lorem elit adipisicing ea nisi enim. Pariatur anim laboris et tempor veniam id fugiat officia labore dolore in laborum id. Nostrud esse ipsum incididunt fugiat sit aliqua ullamco sunt. Ad dolor aute deserunt mollit ullamco sint magna do consequat veniam occaecat excepteur eiusmod aliquip.",
    "registered": "Tuesday, April 18, 2017 2:49 AM",
    "latitude": "76.414341",
    "longitude": "-44.996223",
    "tags": [
      "nulla",
      "reprehenderit",
      "in",
      "est",
      "elit"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Lottie Valencia"
      },
      {
        "id": 1,
        "name": "Shelia Downs"
      },
      {
        "id": 2,
        "name": "Hale Cunningham"
      }
    ],
    "greeting": "Hello, Carter! You have 8 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5d61319e9691e77d4f7651eb",
    "index": 2,
    "guid": "f7a8fe8f-bc76-4862-adf4-2f04e7fcf649",
    "isActive": false,
    "balance": "$2,421.14",
    "picture": "http://placehold.it/32x32",
    "age": 39,
    "eyeColor": "brown",
    "name": {
      "first": "Adrienne",
      "last": "Ruiz"
    },
    "company": "FARMAGE",
    "email": "adrienne.ruiz@farmage.tv",
    "phone": "+1 (822) 480-3761",
    "address": "471 Hyman Court, Hamilton, New Hampshire, 5914",
    "about": "Laborum consequat pariatur cupidatat nulla laborum sint sint id Lorem. Sit adipisicing eiusmod enim excepteur ex incididunt duis est ex. Eu et labore ipsum commodo qui pariatur incididunt. Nostrud et anim aliqua exercitation voluptate deserunt proident velit ut aliqua eiusmod sit.",
    "registered": "Sunday, April 8, 2018 3:47 PM",
    "latitude": "-14.903675",
    "longitude": "148.65533",
    "tags": [
      "officia",
      "enim",
      "cupidatat",
      "anim",
      "ipsum"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Janette Williamson"
      },
      {
        "id": 1,
        "name": "Lea Wilkins"
      },
      {
        "id": 2,
        "name": "Mary Carlson"
      }
    ],
    "greeting": "Hello, Adrienne! You have 6 unread messages.",
    "favoriteFruit": "apple"
  },
  {
    "_id": "5d61319e99ad0afdaec7ae1b",
    "index": 3,
    "guid": "a1768c3f-97bf-473e-a17f-1a1569f73434",
    "isActive": true,
    "balance": "$2,114.76",
    "picture": "http://placehold.it/32x32",
    "age": 28,
    "eyeColor": "brown",
    "name": {
      "first": "Terry",
      "last": "Wolfe"
    },
    "company": "CEDWARD",
    "email": "terry.wolfe@cedward.co.uk",
    "phone": "+1 (965) 495-3608",
    "address": "849 Visitation Place, Glendale, Palau, 6575",
    "about": "Eiusmod duis magna sit magna fugiat ex mollit eiusmod duis fugiat non tempor. Aliquip pariatur nulla consectetur laborum qui ad quis aute nisi. Fugiat ea exercitation irure do enim ipsum in ullamco tempor adipisicing cupidatat cillum Lorem aliquip. Anim sit qui eiusmod Lorem do ad veniam deserunt nisi qui cillum.",
    "registered": "Friday, March 25, 2016 1:12 PM",
    "latitude": "-41.353055",
    "longitude": "-41.574776",
    "tags": [
      "culpa",
      "incididunt",
      "nisi",
      "proident",
      "ad"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Haley Ortiz"
      },
      {
        "id": 1,
        "name": "Valencia Ray"
      },
      {
        "id": 2,
        "name": "Dillard Crane"
      }
    ],
    "greeting": "Hello, Terry! You have 6 unread messages.",
    "favoriteFruit": "strawberry"
  },
  {
    "_id": "5d61319ecdd42b472f2d2915",
    "index": 4,
    "guid": "b66970fd-a086-40c5-8e27-d4c5fba320b8",
    "isActive": false,
    "balance": "$1,061.76",
    "picture": "http://placehold.it/32x32",
    "age": 31,
    "eyeColor": "brown",
    "name": {
      "first": "Florine",
      "last": "Odonnell"
    },
    "company": "EMOLTRA",
    "email": "florine.odonnell@emoltra.net",
    "phone": "+1 (988) 507-3553",
    "address": "775 Creamer Street, Columbus, Utah, 2783",
    "about": "Enim qui ea ut sit laborum mollit dolor culpa cillum voluptate non reprehenderit. Est incididunt consequat ea ipsum amet veniam. Excepteur ut duis officia excepteur proident pariatur et elit velit ea. Fugiat aute dolore nisi amet aute dolore. Dolor duis proident ex non est enim.",
    "registered": "Sunday, May 6, 2018 6:07 AM",
    "latitude": "-82.113645",
    "longitude": "-179.215071",
    "tags": [
      "nulla",
      "duis",
      "consequat",
      "Lorem",
      "nisi"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Clay Hooper"
      },
      {
        "id": 1,
        "name": "Chrystal Barnett"
      },
      {
        "id": 2,
        "name": "Griffith Fischer"
      }
    ],
    "greeting": "Hello, Florine! You have 8 unread messages.",
    "favoriteFruit": "banana"
  },
  {
    "_id": "5d61319e42bb19e55e71d26c",
    "index": 5,
    "guid": "7a715f20-2415-4cd4-8dd7-5576e73db827",
    "isActive": false,
    "balance": "$1,282.02",
    "picture": "http://placehold.it/32x32",
    "age": 37,
    "eyeColor": "green",
    "name": {
      "first": "Susie",
      "last": "Hebert"
    },
    "company": "ORBOID",
    "email": "susie.hebert@orboid.org",
    "phone": "+1 (879) 446-3634",
    "address": "529 Green Street, Longoria, Iowa, 2493",
    "about": "Adipisicing irure ea voluptate magna aute velit irure elit voluptate aliqua proident sunt deserunt. Sit irure proident veniam id dolore irure sit aliqua pariatur et. Ut Lorem pariatur pariatur adipisicing sit id sit Lorem et ea tempor. Ipsum veniam excepteur incididunt officia officia esse amet exercitation. Do in in quis et dolor eiusmod ad ipsum ad dolor aliquip. Consequat do minim magna aute et irure incididunt magna voluptate cillum aliquip dolor sunt.",
    "registered": "Thursday, May 17, 2018 9:42 AM",
    "latitude": "58.540125",
    "longitude": "44.642099",
    "tags": [
      "deserunt",
      "occaecat",
      "cupidatat",
      "nulla",
      "amet"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Benita Pace"
      },
      {
        "id": 1,
        "name": "Clarice Powell"
      },
      {
        "id": 2,
        "name": "Kelley Mayer"
      }
    ],
    "greeting": "Hello, Susie! You have 10 unread messages.",
    "favoriteFruit": "apple"
  },
  {
    "_id": "5d61319eb9f74a89f8189c59",
    "index": 6,
    "guid": "0ac7c4eb-c4c3-4f35-869a-05ba5cb8a052",
    "isActive": false,
    "balance": "$1,401.41",
    "picture": "http://placehold.it/32x32",
    "age": 23,
    "eyeColor": "brown",
    "name": {
      "first": "Miranda",
      "last": "Erickson"
    },
    "company": "COLLAIRE",
    "email": "miranda.erickson@collaire.com",
    "phone": "+1 (961) 443-2037",
    "address": "917 Putnam Avenue, Bluffview, North Carolina, 7451",
    "about": "Cillum consectetur adipisicing minim laborum irure. Est veniam consequat amet reprehenderit dolor nulla veniam. Quis amet deserunt Lorem dolor sint nostrud anim non.",
    "registered": "Sunday, August 3, 2014 10:02 PM",
    "latitude": "-36.905012",
    "longitude": "81.905921",
    "tags": [
      "consequat",
      "ut",
      "tempor",
      "consectetur",
      "consequat"
    ],
    "range": [
      0,
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9
    ],
    "friends": [
      {
        "id": 0,
        "name": "Cole Morrow"
      },
      {
        "id": 1,
        "name": "Ochoa Wiley"
      },
      {
        "id": 2,
        "name": "Sparks Reynolds"
      }
    ],
    "greeting": "Hello, Miranda! You have 10 unread messages.",
    "favoriteFruit": "strawberry"
  }
]
`
b5 commented 5 years ago

Wow this great @asido! Thanks so such a thorough PR. I've been thinking about making this exact change for some time now, and am delighted you've not only beat me to it, but added benchmarks to help qualify the change.

For validation to work properly, however, we'd have a bunch of work ahead of us. Lots of code in this package looks like this:

https://github.com/qri-io/jsonschema/blob/9f11b79125715650da0b4932b3ca66328b508ac7/keywords_objects.go#L39-L46

Each validator uses a type assertion before performing a validation operation, and silently skips if the wrong type is presented.

We'd need to flip from a concrete type assertion to using reflection in all of these places. This would definitely be a source of slowdowns, but I think it'd be worth it to be able to use this package on arbitrary go structs. Others have asked for this change as well.

Long story short: I'll happily accept this PR as-is, there's more work to be done. We'd happily accept any subsequent PRs that move us in this direction.

On a clerical note, we have a set of commit message guidelines, for this repo that we ask others to follow. We rely on commit messages to generate change logs. I'd be happy to squash-merge this if you'd prefer not to re-write your commit messages, or you can rebase to have the record properly attributed to you 😄.

asido commented 5 years ago

Sounds good. I'm changing the commit message.

Shell we also interpret all other 10+ numeric types as number/integer?

Let me do an additional PR to fix other validators.

b5 commented 5 years ago

Sounds good. I'm changing the commit message.

delightful. Thanks!

Shell we also interpret all other 10+ numeric types as number/integer?

Yes for sure. Apologies for not clarifying earlier

Let me do an additional PR to fix other validators.

Great. If you'd like to pick a single kind of validator as a proof-of-concept it'll be easier to review, and reduce the chance of duplicated work across all validators. Maybe let's start the validators in object.go & get a PR merged that updates just those before moving on to the rest

asido commented 5 years ago

I squashed and changed the message. Further code changes will come another day as another PR if you end up accepting this one as is.

b5 commented 5 years ago

Looks great! Thanks! I'll merge this now