RicoSuter / NJsonSchema

JSON Schema reader, generator and validator for .NET
http://NJsonSchema.org
MIT License
1.4k stars 534 forks source link

Generation of $ref classes should be supressed #646

Open WilfriedRicken opened 6 years ago

WilfriedRicken commented 6 years ago

We have JSON schema which uses $ref to specify the type of a referenced class in a JSON schema. However, the classes are now emitted more than once. Is it possible to supress this?

Example:

OrderDTO.json:

{
    "type": "object",
    "required": [
        "orderId",
        "title"
    ],
    "properties": {
        "orderId": {
            "type": "string"
        },
        "title": {
            "type": "string"
        },
        "orderText": {
            "type": "string"
        },
        "state": {
            "type": "string",
            "enum": [
                "in_progress",
                "finalized"
            ]
        },
        "job": {
            "$ref": "JobDTO.json"
        }
    }
}

JobDTO.json:

{
    "type": "object",
    "required": [
        "jobId"
    ],
    "properties": {
        "jobId": {
            "type": "string"
        }
    }
}

Generating code for both JSON schema files leads to two generated classes JobDTO.json, one correctly in a file JobDTO.cs, another one as a duplicate in OrderDTO.cs.

One could argue that only OrderDTO.cs is needed, however, better would be to suppress the generation of JobDTO in OrderDTO.cs because the $ref correctly refers to another class in another file. When using $ref more than once including only one file would be not sufficient anymore.

Any idea?

RicoSuter commented 6 years ago

Can you provide a sample project which does this generation?

WilfriedRicken commented 6 years ago

So, basically we scan a given folder for *.json files, and for each file found we call CSharpGenerator. Looks like this one:

        private static void Generate(string folder)
        {
            foreach (var file in Directory.GetFiles(folder, "*.json"))
            {
                if (Path.GetFileName(file).ToLower() == "project.json")
                    continue;
                if (Path.GetFileName(file).ToLower() == "project.lock.json")
                    continue;

                // Generate class from json schema file
                Console.WriteLine($"Generating C# code from schema {file}");

                var schema = JsonSchema4.FromFileAsync(file).Result;
                var settings = new CSharpGeneratorSettings { Namespace = folder.Replace(Path.DirectorySeparatorChar, '.').Trim('.'), ClassStyle = CSharpClassStyle.Inpc };
                settings.TemplateFactory = new PrismTemplateFactory(schema, settings, new[] { typeof(CSharpGeneratorSettings).GetTypeInfo().Assembly });
                var generator = new CSharpGenerator(schema, settings);
                var fileName = Path.ChangeExtension(file, ".cs");

                using (var stream = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    using (var writer = new StreamWriter(stream))
                    {
                        Console.WriteLine(fileName);
                        writer.Write(generator.GenerateFile(Path.GetFileNameWithoutExtension(file)));
                    }
                }
            }

            foreach (var childFolder in Directory.GetDirectories(folder))
            {
                Generate(childFolder);
            }
        }
WilfriedRicken commented 6 years ago

Any progress? Is there a chance to get this in an official release?

WilfriedRicken commented 6 years ago

This issue is a big problem for us. Therefore: Could you give me a hint where I can start in the code and I would like to make a proposal how tomsolve it. Thanks!

RicoSuter commented 6 years ago

Instead of using GenerateFile(), try to use GenerateTypes() and collect all artifacts, then filter out duplicates and merge everything into a single CodeArtifactCollection, then merge everything into a single file with generator.GenerateFile(CodeArtifactCollection artifactCollection)

WilfriedRicken commented 6 years ago

This would result in one large single file I assume? I need exactly the opposite: Every class, enum, ... which is defined in a json schema should be in a single cs-File. What I did now (and what only works half way):

    public class MyCSharpGenerator : CSharpGenerator
    {
        public MyCSharpGenerator(object rootObject)
            : base(rootObject)
        {
        }

        public MyCSharpGenerator(object rootObject, CSharpGeneratorSettings settings)
            : base(rootObject, settings)
        {
        }

        public MyCSharpGenerator(object rootObject, CSharpGeneratorSettings settings, CSharpTypeResolver resolver)
            : base(rootObject, settings, resolver)
        {
        }

        protected override string GenerateFile(CodeArtifactCollection artifactCollection)
        {
            CodeArtifact[] artifacts = { artifactCollection.Artifacts.Last() };
            return base.GenerateFile(new CodeArtifactCollection(artifacts, artifactCollection.ExtensionCode));
        }
    }

This at least does not produce duplicate classes, however, it has two problems:

Any further idea? I think it would be best to have a switch in CSharpGeneratorSettings to specify whether $ref-included classes should be supressed in code generation and their usage should be the fully qualified type name for that class.

WilfriedRicken commented 6 years ago

Ok, fixed this problem for us by deriving from CSharpCodeGenerator and modifying the code artifact collection. However, it would be very helpful if CodeArtifact could get a property "OriginalFileName" which contains the name of the JSON schema where it came from. Actually, we use a mix of convention (if there is a file with TypeName.json, then replace code by using statement) and first come, first serve (if there is no such file, the first occurence of a TypeName will be generated, all other occurences will be replaced by using statement). The second approach deals e.g. with enums declared directly at a property, and this one is not exactly what one would expect.

RicoSuter commented 5 years ago

FYI: Multiple file output should be supported: https://github.com/RSuter/NJsonSchema/issues/514

RicoSuter commented 5 years ago

I think it would be best to have a switch in CSharpGeneratorSettings to specify whether $ref-included classes should be supressed in code generation and their usage should be the fully qualified type name for that class.

I think this setting should be possible to add...