New Upstream Release - golang-github-alecthomas-jsonschema

Ready changes

Summary

Merged new upstream version: 0.0~git20220216.9eeeec9 (was: 0.0~git20210127.19bc6f2).

Resulting package

Built on 2023-01-19T07:03 (took 3m26s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-alecthomas-jsonschema-dev

Lintian Result

Diff

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..487674d
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [alecthomas]
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1ce2470
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,15 @@
+on: [push, pull_request]
+name: CI
+jobs:
+  test:
+    name: Test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+      - name: Init Hermit
+        run: ./bin/hermit env --raw >> $GITHUB_ENV
+      - name: Test
+        run: go test ./...
+      - name: Lint
+        run: golangci-lint run
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..df2c4fe
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,88 @@
+run:
+  tests: true
+  max-same-issues: 50
+  skip-dirs:
+    - resources
+    - old
+  skip-files:
+    - cmd/protopkg/main.go
+
+output:
+  print-issued-lines: false
+
+linters:
+  enable-all: true
+  disable:
+    - maligned
+    - megacheck
+    - lll
+    - typecheck # `go build` catches this, and it doesn't currently work with Go 1.11 modules
+    - goimports # horrendously slow with go modules :(
+    - dupl # has never been actually useful
+    - gochecknoglobals
+    - gochecknoinits
+    - interfacer # author deprecated it because it provides bad suggestions
+    - funlen
+    - whitespace
+    - godox
+    - wsl
+    - dogsled
+    - gomnd
+    - gocognit
+    - gocyclo
+    - scopelint
+    - godot
+    - nestif
+    - testpackage
+    - goerr113
+    - gci
+    - gofumpt
+    - exhaustivestruct
+    - nlreturn
+    - forbidigo
+    - cyclop
+    - paralleltest
+    - ifshort # so annoying
+    - golint
+    - tagliatelle
+    - forcetypeassert
+    - wrapcheck
+    - revive
+    - structcheck
+    - stylecheck
+    - exhaustive
+
+linters-settings:
+  govet:
+    check-shadowing: true
+    use-installed-packages: true
+  dupl:
+    threshold: 100
+  goconst:
+    min-len: 8
+    min-occurrences: 3
+  gocyclo:
+    min-complexity: 20
+  gocritic:
+    disabled-checks:
+      - ifElseChain
+
+
+issues:
+  max-per-linter: 0
+  max-same: 0
+  exclude-use-default: false
+  exclude:
+    # Captured by errcheck.
+    - '^(G104|G204):'
+    # Very commonly not checked.
+    - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked'
+    # Weird error only seen on Kochiku...
+    - 'internal error: no range for'
+    - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported'
+    - 'composite literal uses unkeyed fields'
+    - 'declaration of "err" shadows declaration'
+    - 'by other packages, and that stutters'
+    - 'Potential file inclusion via variable'
+    - 'at least one file in a package should have a package comment'
+    - 'bad syntax for struct tag pair'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index c056a1b..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-sudo: false
-language: go
-install: go get -t -v ./...
-go:
-    - 1.15
diff --git a/README.md b/README.md
index 78839a4..c40d7b4 100644
--- a/README.md
+++ b/README.md
@@ -1,190 +1 @@
-# Go JSON Schema Reflection
-
-[![Build Status](https://travis-ci.org/alecthomas/jsonschema.png)](https://travis-ci.org/alecthomas/jsonschema)
-[![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby)
-[![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/jsonschema)](https://goreportcard.com/report/github.com/alecthomas/jsonschema)
-[![GoDoc](https://godoc.org/github.com/alecthomas/jsonschema?status.svg)](https://godoc.org/github.com/alecthomas/jsonschema)
-
-This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection.
-
-- Supports arbitrarily complex types, including `interface{}`, maps, slices, etc.
-- Supports json-schema features such as minLength, maxLength, pattern, format, etc.
-- Supports simple string and numeric enums.
-- Supports custom property fields via the `jsonschema_extras` struct tag.
-
-## Example
-
-The following Go type:
-
-```go
-type TestUser struct {
-  ID            int                    `json:"id"`
-  Name          string                 `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"`
-  Friends       []int                  `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"`
-  Tags          map[string]interface{} `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"`
-  BirthDate     time.Time              `json:"birth_date,omitempty" jsonschema:"oneof_required=date"`
-  YearOfBirth   string                 `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"`
-  Metadata      interface{}            `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"`
-  FavColor      string                 `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"`
-}
-```
-
-Results in following JSON Schema:
-
-```go
-jsonschema.Reflect(&TestUser{})
-```
-
-```json
-{
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "$ref": "#/definitions/TestUser",
-  "definitions": {
-    "TestUser": {
-      "type": "object",
-      "properties": {
-        "metadata": {
-          "oneOf": [
-            {
-              "type": "string"
-            },
-            {
-              "type": "array"
-            }
-          ]
-        },
-        "birth_date": {
-          "type": "string",
-          "format": "date-time"
-        },
-        "friends": {
-          "type": "array",
-          "items": {
-            "type": "integer"
-          },
-          "description": "The list of IDs, omitted when empty"
-        },
-        "id": {
-          "type": "integer"
-        },
-        "name": {
-          "type": "string",
-          "title": "the name",
-          "description": "The name of a friend",
-          "default": "alex",
-          "examples": [
-            "joe",
-            "lucy"
-          ]
-        },
-        "tags": {
-          "type": "object",
-          "patternProperties": {
-            ".*": {
-              "additionalProperties": true
-            }
-          },
-          "a": "b",
-          "foo": [
-            "bar",
-            "bar1"
-          ]
-        },
-        "fav_color": {
-          "type": "string",
-          "enum": [
-            "red",
-            "green",
-            "blue"
-          ]
-        }
-      },
-      "additionalProperties": false,
-      "required": ["id", "name"],
-      "oneOf": [
-        {
-          "required": [
-            "birth_date"
-          ],
-          "title": "date"
-        },
-        {
-          "required": [
-            "year_of_birth"
-          ],
-          "title": "year"
-        }
-      ]
-    }
-  }
-}
-```
-## Configurable behaviour
-
-The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector`
-instance is created.
-
-### ExpandedStruct
-
-If set to ```true```, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type.
-
-eg.
-
-```go
-type GrandfatherType struct {
-	FamilyName string `json:"family_name" jsonschema:"required"`
-}
-
-type SomeBaseType struct {
-	SomeBaseProperty int `json:"some_base_property"`
-	// The jsonschema required tag is nonsensical for private and ignored properties.
-	// Their presence here tests that the fields *will not* be required in the output
-	// schema, even if they are tagged required.
-	somePrivateBaseProperty            string `json:"i_am_private" jsonschema:"required"`
-	SomeIgnoredBaseProperty            string `json:"-" jsonschema:"required"`
-	SomeSchemaIgnoredProperty          string `jsonschema:"-,required"`
-	SomeUntaggedBaseProperty           bool   `jsonschema:"required"`
-	someUnexportedUntaggedBaseProperty bool
-	Grandfather                        GrandfatherType `json:"grand"`
-}
-```
-
-will output:
-
-```json
-{
-  "$schema": "http://json-schema.org/draft-04/schema#",
-  "required": [
-    "some_base_property",
-    "grand",
-    "SomeUntaggedBaseProperty"
-  ],
-  "properties": {
-    "SomeUntaggedBaseProperty": {
-      "type": "boolean"
-    },
-    "grand": {
-      "$schema": "http://json-schema.org/draft-04/schema#",
-      "$ref": "#/definitions/GrandfatherType"
-    },
-    "some_base_property": {
-      "type": "integer"
-    }
-  },
-  "type": "object",
-  "definitions": {
-    "GrandfatherType": {
-      "required": [
-        "family_name"
-      ],
-      "properties": {
-        "family_name": {
-          "type": "string"
-        }
-      },
-      "additionalProperties": false,
-      "type": "object"
-    }
-  }
-}
-```
+# Maintenance of this project has moved to [invopop/jsonschema](https://github.com/invopop/jsonschema).
diff --git a/bin/.go@latest.pkg b/bin/.go@latest.pkg
new file mode 120000
index 0000000..383f451
--- /dev/null
+++ b/bin/.go@latest.pkg
@@ -0,0 +1 @@
+hermit
\ No newline at end of file
diff --git a/bin/.golangci-lint-1.42.1.pkg b/bin/.golangci-lint-1.42.1.pkg
new file mode 120000
index 0000000..383f451
--- /dev/null
+++ b/bin/.golangci-lint-1.42.1.pkg
@@ -0,0 +1 @@
+hermit
\ No newline at end of file
diff --git a/bin/README.hermit.md b/bin/README.hermit.md
new file mode 100644
index 0000000..e889550
--- /dev/null
+++ b/bin/README.hermit.md
@@ -0,0 +1,7 @@
+# Hermit environment
+
+This is a [Hermit](https://github.com/cashapp/hermit) bin directory.
+
+The symlinks in this directory are managed by Hermit and will automatically
+download and install Hermit itself as well as packages. These packages are
+local to this environment.
diff --git a/bin/activate-hermit b/bin/activate-hermit
new file mode 100755
index 0000000..3b191fb
--- /dev/null
+++ b/bin/activate-hermit
@@ -0,0 +1,19 @@
+#!/bin/bash
+# This file must be used with "source bin/activate-hermit" from bash or zsh.
+# You cannot run it directly
+
+if [ "${BASH_SOURCE-}" = "$0" ]; then
+  echo "You must source this script: \$ source $0" >&2
+  exit 33
+fi
+
+BIN_DIR="$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")"
+if "${BIN_DIR}/hermit" noop > /dev/null; then
+  eval "$("${BIN_DIR}/hermit" activate "${BIN_DIR}/..")"
+
+  if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ]; then
+      hash -r 2>/dev/null
+    fi
+
+    echo "Hermit environment $("${HERMIT_ENV}"/bin/hermit env HERMIT_ENV) activated"
+fi
diff --git a/bin/go b/bin/go
new file mode 120000
index 0000000..142d6ff
--- /dev/null
+++ b/bin/go
@@ -0,0 +1 @@
+.go@latest.pkg
\ No newline at end of file
diff --git a/bin/gofmt b/bin/gofmt
new file mode 120000
index 0000000..142d6ff
--- /dev/null
+++ b/bin/gofmt
@@ -0,0 +1 @@
+.go@latest.pkg
\ No newline at end of file
diff --git a/bin/golangci-lint b/bin/golangci-lint
new file mode 120000
index 0000000..ae19155
--- /dev/null
+++ b/bin/golangci-lint
@@ -0,0 +1 @@
+.golangci-lint-1.42.1.pkg
\ No newline at end of file
diff --git a/bin/hermit b/bin/hermit
new file mode 100755
index 0000000..d13a24d
--- /dev/null
+++ b/bin/hermit
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+set -eo pipefail
+
+if [ -z "${HERMIT_STATE_DIR}" ]; then
+  case "$(uname -s)" in
+  Darwin)
+    export HERMIT_STATE_DIR="${HOME}/Library/Caches/hermit"
+    ;;
+  Linux)
+    export HERMIT_STATE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/hermit"
+    ;;
+  esac
+fi
+
+export HERMIT_DIST_URL="${HERMIT_DIST_URL:-https://github.com/cashapp/hermit/releases/download/stable}"
+HERMIT_CHANNEL="$(basename "${HERMIT_DIST_URL}")"
+export HERMIT_CHANNEL
+export HERMIT_EXE=${HERMIT_EXE:-${HERMIT_STATE_DIR}/pkg/hermit@${HERMIT_CHANNEL}/hermit}
+
+if [ ! -x "${HERMIT_EXE}" ]; then
+  echo "Bootstrapping ${HERMIT_EXE} from ${HERMIT_DIST_URL}" 1>&2
+  curl -fsSL "${HERMIT_DIST_URL}/install.sh" | /bin/bash 1>&2
+fi
+
+exec "${HERMIT_EXE}" --level=fatal exec "$0" -- "$@"
diff --git a/bin/hermit.hcl b/bin/hermit.hcl
new file mode 100644
index 0000000..e69de29
diff --git a/comment_extractor.go b/comment_extractor.go
new file mode 100644
index 0000000..0088b41
--- /dev/null
+++ b/comment_extractor.go
@@ -0,0 +1,90 @@
+package jsonschema
+
+import (
+	"fmt"
+	"io/fs"
+	gopath "path"
+	"path/filepath"
+	"strings"
+
+	"go/ast"
+	"go/doc"
+	"go/parser"
+	"go/token"
+)
+
+// ExtractGoComments will read all the go files contained in the provided path,
+// including sub-directories, in order to generate a dictionary of comments
+// associated with Types and Fields. The results will be added to the `commentsMap`
+// provided in the parameters and expected to be used for Schema "description" fields.
+//
+// The `go/parser` library is used to extract all the comments and unfortunately doesn't
+// have a built-in way to determine the fully qualified name of a package. The `base` paremeter,
+// the URL used to import that package, is thus required to be able to match reflected types.
+//
+// When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase
+// only. Field comments, which tend to be much shorter, will include everything.
+func ExtractGoComments(base, path string, commentMap map[string]string) error {
+	fset := token.NewFileSet()
+	dict := make(map[string][]*ast.Package)
+	err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if info.IsDir() {
+			d, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
+			if err != nil {
+				return err
+			}
+			for _, v := range d {
+				// paths may have multiple packages, like for tests
+				k := gopath.Join(base, path)
+				dict[k] = append(dict[k], v)
+			}
+		}
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	for pkg, p := range dict {
+		for _, f := range p {
+			gtxt := ""
+			typ := ""
+			ast.Inspect(f, func(n ast.Node) bool {
+				switch x := n.(type) {
+				case *ast.TypeSpec:
+					typ = x.Name.String()
+					if !ast.IsExported(typ) {
+						typ = ""
+					} else {
+						txt := x.Doc.Text()
+						if txt == "" && gtxt != "" {
+							txt = gtxt
+							gtxt = ""
+						}
+						txt = doc.Synopsis(txt)
+						commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt)
+					}
+				case *ast.Field:
+					txt := x.Doc.Text()
+					if typ != "" && txt != "" {
+						for _, n := range x.Names {
+							if ast.IsExported(n.String()) {
+								k := fmt.Sprintf("%s.%s.%s", pkg, typ, n)
+								commentMap[k] = strings.TrimSpace(txt)
+							}
+						}
+					}
+				case *ast.GenDecl:
+					// remember for the next type
+					gtxt = x.Doc.Text()
+				}
+				return true
+			})
+		}
+	}
+
+	return nil
+}
diff --git a/debian/changelog b/debian/changelog
index d6e50dd..51b0fc9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-alecthomas-jsonschema (0.0~git20220216.9eeeec9-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 19 Jan 2023 07:00:55 -0000
+
 golang-github-alecthomas-jsonschema (0.0~git20210127.19bc6f2-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/examples/nested/nested.go b/examples/nested/nested.go
new file mode 100644
index 0000000..953c499
--- /dev/null
+++ b/examples/nested/nested.go
@@ -0,0 +1,15 @@
+package nested
+
+// Pet defines the user's fury friend.
+type Pet struct {
+	// Name of the animal.
+	Name string `json:"name" jsonschema:"title=Name"`
+}
+
+type (
+	// Plant represents the plants the user might have and serves as a test
+	// of structs inside a `type` set.
+	Plant struct {
+		Variant string `json:"variant" jsonschema:"title=Variant"` // This comment will be ignored
+	}
+)
diff --git a/examples/user.go b/examples/user.go
new file mode 100644
index 0000000..12ac497
--- /dev/null
+++ b/examples/user.go
@@ -0,0 +1,22 @@
+package examples
+
+import (
+	"github.com/alecthomas/jsonschema/examples/nested"
+)
+
+// User is used as a base to provide tests for comments.
+// Don't forget to checkout the nested path.
+type User struct {
+	// Unique sequential identifier.
+	ID int `json:"id" jsonschema:"required"`
+	// This comment will be ignored
+	Name    string                 `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"`
+	Friends []int                  `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"`
+	Tags    map[string]interface{} `json:"tags,omitempty"`
+
+	// An array of pets the user cares for.
+	Pets []*nested.Pet `json:"pets"`
+
+	// Set of plants that the user likes
+	Plants []*nested.Plant `json:"plants" jsonschema:"title=Pants"`
+}
diff --git a/fixtures/allow_additional_props.json b/fixtures/allow_additional_props.json
index 623e5ca..febe259 100644
--- a/fixtures/allow_additional_props.json
+++ b/fixtures/allow_additional_props.json
@@ -23,11 +23,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -60,7 +63,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -114,7 +122,7 @@
             }
           ]
         },
-        "age":{
+        "age": {
           "maximum": 120,
           "exclusiveMaximum": true,
           "minimum": 18,
@@ -156,10 +164,46 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": true,
       "type": "object"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/fixtures/compact_date.json b/fixtures/compact_date.json
new file mode 100644
index 0000000..478187d
--- /dev/null
+++ b/fixtures/compact_date.json
@@ -0,0 +1,12 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/CompactDate",
+  "definitions": {
+    "CompactDate": {
+      "pattern": "^[0-9]{4}-[0-1][0-9]$",
+      "type": "string",
+      "title": "Compact Date",
+      "description": "Short date that only includes year and month"
+    }
+  }
+}
\ No newline at end of file
diff --git a/fixtures/custom_additional.json b/fixtures/custom_additional.json
new file mode 100644
index 0000000..b3a841c
--- /dev/null
+++ b/fixtures/custom_additional.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "http:\/\/json-schema.org\/draft-04\/schema#",
+  "$ref": "#\/definitions\/GrandfatherType",
+  "definitions": {
+    "GrandfatherType": {
+      "required": [
+        "family_name",
+        "ip_addr"
+      ],
+      "properties": {
+        "family_name": {
+          "type": "string"
+        },
+        "ip_addr": {
+          "type": "string",
+          "format": "ipv4"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/fixtures/custom_map_type.json b/fixtures/custom_map_type.json
new file mode 100644
index 0000000..7d6f188
--- /dev/null
+++ b/fixtures/custom_map_type.json
@@ -0,0 +1,37 @@
+{
+	"$schema": "http://json-schema.org/draft-04/schema#",
+	"$ref": "#/definitions/CustomMapOuter",
+	"definitions": {
+		"CustomMapOuter": {
+			"type": "object",
+			"required": [
+				"my_map"
+			],
+			"additionalProperties": false,
+			"properties": {
+				"my_map": {
+					"$schema": "http://json-schema.org/draft-04/schema#",
+					"$ref": "#/definitions/CustomMapType"
+				}
+			}
+		},
+		"CustomMapType": {
+			"items": {
+				"required": [
+					"key",
+					"value"
+				],
+				"properties": {
+					"key": {
+						"type": "string"
+					},
+					"value": {
+						"type": "string"
+					}
+				},
+				"type": "object"
+			},
+			"type": "array"
+		}
+	}
+}
diff --git a/fixtures/custom_slice_type.json b/fixtures/custom_slice_type.json
new file mode 100644
index 0000000..dee08f4
--- /dev/null
+++ b/fixtures/custom_slice_type.json
@@ -0,0 +1,32 @@
+{
+	"$schema": "http://json-schema.org/draft-04/schema#",
+	"$ref": "#/definitions/CustomSliceOuter",
+	"definitions": {
+		"CustomSliceOuter": {
+			"type": "object",
+			"required": [
+				"slice"
+			],
+			"additionalProperties": false,
+			"properties": {
+				"slice": {
+					"$schema": "http://json-schema.org/draft-04/schema#",
+					"$ref": "#/definitions/CustomSliceType"
+				}
+			}
+		},
+		"CustomSliceType": {
+			"oneOf": [
+				{
+					"type": "string"
+				},
+				{
+					"items": {
+						"type": "string"
+					},
+					"type": "array"
+				}
+			]
+		}
+	}
+}
diff --git a/fixtures/custom_type_with_interface.json b/fixtures/custom_type_with_interface.json
new file mode 100644
index 0000000..8e8d275
--- /dev/null
+++ b/fixtures/custom_type_with_interface.json
@@ -0,0 +1,23 @@
+{
+	"$schema": "http://json-schema.org/draft-04/schema#",
+	"$ref": "#/definitions/CustomTypeFieldWithInterface",
+	"definitions": {
+		"CustomTypeFieldWithInterface": {
+			"required": [
+				"CreatedAt"
+			],
+			"properties": {
+				"CreatedAt": {
+					"$schema": "http://json-schema.org/draft-04/schema#",
+					"$ref": "#/definitions/CustomTimeWithInterface"
+				}
+			},
+			"additionalProperties": false,
+			"type": "object"
+		},
+		"CustomTimeWithInterface": {
+			"type": "string",
+			"format": "date-time"
+		}
+	}
+}
diff --git a/fixtures/defaults.json b/fixtures/defaults.json
index b3a4c9a..f2fcfa9 100644
--- a/fixtures/defaults.json
+++ b/fixtures/defaults.json
@@ -23,11 +23,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -60,7 +63,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -156,6 +164,42 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
diff --git a/fixtures/defaults_expanded_toplevel.json b/fixtures/defaults_expanded_toplevel.json
index 4bb3bac..859e690 100644
--- a/fixtures/defaults_expanded_toplevel.json
+++ b/fixtures/defaults_expanded_toplevel.json
@@ -8,11 +8,14 @@
     "PublicNonExported",
     "id",
     "name",
+    "password",
     "TestFlag",
     "age",
     "email",
     "Baz",
-    "color"
+    "color",
+    "roles",
+    "raw"
   ],
   "properties": {
     "some_base_property": {
@@ -35,17 +38,22 @@
       "type": "integer"
     },
     "name": {
-        "maxLength": 20,
-        "minLength": 1,
-        "pattern": ".*",
-        "type": "string",
-        "title": "the name",
-        "description": "this is a property",
-        "default": "alex",
-        "examples": [
-          "joe",
-          "lucy"
-        ]
+      "maxLength": 20,
+      "minLength": 1,
+      "pattern": ".*",
+      "type": "string",
+      "title": "the name",
+      "description": "this is a property",
+      "default": "alex",
+      "examples": [
+        "joe",
+        "lucy"
+      ],
+      "readOnly": true
+    },
+    "password": {
+      "type": "string",
+      "writeOnly": true
     },
     "friends": {
       "items": {
@@ -99,7 +107,7 @@
         }
       ]
     },
-    "age":{
+    "age": {
       "maximum": 120,
       "exclusiveMaximum": true,
       "minimum": 18,
@@ -112,10 +120,10 @@
     },
     "Baz": {
       "type": "string",
-       "foo": [
-          "bar",
-          "bar1"
-        ],
+      "foo": [
+        "bar",
+        "bar1"
+      ],
       "hello": "world"
     },
     "color": {
@@ -141,6 +149,42 @@
         2.0
       ],
       "type": "number"
+    },
+    "roles": {
+      "items": {
+        "enum": [
+          "admin",
+          "moderator",
+          "user"
+        ],
+        "type": "string"
+      },
+      "type": "array"
+    },
+    "priorities": {
+      "items": {
+        "enum": [
+          -1,
+          0,
+          1
+        ],
+        "type": "integer"
+      },
+      "type": "array"
+    },
+    "offsets": {
+      "items": {
+        "enum": [
+          1.570796,
+          3.141592,
+          6.283185
+        ],
+        "type": "number"
+      },
+      "type": "array"
+    },
+    "raw": {
+      "additionalProperties": true
     }
   },
   "additionalProperties": false,
@@ -159,4 +203,4 @@
       "type": "object"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/fixtures/fully_qualified.json b/fixtures/fully_qualified.json
index 9b3041d..1c1a303 100644
--- a/fixtures/fully_qualified.json
+++ b/fixtures/fully_qualified.json
@@ -23,11 +23,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -60,7 +63,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -156,6 +164,42 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
diff --git a/fixtures/go_comments.json b/fixtures/go_comments.json
new file mode 100644
index 0000000..dc88065
--- /dev/null
+++ b/fixtures/go_comments.json
@@ -0,0 +1,97 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/User",
+  "definitions": {
+    "Pet": {
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "name": {
+          "type": "string",
+          "title": "Name",
+          "description": "Name of the animal."
+        }
+      },
+      "additionalProperties": false,
+      "type": "object",
+      "description": "Pet defines the user's fury friend."
+    },
+    "Plant": {
+      "required": [
+        "variant"
+      ],
+      "properties": {
+        "variant": {
+          "type": "string",
+          "title": "Variant"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object",
+      "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set."
+    },
+    "User": {
+      "required": [
+        "id",
+        "name",
+        "pets",
+        "plants"
+      ],
+      "properties": {
+        "id": {
+          "type": "integer",
+          "description": "Unique sequential identifier."
+        },
+        "name": {
+          "maxLength": 20,
+          "minLength": 1,
+          "pattern": ".*",
+          "type": "string",
+          "title": "the name",
+          "description": "this is a property",
+          "default": "alex",
+          "examples": [
+            "joe",
+            "lucy"
+          ]
+        },
+        "friends": {
+          "items": {
+            "type": "integer"
+          },
+          "type": "array",
+          "description": "list of IDs, omitted when empty"
+        },
+        "tags": {
+          "patternProperties": {
+            ".*": {
+              "additionalProperties": true
+            }
+          },
+          "type": "object"
+        },
+        "pets": {
+          "items": {
+            "$schema": "http://json-schema.org/draft-04/schema#",
+            "$ref": "#/definitions/Pet"
+          },
+          "type": "array",
+          "description": "An array of pets the user cares for."
+        },
+        "plants": {
+          "items": {
+            "$schema": "http://json-schema.org/draft-04/schema#",
+            "$ref": "#/definitions/Plant"
+          },
+          "type": "array",
+          "title": "Pants",
+          "description": "Set of plants that the user likes"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object",
+      "description": "User is used as a base to provide tests for comments."
+    }
+  }
+}
\ No newline at end of file
diff --git a/fixtures/ignore_type.json b/fixtures/ignore_type.json
index c36d9fc..b21451a 100644
--- a/fixtures/ignore_type.json
+++ b/fixtures/ignore_type.json
@@ -16,11 +16,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -53,7 +56,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -149,10 +157,46 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
       "type": "object"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/fixtures/no_ref_qual_types.json b/fixtures/no_ref_qual_types.json
index c53688c..85bf476 100644
--- a/fixtures/no_ref_qual_types.json
+++ b/fixtures/no_ref_qual_types.json
@@ -7,11 +7,14 @@
     "PublicNonExported",
     "id",
     "name",
+    "password",
     "TestFlag",
     "age",
     "email",
     "Baz",
-    "color"
+    "color",
+    "roles",
+    "raw"
   ],
   "properties": {
     "some_base_property": {
@@ -52,7 +55,12 @@
       "examples": [
         "joe",
         "lucy"
-      ]
+      ],
+      "readOnly": true
+    },
+    "password": {
+      "type": "string",
+      "writeOnly": true
     },
     "friends": {
       "items": {
@@ -148,6 +156,42 @@
         2
       ],
       "type": "number"
+    },
+    "roles": {
+      "items": {
+        "enum": [
+          "admin",
+          "moderator",
+          "user"
+        ],
+        "type": "string"
+      },
+      "type": "array"
+    },
+    "priorities": {
+      "items": {
+        "enum": [
+          -1,
+          0,
+          1
+        ],
+        "type": "integer"
+      },
+      "type": "array"
+    },
+    "offsets": {
+      "items": {
+        "enum": [
+          1.570796,
+          3.141592,
+          6.283185
+        ],
+        "type": "number"
+      },
+      "type": "array"
+    },
+    "raw": {
+      "additionalProperties": true
     }
   },
   "additionalProperties": false,
@@ -174,11 +218,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -219,7 +266,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -315,6 +367,42 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
diff --git a/fixtures/no_reference.json b/fixtures/no_reference.json
index ba1f8d0..6a51127 100644
--- a/fixtures/no_reference.json
+++ b/fixtures/no_reference.json
@@ -7,11 +7,14 @@
     "PublicNonExported",
     "id",
     "name",
+    "password",
     "TestFlag",
     "age",
     "email",
     "Baz",
-    "color"
+    "color",
+    "roles",
+    "raw"
   ],
   "properties": {
     "some_base_property": {
@@ -52,7 +55,12 @@
       "examples": [
         "joe",
         "lucy"
-      ]
+      ],
+      "readOnly": true
+    },
+    "password": {
+      "type": "string",
+      "writeOnly": true
     },
     "friends": {
       "items": {
@@ -148,6 +156,42 @@
         2
       ],
       "type": "number"
+    },
+    "roles": {
+      "items": {
+        "enum": [
+          "admin",
+          "moderator",
+          "user"
+        ],
+        "type": "string"
+      },
+      "type": "array"
+    },
+    "priorities": {
+      "items": {
+        "enum": [
+          -1,
+          0,
+          1
+        ],
+        "type": "integer"
+      },
+      "type": "array"
+    },
+    "offsets": {
+      "items": {
+        "enum": [
+          1.570796,
+          3.141592,
+          6.283185
+        ],
+        "type": "number"
+      },
+      "type": "array"
+    },
+    "raw": {
+      "additionalProperties": true
     }
   },
   "additionalProperties": false,
@@ -174,11 +218,14 @@
         "PublicNonExported",
         "id",
         "name",
+        "password",
         "TestFlag",
         "age",
         "email",
         "Baz",
-        "color"
+        "color",
+        "roles",
+        "raw"
       ],
       "properties": {
         "some_base_property": {
@@ -219,7 +266,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -315,6 +367,42 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
diff --git a/fixtures/required_from_jsontags.json b/fixtures/required_from_jsontags.json
index a27ad19..11fb53f 100644
--- a/fixtures/required_from_jsontags.json
+++ b/fixtures/required_from_jsontags.json
@@ -53,7 +53,12 @@
           "examples": [
             "joe",
             "lucy"
-          ]
+          ],
+          "readOnly": true
+        },
+        "password": {
+          "type": "string",
+          "writeOnly": true
         },
         "friends": {
           "items": {
@@ -107,7 +112,7 @@
             }
           ]
         },
-        "age":{
+        "age": {
           "maximum": 120,
           "exclusiveMaximum": true,
           "minimum": 18,
@@ -149,10 +154,46 @@
             2
           ],
           "type": "number"
+        },
+        "roles": {
+          "items": {
+            "enum": [
+              "admin",
+              "moderator",
+              "user"
+            ],
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "priorities": {
+          "items": {
+            "enum": [
+              -1,
+              0,
+              1
+            ],
+            "type": "integer"
+          },
+          "type": "array"
+        },
+        "offsets": {
+          "items": {
+            "enum": [
+              1.570796,
+              3.141592,
+              6.283185
+            ],
+            "type": "number"
+          },
+          "type": "array"
+        },
+        "raw": {
+          "additionalProperties": true
         }
       },
       "additionalProperties": false,
       "type": "object"
     }
   }
-}
+}
\ No newline at end of file
diff --git a/fixtures/schema_with_minimum.json b/fixtures/schema_with_minimum.json
new file mode 100644
index 0000000..4522324
--- /dev/null
+++ b/fixtures/schema_with_minimum.json
@@ -0,0 +1,19 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "$ref": "#/definitions/MinValue",
+    "definitions": {
+      "MinValue": {
+        "required": [
+          "value4"
+        ],
+        "properties": {
+          "value4": {
+            "type": "integer",
+            "minimum": 0
+          }
+        },
+        "additionalProperties": false,
+        "type": "object"
+      }
+    }
+  }
\ No newline at end of file
diff --git a/fixtures/test_yaml_and_json.json b/fixtures/test_yaml_and_json.json
new file mode 100644
index 0000000..0999cb6
--- /dev/null
+++ b/fixtures/test_yaml_and_json.json
@@ -0,0 +1,25 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/TestYamlAndJson",
+  "definitions": {
+    "TestYamlAndJson": {
+      "required": ["FirstName", "LastName", "age"],
+      "properties": {
+        "FirstName": {
+          "type": "string"
+        },
+        "LastName": {
+          "type": "string"
+        },
+        "age": {
+          "type": "integer"
+        },
+        "MiddleName": {
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/fixtures/test_yaml_and_json2.json b/fixtures/test_yaml_and_json2.json
new file mode 100644
index 0000000..48497e8
--- /dev/null
+++ b/fixtures/test_yaml_and_json2.json
@@ -0,0 +1,29 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/TestYamlAndJson2",
+  "definitions": {
+    "TestYamlAndJson2": {
+      "required": ["FirstName", "LastName", "age"],
+      "properties": {
+        "FirstName": {
+          "type": "string",
+          "description": "test2"
+        },
+        "LastName": {
+          "type": "string",
+          "description": "test3"
+        },
+        "age": {
+          "type": "integer",
+          "description": "test4"
+        },
+        "MiddleName": {
+          "type": "string",
+          "description": "test5"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/fixtures/test_yaml_and_json_prefer_yaml.json b/fixtures/test_yaml_and_json_prefer_yaml.json
new file mode 100644
index 0000000..9133d86
--- /dev/null
+++ b/fixtures/test_yaml_and_json_prefer_yaml.json
@@ -0,0 +1,25 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/TestYamlAndJson",
+  "definitions": {
+    "TestYamlAndJson": {
+      "required": ["first_name", "LastName", "age"],
+      "properties": {
+        "first_name": {
+          "type": "string"
+        },
+        "LastName": {
+          "type": "string"
+        },
+        "age": {
+          "type": "integer"
+        },
+        "middle_name": {
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/fixtures/yaml_inline.json b/fixtures/yaml_inline.json
new file mode 100644
index 0000000..46d561d
--- /dev/null
+++ b/fixtures/yaml_inline.json
@@ -0,0 +1,29 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/TestYamlInline",
+  "definitions": {
+    "Inner": {
+      "required": ["foo"],
+      "properties": {
+        "foo": {
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    },
+    "TestYamlInline": {
+      "required": [
+        "Inlined"
+      ],
+      "properties": {
+        "Inlined": {
+          "$schema": "http://json-schema.org/draft-04/schema#",
+          "$ref": "#/definitions/Inner"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/fixtures/yaml_inline_embed.json b/fixtures/yaml_inline_embed.json
new file mode 100644
index 0000000..4b2f064
--- /dev/null
+++ b/fixtures/yaml_inline_embed.json
@@ -0,0 +1,16 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "$ref": "#/definitions/TestYamlInline",
+  "definitions": {
+    "TestYamlInline": {
+      "required": ["foo"],
+      "properties": {
+        "foo": {
+          "type": "string"
+        }
+      },
+      "additionalProperties": false,
+      "type": "object"
+    }
+  }
+}
diff --git a/reflect.go b/reflect.go
index 64de721..34ca4af 100644
--- a/reflect.go
+++ b/reflect.go
@@ -30,6 +30,24 @@ type Schema struct {
 	Definitions Definitions
 }
 
+// customSchemaType is used to detect if the type provides it's own
+// custom Schema Type definition to use instead. Very useful for situations
+// where there are custom JSON Marshal and Unmarshal methods.
+type customSchemaType interface {
+	JSONSchemaType() *Type
+}
+
+var customType = reflect.TypeOf((*customSchemaType)(nil)).Elem()
+
+// customSchemaGetFieldDocString
+type customSchemaGetFieldDocString interface {
+	GetFieldDocString(fieldName string) string
+}
+
+type customGetFieldDocString func(fieldName string) string
+
+var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem()
+
 // Type represents a JSON Schema object type.
 type Type struct {
 	// RFC draft-wright-json-schema-00
@@ -69,6 +87,9 @@ type Type struct {
 	Default     interface{}   `json:"default,omitempty"`     // section 6.2
 	Format      string        `json:"format,omitempty"`      // section 7
 	Examples    []interface{} `json:"examples,omitempty"`    // section 7.4
+	// RFC draft-handrews-json-schema-validation-02, section 9.4
+	ReadOnly  bool `json:"readOnly,omitempty"`
+	WriteOnly bool `json:"writeOnly,omitempty"`
 	// RFC draft-wright-json-schema-hyperschema-00, section 4
 	Media          *Type  `json:"media,omitempty"`          // section 4.3
 	BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3
@@ -106,6 +127,10 @@ type Reflector struct {
 	// used with yaml.Marshal/Unmarshal.
 	YAMLEmbeddedStructs bool
 
+	// Prefer yaml: tags over json: tags to generate the schema even if json: tags
+	// are present
+	PreferYAMLSchema bool
+
 	// ExpandedStruct will cause the toplevel definitions of the schema not
 	// be referenced itself to a definition.
 	ExpandedStruct bool
@@ -128,8 +153,30 @@ type Reflector struct {
 	// switching to just allowing additional properties instead.
 	IgnoredTypes []interface{}
 
-	// TypeMapper is a function that can be used to map custom Go types to jsconschema types.
+	// TypeMapper is a function that can be used to map custom Go types to jsonschema types.
 	TypeMapper func(reflect.Type) *Type
+
+	// TypeNamer allows customizing of type names
+	TypeNamer func(reflect.Type) string
+
+	// AdditionalFields allows adding structfields for a given type
+	AdditionalFields func(reflect.Type) []reflect.StructField
+
+	// CommentMap is a dictionary of fully qualified go types and fields to comment
+	// strings that will be used if a description has not already been provided in
+	// the tags. Types and fields are added to the package path using "." as a
+	// separator.
+	//
+	// Type descriptions should be defined like:
+	//
+	//   map[string]string{"github.com/alecthomas/jsonschema.Reflector": "A Reflector reflects values into a Schema."}
+	//
+	// And Fields defined as:
+	//
+	//   map[string]string{"github.com/alecthomas/jsonschema.Reflector.DoNotReference": "Do not reference definitions."}
+	//
+	// See also: AddGoComments
+	CommentMap map[string]string
 }
 
 // Reflect reflects to Schema from a value.
@@ -179,6 +226,9 @@ var (
 // Byte slices will be encoded as base64
 var byteSliceType = reflect.TypeOf([]byte(nil))
 
+// Except for json.RawMessage
+var rawMessageType = reflect.TypeOf(json.RawMessage{})
+
 // Go code generated from protobuf enum types should fulfil this interface.
 type protoEnum interface {
 	EnumDescriptor() ([]byte, []int)
@@ -192,6 +242,16 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
 		return &Type{Ref: "#/definitions/" + r.typeName(t)}
 	}
 
+	if r.TypeMapper != nil {
+		if t := r.TypeMapper(t); t != nil {
+			return t
+		}
+	}
+
+	if rt := r.reflectCustomType(definitions, t); rt != nil {
+		return rt
+	}
+
 	// jsonpb will marshal protobuf enum options as either strings or integers.
 	// It will unmarshal either.
 	if t.Implements(protoEnumType) {
@@ -201,24 +261,16 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
 		}}
 	}
 
-	if r.TypeMapper != nil {
-		if t := r.TypeMapper(t); t != nil {
-			return t
-		}
-	}
-
 	// Defined format types for JSON Schema Validation
 	// RFC draft-wright-json-schema-validation-00, section 7.3
 	// TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7
-	switch t {
-	case ipType:
+	if t == ipType {
 		// TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5
 		return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4
 	}
 
 	switch t.Kind() {
 	case reflect.Struct:
-
 		switch t {
 		case timeType: // date-time RFC section 7.3.1
 			return &Type{Type: "string", Format: "date-time"}
@@ -229,6 +281,18 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
 		}
 
 	case reflect.Map:
+		switch t.Key().Kind() {
+		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+			rt := &Type{
+				Type: "object",
+				PatternProperties: map[string]*Type{
+					"^[0-9]+$": r.reflectTypeToSchema(definitions, t.Elem()),
+				},
+				AdditionalProperties: []byte("false"),
+			}
+			return rt
+		}
+
 		rt := &Type{
 			Type: "object",
 			PatternProperties: map[string]*Type{
@@ -240,6 +304,11 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
 
 	case reflect.Slice, reflect.Array:
 		returnType := &Type{}
+		if t == rawMessageType {
+			return &Type{
+				AdditionalProperties: []byte("true"),
+			}
+		}
 		if t.Kind() == reflect.Array {
 			returnType.MinItems = t.Len()
 			returnType.MaxItems = returnType.MinItems
@@ -277,8 +346,35 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type)
 	panic("unsupported type " + t.String())
 }
 
-// Refects a struct to a JSON Schema type.
+func (r *Reflector) reflectCustomType(definitions Definitions, t reflect.Type) *Type {
+	if t.Kind() == reflect.Ptr {
+		return r.reflectCustomType(definitions, t.Elem())
+	}
+
+	if t.Implements(customType) {
+		v := reflect.New(t)
+		o := v.Interface().(customSchemaType)
+		st := o.JSONSchemaType()
+		definitions[r.typeName(t)] = st
+		if r.DoNotReference {
+			return st
+		} else {
+			return &Type{
+				Version: Version,
+				Ref:     "#/definitions/" + r.typeName(t),
+			}
+		}
+	}
+
+	return nil
+}
+
+// Reflects a struct to a JSON Schema type.
 func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type {
+	if st := r.reflectCustomType(definitions, t); st != nil {
+		return st
+	}
+
 	for _, ignored := range r.IgnoredTypes {
 		if reflect.TypeOf(ignored) == t {
 			st := &Type{
@@ -296,13 +392,14 @@ func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type
 					Ref:     "#/definitions/" + r.typeName(t),
 				}
 			}
-
 		}
 	}
+
 	st := &Type{
 		Type:                 "object",
 		Properties:           orderedmap.New(),
 		AdditionalProperties: []byte("false"),
+		Description:          r.lookupComment(t, ""),
 	}
 	if r.AllowAdditionalProperties {
 		st.AdditionalProperties = []byte("true")
@@ -327,20 +424,33 @@ func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t ref
 	if t.Kind() != reflect.Struct {
 		return
 	}
-	for i := 0; i < t.NumField(); i++ {
-		f := t.Field(i)
-		name, exist, required, nullable := r.reflectFieldName(f)
+
+	var getFieldDocString customGetFieldDocString
+	if t.Implements(customStructGetFieldDocString) {
+		v := reflect.New(t)
+		o := v.Interface().(customSchemaGetFieldDocString)
+		getFieldDocString = o.GetFieldDocString
+	}
+
+	handleField := func(f reflect.StructField) {
+		name, shouldEmbed, required, nullable := r.reflectFieldName(f)
 		// if anonymous and exported type should be processed recursively
 		// current type should inherit properties of anonymous one
 		if name == "" {
-			if f.Anonymous && !exist {
+			if shouldEmbed {
 				r.reflectStructFields(st, definitions, f.Type)
 			}
-			continue
+			return
 		}
 
 		property := r.reflectTypeToSchema(definitions, f.Type)
 		property.structKeywordsFromTags(f, st, name)
+		if property.Description == "" {
+			property.Description = r.lookupComment(t, f.Name)
+		}
+		if getFieldDocString != nil {
+			property.Description = getFieldDocString(f.Name)
+		}
 
 		if nullable {
 			property = &Type{
@@ -358,6 +468,31 @@ func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t ref
 			st.Required = append(st.Required, name)
 		}
 	}
+
+	for i := 0; i < t.NumField(); i++ {
+		f := t.Field(i)
+		handleField(f)
+	}
+	if r.AdditionalFields != nil {
+		if af := r.AdditionalFields(t); af != nil {
+			for _, sf := range af {
+				handleField(sf)
+			}
+		}
+	}
+}
+
+func (r *Reflector) lookupComment(t reflect.Type, name string) string {
+	if r.CommentMap == nil {
+		return ""
+	}
+
+	n := fullyQualifiedTypeName(t)
+	if name != "" {
+		n = n + "." + name
+	}
+
+	return r.CommentMap[n]
 }
 
 func (t *Type) structKeywordsFromTags(f reflect.StructField, parentType *Type, propertyName string) {
@@ -454,6 +589,12 @@ func (t *Type) stringKeywords(tags []string) {
 					t.Format = val
 					break
 				}
+			case "readOnly":
+				i, _ := strconv.ParseBool(val)
+				t.ReadOnly = i
+			case "writeOnly":
+				i, _ := strconv.ParseBool(val)
+				t.WriteOnly = i
 			case "default":
 				t.Default = val
 			case "example":
@@ -531,6 +672,17 @@ func (t *Type) arrayKeywords(tags []string) {
 				t.UniqueItems = true
 			case "default":
 				defaultValues = append(defaultValues, val)
+			case "enum":
+				switch t.Items.Type {
+				case "string":
+					t.Items.Enum = append(t.Items.Enum, val)
+				case "integer":
+					i, _ := strconv.Atoi(val)
+					t.Items.Enum = append(t.Items.Enum, i)
+				case "number":
+					f, _ := strconv.ParseFloat(val, 64)
+					t.Items.Enum = append(t.Items.Enum, f)
+				}
 			}
 		}
 	}
@@ -553,14 +705,21 @@ func (t *Type) setExtra(key, val string) {
 		t.Extras = map[string]interface{}{}
 	}
 	if existingVal, ok := t.Extras[key]; ok {
-		switch existingVal.(type) {
+		switch existingVal := existingVal.(type) {
 		case string:
-			t.Extras[key] = []string{existingVal.(string), val}
+			t.Extras[key] = []string{existingVal, val}
 		case []string:
-			t.Extras[key] = append(existingVal.([]string), val)
+			t.Extras[key] = append(existingVal, val)
+		case int:
+			t.Extras[key], _ = strconv.Atoi(val)
 		}
 	} else {
-		t.Extras[key] = val
+		switch key {
+		case "minimum":
+			t.Extras[key], _ = strconv.Atoi(val)
+		default:
+			t.Extras[key] = val
+		}
 	}
 }
 
@@ -601,6 +760,15 @@ func nullableFromJSONSchemaTags(tags []string) bool {
 	return false
 }
 
+func inlineYAMLTags(tags []string) bool {
+	for _, tag := range tags {
+		if tag == "inline" {
+			return true
+		}
+	}
+	return false
+}
+
 func ignoredByJSONTags(tags []string) bool {
 	return tags[0] == "-"
 }
@@ -611,19 +779,22 @@ func ignoredByJSONSchemaTags(tags []string) bool {
 
 func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) {
 	jsonTags, exist := f.Tag.Lookup("json")
-	if !exist {
-		jsonTags = f.Tag.Get("yaml")
+	yamlTags, yamlExist := f.Tag.Lookup("yaml")
+	if !exist || r.PreferYAMLSchema {
+		jsonTags = yamlTags
+		exist = yamlExist
 	}
 
 	jsonTagsList := strings.Split(jsonTags, ",")
+	yamlTagsList := strings.Split(yamlTags, ",")
 
 	if ignoredByJSONTags(jsonTagsList) {
-		return "", exist, false, false
+		return "", false, false, false
 	}
 
 	jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",")
 	if ignoredByJSONSchemaTags(jsonSchemaTags) {
-		return "", exist, false, false
+		return "", false, false, false
 	}
 
 	name := f.Name
@@ -644,16 +815,24 @@ func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool,
 		name = ""
 	}
 
+	embed := false
+
 	// field anonymous but without json tag should be inherited by current type
 	if f.Anonymous && !exist {
 		if !r.YAMLEmbeddedStructs {
 			name = ""
+			embed = true
 		} else {
 			name = strings.ToLower(name)
 		}
 	}
 
-	return name, exist, required, nullable
+	if yamlExist && inlineYAMLTags(yamlTagsList) {
+		name = ""
+		embed = true
+	}
+
+	return name, embed, required, nullable
 }
 
 func (s *Schema) MarshalJSON() ([]byte, error) {
@@ -700,8 +879,27 @@ func (t *Type) MarshalJSON() ([]byte, error) {
 }
 
 func (r *Reflector) typeName(t reflect.Type) string {
+	if r.TypeNamer != nil {
+		if name := r.TypeNamer(t); name != "" {
+			return name
+		}
+	}
 	if r.FullyQualifyTypeNames {
-		return t.PkgPath() + "." + t.Name()
+		return fullyQualifiedTypeName(t)
 	}
 	return t.Name()
 }
+
+func fullyQualifiedTypeName(t reflect.Type) string {
+	return t.PkgPath() + "." + t.Name()
+}
+
+// AddGoComments will update the reflectors comment map with all the comments
+// found in the provided source directories. See the #ExtractGoComments method
+// for more details.
+func (r *Reflector) AddGoComments(base, path string) error {
+	if r.CommentMap == nil {
+		r.CommentMap = make(map[string]string)
+	}
+	return ExtractGoComments(base, path, r.CommentMap)
+}
diff --git a/reflect_test.go b/reflect_test.go
index ad3c8a5..e6733d6 100644
--- a/reflect_test.go
+++ b/reflect_test.go
@@ -11,6 +11,10 @@ import (
 	"testing"
 	"time"
 
+	"github.com/iancoleman/orderedmap"
+
+	"github.com/alecthomas/jsonschema/examples"
+
 	"github.com/stretchr/testify/require"
 )
 
@@ -24,7 +28,7 @@ type SomeBaseType struct {
 	// The jsonschema required tag is nonsensical for private and ignored properties.
 	// Their presence here tests that the fields *will not* be required in the output
 	// schema, even if they are tagged required.
-	somePrivateBaseProperty   string          `json:"i_am_private" jsonschema:"required"`
+	somePrivateBaseProperty   string          `jsonschema:"required"`
 	SomeIgnoredBaseProperty   string          `json:"-" jsonschema:"required"`
 	SomeSchemaIgnoredProperty string          `jsonschema:"-,required"`
 	Grandfather               GrandfatherType `json:"grand"`
@@ -54,10 +58,11 @@ type TestUser struct {
 	nonExported
 	MapType
 
-	ID      int                    `json:"id" jsonschema:"required"`
-	Name    string                 `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"`
-	Friends []int                  `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"`
-	Tags    map[string]interface{} `json:"tags,omitempty"`
+	ID       int                    `json:"id" jsonschema:"required"`
+	Name     string                 `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"`
+	Password string                 `json:"password" jsonschema:"writeOnly=true"`
+	Friends  []int                  `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"`
+	Tags     map[string]interface{} `json:"tags,omitempty"`
 
 	TestFlag       bool
 	IgnoredCounter int `json:"-"`
@@ -83,6 +88,14 @@ type TestUser struct {
 	Color      string  `json:"color" jsonschema:"enum=red,enum=green,enum=blue"`
 	Rank       int     `json:"rank,omitempty" jsonschema:"enum=1,enum=2,enum=3"`
 	Multiplier float64 `json:"mult,omitempty" jsonschema:"enum=1.0,enum=1.5,enum=2.0"`
+
+	// Tests for enum tags on slices
+	Roles      []string  `json:"roles" jsonschema:"enum=admin,enum=moderator,enum=user"`
+	Priorities []int     `json:"priorities,omitempty" jsonschema:"enum=-1,enum=0,enum=1,enun=2"`
+	Offsets    []float64 `json:"offsets,omitempty" jsonschema:"enum=1.570796,enum=3.141592,enum=6.283185"`
+
+	// Test for raw JSON
+	Raw json.RawMessage `json:"raw"`
 }
 
 type CustomTime time.Time
@@ -91,6 +104,19 @@ type CustomTypeField struct {
 	CreatedAt CustomTime
 }
 
+type CustomTimeWithInterface time.Time
+
+type CustomTypeFieldWithInterface struct {
+	CreatedAt CustomTimeWithInterface
+}
+
+func (CustomTimeWithInterface) JSONSchemaType() *Type {
+	return &Type{
+		Type:   "string",
+		Format: "date-time",
+	}
+}
+
 type RootOneOf struct {
 	Field1 string      `json:"field1" jsonschema:"oneof_required=group1"`
 	Field2 string      `json:"field2" jsonschema:"oneof_required=group2"`
@@ -114,12 +140,105 @@ type Inner struct {
 	Foo string `yaml:"foo"`
 }
 
+type MinValue struct {
+	Value int `json:"value4" jsonschema_extras:"minimum=0"`
+}
 type Bytes []byte
 
 type TestNullable struct {
 	Child1 string `json:"child1" jsonschema:"nullable"`
 }
 
+type TestYamlInline struct {
+	Inlined Inner `yaml:",inline"`
+}
+
+type TestYamlAndJson struct {
+	FirstName  string `json:"FirstName" yaml:"first_name"`
+	LastName   string `json:"LastName"`
+	Age        uint   `yaml:"age"`
+	MiddleName string `yaml:"middle_name,omitempty" json:"MiddleName,omitempty"`
+}
+
+type CompactDate struct {
+	Year  int
+	Month int
+}
+
+func (CompactDate) JSONSchemaType() *Type {
+	return &Type{
+		Type:        "string",
+		Title:       "Compact Date",
+		Description: "Short date that only includes year and month",
+		Pattern:     "^[0-9]{4}-[0-1][0-9]$",
+	}
+}
+
+type TestYamlAndJson2 struct {
+	FirstName  string `json:"FirstName" yaml:"first_name"`
+	LastName   string `json:"LastName"`
+	Age        uint   `yaml:"age"`
+	MiddleName string `yaml:"middle_name,omitempty" json:"MiddleName,omitempty"`
+}
+
+func (TestYamlAndJson2) GetFieldDocString(fieldName string) string {
+	switch fieldName {
+	case "FirstName":
+		return "test2"
+	case "LastName":
+		return "test3"
+	case "Age":
+		return "test4"
+	case "MiddleName":
+		return "test5"
+	default:
+		return ""
+	}
+}
+
+type CustomSliceOuter struct {
+	Slice CustomSliceType `json:"slice"`
+}
+
+type CustomSliceType []string
+
+func (CustomSliceType) JSONSchemaType() *Type {
+	return &Type{
+		OneOf: []*Type{{
+			Type: "string",
+		}, {
+			Type: "array",
+			Items: &Type{
+				Type: "string",
+			},
+		}},
+	}
+}
+
+type CustomMapType map[string]string
+
+func (CustomMapType) JSONSchemaType() *Type {
+	properties := orderedmap.New()
+	properties.Set("key", &Type{
+		Type: "string",
+	})
+	properties.Set("value", &Type{
+		Type: "string",
+	})
+	return &Type{
+		Type: "array",
+		Items: &Type{
+			Type:       "object",
+			Properties: properties,
+			Required:   []string{"key", "value"},
+		},
+	}
+}
+
+type CustomMapOuter struct {
+	MyMap CustomMapType `json:"my_map"`
+}
+
 func TestSchemaGeneration(t *testing.T) {
 	tests := []struct {
 		typ       interface{}
@@ -148,7 +267,30 @@ func TestSchemaGeneration(t *testing.T) {
 		}, "fixtures/custom_type.json"},
 		{&TestUser{}, &Reflector{DoNotReference: true, FullyQualifyTypeNames: true}, "fixtures/no_ref_qual_types.json"},
 		{&Outer{}, &Reflector{ExpandedStruct: true, DoNotReference: true, YAMLEmbeddedStructs: true}, "fixtures/disable_inlining_embedded.json"},
+		{&MinValue{}, &Reflector{}, "fixtures/schema_with_minimum.json"},
 		{&TestNullable{}, &Reflector{}, "fixtures/nullable.json"},
+		{&TestYamlInline{}, &Reflector{YAMLEmbeddedStructs: true}, "fixtures/yaml_inline_embed.json"},
+		{&TestYamlInline{}, &Reflector{}, "fixtures/yaml_inline_embed.json"},
+		{&GrandfatherType{}, &Reflector{
+			AdditionalFields: func(r reflect.Type) []reflect.StructField {
+				return []reflect.StructField{
+					{
+						Name:      "Addr",
+						Type:      reflect.TypeOf((*net.IP)(nil)).Elem(),
+						Tag:       "json:\"ip_addr\"",
+						Anonymous: false,
+					},
+				}
+			},
+		}, "fixtures/custom_additional.json"},
+		{&TestYamlAndJson{}, &Reflector{PreferYAMLSchema: true}, "fixtures/test_yaml_and_json_prefer_yaml.json"},
+		{&TestYamlAndJson{}, &Reflector{}, "fixtures/test_yaml_and_json.json"},
+		// {&TestYamlAndJson2{}, &Reflector{}, "fixtures/test_yaml_and_json2.json"},
+		{&CompactDate{}, &Reflector{}, "fixtures/compact_date.json"},
+		{&CustomSliceOuter{}, &Reflector{}, "fixtures/custom_slice_type.json"},
+		{&CustomMapOuter{}, &Reflector{}, "fixtures/custom_map_type.json"},
+		{&CustomTypeFieldWithInterface{}, &Reflector{}, "fixtures/custom_type_with_interface.json"},
+		{&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"},
 	}
 
 	for _, tt := range tests {
@@ -170,6 +312,14 @@ func TestSchemaGeneration(t *testing.T) {
 	}
 }
 
+func prepareCommentReflector(t *testing.T) *Reflector {
+	t.Helper()
+	r := new(Reflector)
+	err := r.AddGoComments("github.com/alecthomas/jsonschema", "./examples")
+	require.NoError(t, err, "did not expect error while adding comments")
+	return r
+}
+
 func TestBaselineUnmarshal(t *testing.T) {
 	expectedJSON, err := ioutil.ReadFile("fixtures/defaults.json")
 	require.NoError(t, err)
@@ -179,5 +329,5 @@ func TestBaselineUnmarshal(t *testing.T) {
 
 	actualJSON, _ := json.MarshalIndent(actualSchema, "", "  ")
 
-	require.Equal(t, strings.Replace(string(expectedJSON), `\/`, "/", -1), string(actualJSON))
+	require.Equal(t, strings.ReplaceAll(string(expectedJSON), `\/`, "/"), string(actualJSON))
 }

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/doc/golang-github-alecthomas-jsonschema-dev/README.md
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/comment_extractor.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/examples/nested/nested.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/examples/user.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/compact_date.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/custom_additional.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/custom_map_type.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/custom_slice_type.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/custom_type_with_interface.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/go_comments.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/schema_with_minimum.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/test_yaml_and_json.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/test_yaml_and_json2.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/test_yaml_and_json_prefer_yaml.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/yaml_inline.json
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/alecthomas/jsonschema/fixtures/yaml_inline_embed.json

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/doc/golang-github-alecthomas-jsonschema-dev/README.md.gz

No differences were encountered in the control files

More details

Full run details