glideapps / quicktype

Generate types and converters from JSON, Schema, and GraphQL
https://app.quicktype.io
Apache License 2.0
12.42k stars 1.08k forks source link

[C#] Multiple classes generated for shared $ref definition #2376

Open teq0 opened 1 year ago

teq0 commented 1 year ago

Where there is an object type defined in a definitions section, whether in the same file or in an external one, every usage of that shared object definitions gets a separate class named after the property that references it. This makes it really hard to do things like have a custom JSON converter for that type, because there's not a single class, there are lots of classes with identical fields but different names.

Schema

{
    "$schema": "https://json-schema.org/draft/2019-09/schema#",
    "description": "Demo of multiple classes from one definition.",
    "type": "object",
    "properties": {
        "foo": {
            "type": "object",
            "description": "The Foo",
            "$ref": "#/definitions/corge"
        },
        "bar": {
            "type": "object",
            "description": "A high Bar",
            "$ref": "#/definitions/corge"
        },
        "baz": {
            "type": "object",
            "description": "Bazalicious",
            "$ref": "#/definitions/corge"
        }
    },
    "definitions": {
        "corge": {
            "type": "object",
            "description": "Corgeous",
            "properties": {
                "name": {
                    "type": "string"
                },
                "something": {
                    "type": "string"
                }
            }
        }
    }
}

C#

namespace Quux
{

    /// <summary>
    /// Demo of multiple classes from one definition.
    /// </summary>
    public partial class DefDemo
    {
        /// <summary>
        /// A high Bar
        /// </summary>
        public Bar Bar { get; set; }

        /// <summary>
        /// Bazalicious
        /// </summary>
        public Baz Baz { get; set; }

        /// <summary>
        /// The Foo
        /// </summary>
        public Foo Foo { get; set; }
    }

    /// <summary>
    /// A high Bar
    ///
    /// Corgeous
    /// </summary>
    public partial class Bar
    {
        public string Name { get; set; }
        public string Something { get; set; }
    }

    /// <summary>
    /// Bazalicious
    ///
    /// A high Bar
    ///
    /// Corgeous
    /// </summary>
    public partial class Baz
    {
        public string Name { get; set; }
        public string Something { get; set; }
    }

    /// <summary>
    /// The Foo
    ///
    /// A high Bar
    ///
    /// Corgeous
    /// </summary>
    public partial class Foo
    {
        public string Name { get; set; }
        public string Something { get; set; }
    }
}

I'm assuming this is deliberate, but can we have an option to just have a single class that is reused?

    /// <summary>
    /// Demo of multiple classes from one definition.
    /// </summary>
    public partial class DefDemo
    {
        /// <summary>
        /// A high Bar
        /// </summary>
        public Corge Bar { get; set; }

        /// <summary>
        /// Bazalicious
        /// </summary>
        public Corge Baz { get; set; }

        /// <summary>
        /// The Foo
        /// </summary>
        public Corge Foo { get; set; }
    }

    /// <summary>
    /// Corgeous
    /// </summary>
    public partial class Corge
    {
        public string Name { get; set; }
        public string Something { get; set; }
    }

As an aside, what's going on with the comments? There are descriptions from multiple properties appearing in multiple places.

nathan-spencer commented 1 year ago

This can partly be resolved by changing the schema to not include a type on properties that reference a definition:

  "properties": {
      "foo": {
-         "type": "object",
          "description": "The Foo",
          "$ref": "#/definitions/corge"
      },
      "bar": {
-         "type": "object",
          "description": "A high Bar",
          "$ref": "#/definitions/corge"
      },
      "baz": {
-         "type": "object",
          "description": "Bazalicious",
          "$ref": "#/definitions/corge"
      }
  }

This will give you a single shared class. However, it will still combine the descriptions on the shared class.

namespace Quux
{

    /// <summary>Demo of multiple classes from one definition.</summary>
    public partial class DefDemo
    {
        /// <summary>A high Bar</summary>
        public Corge Bar { get; set; }

        /// <summary>Bazalicious</summary>
        public Corge Baz { get; set; }

        /// <summary>The Foo</summary>
        public Corge Foo { get; set; }
    }

    /// <summary>A high Bar; ; Corgeous; ; Bazalicious; ; The Foo</summary>
    public partial class Corge
    {
        public string Name { get; set; }
        public string Something { get; set; }
    }
}