omissis/go-jsonschema

Nullable nested object support

siviae opened this issue · 0 comments

Hello! Thanks a lot for your work.

Right now I am trying to use this tool during my work and I have a problem with using oneOf to support passing null json values. For example, when I use the following schema:

{
  "title": "Repro",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "nested": {
      "oneOf": [
        {
          "type": "null"
        },
        {
          "$ref": "#/definitions/Nested"
        }
      ]
    }
  },
  "definitions": {
    "Nested": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    }
  }
}

I've got the following output:

type Nested struct {
	// Name corresponds to the JSON schema field "name".
	Name string `json:"name" yaml:"name" mapstructure:"name"`
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Nested) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["name"]; !ok || v == nil {
		return fmt.Errorf("field name in Nested: required")
	}
	type Plain Nested
	var plain Plain
	if err := json.Unmarshal(b, &plain); err != nil {
		return err
	}
	*j = Nested(plain)
	return nil
}

type Repro struct {
	// Nested corresponds to the JSON schema field "nested".
	Nested interface{} `json:"nested,omitempty" yaml:"nested,omitempty" mapstructure:"nested,omitempty"`
}

which is very inconvenient to use, since json.Unmarshal does not know, which type to use for nested field.

When I use the following schema:

{
  "title": "Repro",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "nested": {
      "$ref": "#/definitions/Nested"
    }
  },
  "definitions": {
    "Nested": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        }
      },
      "required": [
        "name"
      ]
    }
  }
}

I have the result I want, which is

type Nested struct {
	// Name corresponds to the JSON schema field "name".
	Name string `json:"name" yaml:"name" mapstructure:"name"`
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Nested) UnmarshalJSON(b []byte) error {
	var raw map[string]interface{}
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	if v, ok := raw["name"]; !ok || v == nil {
		return fmt.Errorf("field name in Nested: required")
	}
	type Plain Nested
	var plain Plain
	if err := json.Unmarshal(b, &plain); err != nil {
		return err
	}
	*j = Nested(plain)
	return nil
}

type Repro struct {
	// Nested corresponds to the JSON schema field "nested".
	Nested *Nested `json:"nested,omitempty" yaml:"nested,omitempty" mapstructure:"nested,omitempty"`
}

however, I want the {"nested": null} json object to be valid both in terms of schema and the parser, not just the parser.

I understand that full support of oneOf requires a lot of work, however I think that supporting this special case could be quite easy.