omissis / go-jsonschema

A tool to generate Go data types from JSON Schema definitions.
MIT License
582 stars 93 forks source link

Nullable nested object support #177

Open siviae opened 10 months ago

siviae commented 10 months ago

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.