go-jsonschema icon indicating copy to clipboard operation
go-jsonschema copied to clipboard

Multiple refs to enums results in multiple interfaces

Open samscott89 opened this issue 3 years ago • 1 comments

Given a schema like:

{
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "title": "test",
    "definitions": {
        "Foo1": {
            "type": "object",
            "required": [
                "bar"
            ],
            "properties": {
                "bar": {
                    "$ref": "#/definitions/Bar"
                }
            }
        },
        "Foo2": {
            "type": "object",
            "required": [
                "bar"
            ],
            "properties": {
                "bar": {
                    "$ref": "#/definitions/Bar"
                }
            }
        },
        "Bar": {
            "type": "string",
            "enum": [
                "1",
                "2"
            ]
        }
    }
}

I.e. Foo1 and Foo2 both have a bar field of type Bar, which is an enum.

then the output has three different Bar types:

Long code snippet
package main

import "fmt"
import "reflect"
import "encoding/json"

type Bar string

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar_2) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar_2 {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar_2, v)
        }
        *j = Bar_2(v)
        return nil
}

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar, v)
        }
        *j = Bar(v)
        return nil
}

const BarA1 Bar = "1"
const BarA2 Bar = "2"

type Bar_1 string

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

// UnmarshalJSON implements json.Unmarshaler.
func (j *Bar_1) UnmarshalJSON(b []byte) error {
        var v string
        if err := json.Unmarshal(b, &v); err != nil {
                return err
        }
        var ok bool
        for _, expected := range enumValues_Bar_1 {
                if reflect.DeepEqual(v, expected) {
                        ok = true
                        break
                }
        }
        if !ok {
                return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_Bar_1, v)
        }
        *j = Bar_1(v)
        return nil
}

const Bar_1_A1 Bar_1 = "1"
const Bar_1_A2 Bar_1 = "2"

type Bar_2 string

const Bar_2_A1 Bar_2 = "1"
const Bar_2_A2 Bar_2 = "2"

type Foo1 struct {
        // Bar corresponds to the JSON schema field "bar".
        Bar Bar_1 `json:"bar"`
}

type Foo2 struct {
        // Bar corresponds to the JSON schema field "bar".
        Bar Bar_2 `json:"bar"`
}

var enumValues_Bar = []interface{}{
        "1",
        "2",
}
var enumValues_Bar_1 = []interface{}{
        "1",
        "2",
}
var enumValues_Bar_2 = []interface{}{
        "1",
        "2",
}

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

Expected: there is a single Bar type shared between the definitions.

samscott89 avatar Nov 03 '20 19:11 samscott89

One may see the same behaviour even when enum is referenced from one place and in can be seen in tests/data/core/refToEnum.json and corresponding tests/data/core/refToEnum.go.output. I think that the reason is that for object types special measures were taken to not declare referenced type twice (https://github.com/atombender/go-jsonschema/blame/master/pkg/generator/generate.go#L427) but not for enums.

daa avatar Apr 03 '21 00:04 daa