Leo
Leo

Reputation: 1703

Generate C# classes from json schema with heterogeneous array

I have a json schemas in a project and want to add build step to generate classes from them, on of these schemas contains an array of objects and strings, simplified example below:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "testSchema",
    "type": "object",
    "properties": {
        "array": {
            "type": "array",
            "items": {
                "anyOf": [
                    {
                        "type": "string"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        }
                    }
                ]
            }
        }
    }
}

I'm using NJsonSchema to generate C# code from this schema. As a result I'm getting the following output:

//----------------------
// <auto-generated>
//     Generated using the NJsonSchema v8.32.6319.16936 (http://NJsonSchema.org)
// </auto-generated>
//----------------------

namespace TestSchema
{
#pragma warning disable // Disable all warnings

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "8.32.6319.16936")]
public partial class TestSchema : System.ComponentModel.INotifyPropertyChanged
{
    private System.Collections.ObjectModel.ObservableCollection<Anonymous> _array = new System.Collections.ObjectModel.ObservableCollection<Anonymous>();

    [Newtonsoft.Json.JsonProperty("array", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
    public System.Collections.ObjectModel.ObservableCollection<Anonymous> Array
    {
        get { return _array; }
        set 
        {
            if (_array != value)
            {
                _array = value; 
                RaisePropertyChanged();
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string ToJson() 
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject(this);
    }

    public static TestSchema FromJson(string data)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<TestSchema>(data);
    }

    protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) 
            handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "8.32.6319.16936")]
public partial class Anonymous : System.ComponentModel.INotifyPropertyChanged
{

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string ToJson() 
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject(this);
    }

    public static Anonymous FromJson(string data)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<Anonymous>(data);
    }

    protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) 
            handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}
}

As a result I have this strange Anonymous class and if I try to deserialise a json file below I get an error: string can't be converted into Anonymous. To deserialise I use the following generated method:

TestSchema.FromJson

Is that possible to adjust code generation to have as a result a collection of object instead and to get deserialised objects with correct types in it?

{
    "array": [
        "stringItem1",
        {
            "name": "complexObj1"
        }
    ]
}

Upvotes: 1

Views: 4299

Answers (1)

Leo
Leo

Reputation: 1703

Finally I achieved what I needed.

The idea is to pass custom CSharpTypeResolver into CSharpGenerator:

new CSharpGenerator(jsonSchema4, settings, new CustomCSharpTypeResolver(settings, jsonSchema4), null);

Looks like it wasn't intended by NJsonSchema author. In CustomCSharpTypeResolver I override Resolve method to add the following behaviour:

if (schema.AnyOf.Count > 0)
    return "object";

As a result for simplified example I have the following model:

//----------------------
// <auto-generated>
//     Generated using the NJsonSchema v8.32.6319.16936 (http://NJsonSchema.org)
// </auto-generated>
//----------------------

namespace JsonSchemaClassGenerator.TestSchema
{
#pragma warning disable // Disable all warnings

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "8.32.6319.16936")]
public partial class TestSchema : System.ComponentModel.INotifyPropertyChanged
{
    private System.Collections.ObjectModel.ObservableCollection<object> _array = new System.Collections.ObjectModel.ObservableCollection<object>();

    [Newtonsoft.Json.JsonProperty("array", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
    public System.Collections.ObjectModel.ObservableCollection<object> Array
    {
        get { return _array; }
        set 
        {
            if (_array != value)
            {
                _array = value; 
                RaisePropertyChanged();
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string ToJson() 
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject(this);
    }

    public static TestSchema FromJson(string data)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<TestSchema>(data);
    }

    protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) 
            handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "8.32.6319.16936")]
public partial class Object : System.ComponentModel.INotifyPropertyChanged
{
    private string _name;

    [Newtonsoft.Json.JsonProperty("name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
    public string Name
    {
        get { return _name; }
        set 
        {
            if (_name != value)
            {
                _name = value; 
                RaisePropertyChanged();
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public string ToJson() 
    {
        return Newtonsoft.Json.JsonConvert.SerializeObject(this);
    }

    public static Object FromJson(string data)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<Object>(data);
    }

    protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) 
            handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
    }
}
}

Deserialisation works fine. I can cast objects as I want. There is only one problem: objects are being saved as JObject instances so I need to implement explicit or implicit operator to convert it into generated model.

namespace JsonSchemaClassGenerator.TestSchema
{
    public partial class Object
    {
        public static implicit operator Object(JObject json)
        {
            return FromJson(json.ToString());
        }
    }
}

After that it will be possible to convert JObject into generated model (Object is not a System.Object it just was generated with such a name):

Object a = config.Entries[1] as JObject;

It is the simplest solution I found. I think it is also possible to implement custom CSharpTypeResolver to have something more type-safe. But not sure if I will try since for me it looks like it would be better to make NJsonSchema more flexible first.

Upvotes: 1

Related Questions