How to inject the JSON $schema value during Newtonsoft.JsonConvert.SerializeObject - c#

I created a JSON schema for my C# code using:
// Create JSON schema
var generator = new JSchemaGenerator();
var schema = generator.Generate(typeof(ConfigFileJsonSchema));
schema.Title = "PlexCleaner Schema";
schema.Description = "PlexCleaner config file JSON schema";
schema.SchemaVersion = new Uri("http://json-schema.org/draft-06/schema");
schema.Id = new Uri("https://raw.githubusercontent.com/ptr727/PlexCleaner/main/PlexCleaner.schema.json");
Console.WriteLine(schema);
I want to add a reference to this scheme whenever I create JSON output from my code:
private static string ToJson(ConfigFileJsonSchema settings)
{
return JsonConvert.SerializeObject(settings, Settings);
}
private static readonly JsonSerializerSettings Settings = new()
{
Formatting = Formatting.Indented,
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
NullValueHandling = NullValueHandling.Ignore,
// We expect containers to be cleared before deserializing
// Make sure that collections are not read-only (get; set;) else deserialized values will be appended
// https://stackoverflow.com/questions/35482896/clear-collections-before-adding-items-when-populating-existing-objects
ObjectCreationHandling = ObjectCreationHandling.Replace
// TODO: Add TraceWriter to log to Serilog
};
How can I programmatically add the $schema URI to the created JSON, not meaning creating schema on the fly, but something like this:
public class ConfigFileJsonSchemaBase
{
// Schema reference
[JsonProperty(PropertyName = "$schema", Order = -2)]
public string Schema { get; } = "https://raw.githubusercontent.com/ptr727/PlexCleaner/main/PlexCleaner.schema.json";
// Default to 0 if no value specified, and always write the version first
[DefaultValue(0)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate, Order = -2)]
public int SchemaVersion { get; set; } = ConfigFileJsonSchema.Version;
}
Without needing to add a $schema entry to the class.
E.g. equivalent of:
schema.SchemaVersion = new Uri("http://json-schema.org/draft-06/schema");
There is a similar unanswered question: json serialization to refer schema

You can use a JsonConverter:
public class SchemaJsonConverter : JsonConverter
{
private readonly string _schemaUrl;
private readonly Type[] _types;
public SchemaJsonConverter(string schemaUrl, params Type[] types)
{
this._schemaUrl = schemaUrl;
this._types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
var o = (JObject)t;
o.AddFirst(new JProperty("$Schema", this._schemaUrl));
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return this._types.Any(t => t == objectType);
}
}
You need the type to check the types affected by the converter and the schema url to inject it in your JSON. The converter allow you a fine control about the process of serialization.
I use a simple class to test the converter:
public class Something
{
public int Integer { get; set; }
public string Text { get; set; }
}
And a method to run the sample:
public static void Test()
{
var something = new Something
{
Integer = 37,
Text = "A text"
};
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
StringEscapeHandling = StringEscapeHandling.EscapeNonAscii,
NullValueHandling = NullValueHandling.Ignore,
// We expect containers to be cleared before deserializing
// Make sure that collections are not read-only (get; set;) else deserialized values will be appended
// https://stackoverflow.com/questions/35482896/clear-collections-before-adding-items-when-populating-existing-objects
ObjectCreationHandling = ObjectCreationHandling.Replace
// TODO: Add TraceWriter to log to Serilog
};
var schemaUrl = "http://json-schema.org/draft-06/schema";
settings.Converters.Add(new SchemaJsonConverter(schemaUrl, something.GetType()));
var json = JsonConvert.SerializeObject(something, settings);
Console.WriteLine(json);
}
Output:
{
"$Schema": "http://json-schema.org/draft-06/schema",
"Integer": 37,
"Text": "A text"
}
UPDATE
A static method for serialization:
public static string SerializeJson(object obj, JsonSerializerSettings settings, string schemaUrl = null)
{
if (!string.IsNullOrEmpty(schemaUrl))
{
settings.Converters.Add(new SchemaJsonConverter(schemaUrl, obj.GetType()));
}
return JsonConvert.SerializeObject(obj, settings);
}
Usage:
var json = SerializeJson(something, settings, schemaUrl);

Related

IgnoreNullValue not working when serializing an Anonymous Object

The Problem
I am creating an anonymous object in a method and assigning it properties from a class. Some of the properties can sometimes be empty (strings) and I eventually need to serialize the anonymous object to a JSON string.
When it gets serialized as JSON, I want to get rid of nulls or empty strings. I actually want to do this on a property by property basis (not generically ignoring all nulls) but it doesn't seem to be working at all!
The Code
public class Refrigerator {
public string Brand { get; set; }
public bool HasFood { get; set; }
public void Serialize() {
//ANONYMOUS OBJECT HERE
var fridge = new {
Brand = this.Brand; //THIS VALUE IS SOMETIMES AN EMPTY STRING!!
HasFood = this.HasFood;
}
var value = Newtonsoft.Json.JsonConvert.SerializeObject(fridge);
//value contains Brand as an empty string
}
}
What I've Tried
var fridge = new {
Brand = this.Brand;
HasFood = this.HasFood;
//JsonSerializer should check this function I thought?
ShouldSerializeBrand = new Func<bool>() {
if(String.IsNullOrEmpty(this.Brand){
return false;
}
return true;
}
}
//This should also work, but doesn't.......
var value = Newtonsoft.Json.JsonConvert.SerializeObject(fridge, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
});
//JSON STILL contains Brand as an empty string!!! Its still there!
Any ideas why its not working?
One of the way for this could be that, you can use ExpandoObject for this like :
dynamic fridge = new ExpandoObject();
if(!String.IsNullOrEmpty(this.Brand))
{
fridge.Brand = this.Brand;
}
fridge.HasFood = this.HasFood;
This way the anonymous object will not have Brand property created unless it has a value. Now if the Brand is null of empty string, the property won't be created on the anonymous object we have created.
See this DEMO FIDDLE as example.
An empty string i.e. the literal "" is neither a null value nor the default for a string type. As such, both options from your JsonSerializerSettings will fail to do what you want. You can solve the problem by doing what you already tried, or by creating a custom JSONConverter to add in your specific serialization logic.
Sample code based off the Newtonsoft samples:
public class FridgeJsonConverter : JsonConverter
{
private readonly Type[] _types;
public FridgeJsonConverter(params Type[] types)
{
_types = types;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
var brandProp = o.Properties().First(x => x.Name == "Brand");
if(brandProp.Value.ToString() == string.Empty)
o.Remove(brandProp.Name);
o.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
}
Then, in your calling code, do this:
string json = JsonConvert.SerializeObject(fridge, Formatting.Indented, new FridgeJsonConverter(typeof(Fridge)));

C# System.Text.Json or Newtonsoft JSON properties marking for serialization control by attributes

I am just wonder if I can mark certain property of class instance via any attribute and during serialization serialize just those marked properties (and of-course by deserializing affect also only marked properties via attribute vice-versa in instance of the class - the rest of properties should remain same...).
I know how to identify those properties by reflection, but I do not want to make another Json serialization by myself.
[MyFirstAttribute]
public string A { get; set; } = "hi";
[MyFirstAttribute]
public int B { get; set; } = 13;
[MySecondAttribute]
public string C { get; set; } = "something";
as it documented here in this link you can create a custom CustomJsonConverter by inheriting from JsonConverter class.
And then use it like:
Employee employee = new Employee
{
FirstName = "James",
LastName = "Newton-King",
Roles = new List<string>
{
"Admin"
}
};
string json = JsonConvert.SerializeObject(employee, Formatting.Indented, new KeysJsonConverter(typeof(Employee)));
Console.WriteLine(json);
Based on #ArgeKumandan advice:
public class KeysJsonConverter : JsonConverter
{
private readonly Type[] _types;
public KeysJsonConverter(params Type[] types) => _types = types;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object) t.WriteTo(writer);
else
{
JObject jo = new JObject();
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (!prop.CanRead) continue;
object propVal = prop.GetValue(value, null);
if (propVal is null || !Attribute.IsDefined(prop, _types[0])) continue;
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead { get => false; }
public override bool CanConvert(Type objectType) => _types.Any(t => t == _types[0]);
}
and then usage:
// serialization
var json = JsonConvert.SerializeObject(objectInstance1, Formatting.Indented, new KeysJsonConverter(typeof(MyFirstAttribute)));
// deserialization to an existing instance that updates just the properties contained in JSON
JsonConvert.PopulateObject(jsonCommon, objectInstance2);

Deserializing a list of dynamic properties

My application is consuming an API, and I'm trying to deserialize data of images coming back. The data is formatted like:
{
"images":{
"totalCount":4,
"0":{
"url":"file1.jpg"
},
"1":{
"url":"file2.jpg"
},
"2":{
"url":"file3.jpg"
},
"3":{
"url":"file4.jpg"
}
}
}
I have these model classes:
public class MyViewModel
{
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
public class ImagesViewModel
{
[JsonProperty("totalCount")]
public int TotalCount { get; set; }
public Dictionary<string, ImageViewModel> ListImages { get; set; }
}
public class ImageViewModel
{
[JsonProperty("url")]
public string Url { get; set; }
}
The collection of images isn't really a collection, for some reason it's just a new property for each image. I'm trying to deserialize my object like:
... // create HttpClient object, add headers and such
System.Net.Http.HttpResponseMessage response = await
client.GetAsync(endpointUrl);
var jsonString = response.Content.ReadAsStringAsync();
MyViewModel model =
JsonConvert.DeserializeObject<MyViewModel>(jsonString.Result);
I get back the totalCount property just fine, but the collection of images is coming back null.
Is there a way for me to change my view models so that I can deserialize the json correctly?
Given the formatting of the JSON you will have to go the long route and try to deserialize it using JObjects
//using Newtonsoft.Json.Linq
var jObject = JObject.Parse(jsonString);
var images = jObject.Property("images").Value<JObject>(); ;
var viewModel = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
Going a step further and using a JsonConverter for converting the payload itself actually works as well given that we know now how to convert it.
public class MyViewModelConverter : JsonConverter {
public override bool CanConvert(Type objectType) {
return objectType == typeof(MyViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var jObject = JObject.Load(reader);//<-- Note the use of Load() instead of Parse()
var images = jObject.Property("images").Value<JObject>(); ;
var model = new MyViewModel {
Images = new ImagesViewModel {
TotalCount = images.Property("totalCount").Value<int>(),
ListImages = images.Properties().Skip(1).ToDictionary(p => p.Name, p => p.Value<ImageViewModel>())
}
};
return model;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
}
and decorating the class itself
[JsonConverter(typeof(MyViewModelConverter))]
public class MyViewModel {
[JsonProperty("images")]
public ImagesViewModel Images { get; set; }
}
Deserialization is now as you intended to do before
var jsonString = await response.Content.ReadAsStringAsync();
MyViewModel model = JsonConvert.DeserializeObject<MyViewModel>(jsonString);
.NET Abhors dynamic types. They fly in the face of solid type checking at compile time. That being said, there is support for it:
As the example data is basically just a array of images, any collection could deal with this input.
If you can not even define the types umanbigiously (you might have a array of images and one of strings), the only way is ExpandoObject. It is designed specifically to deal with such cases. It is basically a List[string, object] with some Syntax Sugar, but it also does includes functions like Property Change Notifications.
Sounds like a job for a custom converter!
A custom converter will let you supply your own logic for deserializing specific types. Newtonsoft uses the target class to figure out with type if expects to find in the json and call the appropriate converter.
class ImagesViewModelConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ImagesViewModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
assertToken(JsonToken.StartObject);
var obj = new ImagesViewModel()
{
ListImages = new Dictionary<string, ImageViewModel>()
};
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
assertToken(JsonToken.PropertyName);
var propName = (string)reader.Value;
if (propName.Equals(nameof(ImagesViewModel.TotalCount), StringComparison.InvariantCultureIgnoreCase))
{
reader.Read();
assertToken(JsonToken.Integer);
obj.TotalCount = (int)((Int64)reader.Value);
continue;
}
reader.Read();
var image = serializer.Deserialize<ImageViewModel>(reader); // you can still use normal json deseralization inside a converter
obj.ListImages.Add(propName, image);
}
return obj;
void assertToken(JsonToken token)
{
if (reader.TokenType != token)
throw new Exception(); // might wanna add detailed errors
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException(); // implement if needed
}
}
And then:
var settings = new JsonSerializerSettings()
{
Converters = new[] { new ImagesViewModelConverter() }
};
var obj = JsonConvert.DeserializeObject<MyViewModel>(jsonString, settings);
});
You can even change classes to be easier to handle, given that they no longer need to match the json exactly. You can for example replace the dict with an array and have the converter fill it in order.

Serialize poco containing object property into Json

I have a class that I need to serialize to pass to another system.
The class contains a property that is defined as an object, because the type of class the object will contain can vary at runtime.
My classes looks something like this simplified mock up;
public class MyTestXML
{
public string String1 { get; set; }
public string String2 { get; set; }
[System.Xml.Serialization.XmlElementAttribute("First", typeof(MyFirstObject),
Form = System.Xml.Schema.XmlSchemaForm.Qualified)]
[System.Xml.Serialization.XmlElementAttribute("Second", typeof(MySecondObject),
Form = System.Xml.Schema.XmlSchemaForm.Qualified)]
public object MyObject { get; set; }
}
public class MyFirstObject
{
public string theFirstObjectString { get; set; }
}
public class MySecondObject
{
public string theSecondObjectString { get; set; }
}
This class serializes perfectly to xml by using the XmlElementAttribute and XmlSerializer, but when I try and serialize it to Json (using Newtonsoft Json.Net), the object is of an undefined type, and it cannot be deserialized.
Is there a way to specify the XmlElementAttribute in Json attributes to achieve the same result when serialized?
I would like to offer the use of Json for the serialised object, as it is half the size of the xml, but cannot unless I can solve the serialization of the object property issue.
Thanks in advance.
You would have to create your own custom serialization behaviour. Have a look at this answer here : https://stackoverflow.com/a/22722467/2039359 on how to implement your own JsonConverter for Json.Net
In your case you could do something like this to create your json
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
MyTestXML myTestXml = (MyTestXML) value;
JObject jObject = JObject.FromObject(value);
JProperty prop = jObject.Children<JProperty>().First(p=>p.Name.Contains("MyObject"));
if (myTestXml.MyObject.GetType() == typeof (MyFirstObject))
{
prop.AddAfterSelf(new JProperty("First", JToken.FromObject(myTestXml.MyObject)));
prop.Remove();
jObject.WriteTo(writer);
}
else if (myTestXml.MyObject.GetType() == typeof (MySecondObject))
{
prop.AddAfterSelf(new JProperty("Second", JToken.FromObject(myTestXml.MyObject)));
prop.Remove();
jObject.WriteTo(writer);
}
}
And something like this when you are deserializing
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
MyTestXML myTestXml = new MyTestXML();
serializer.Populate(jo.CreateReader(), myTestXml);
object myObject = null;
if (jo["First"] != null)
{
myObject = new MyFirstObject { TheFirstObjectString = jo["First"].SelectToken(#"TheFirstObjectString").Value<string>() };
}
if (jo["Second"] != null)
{
myObject = new MySecondObject { TheSecondObjectString = jo["Second"].SelectToken(#"TheSecondObjectString").Value<string>() };
}
myTestXml.MyObject = myObject;
return myTestXml;
}
To use it you would supply your JsonConverter when serializing/deserializing like so:
var settings = new JsonSerializerSettings();
settings.Converters.Add(new MyTextXmlJsonConverter());
var a = JsonConvert.SerializeObject(myTestXml, settings);
Hope that's what you're looking for
Another alternative is to create a custom contract resolver which would allow you to detect which xml attribute is applied. You can then apply a custom JsonConverter on the property if you needed a specific output.
public class CustomContractResolver : DefaultContractResolver
{
private readonly JsonMediaTypeFormatter formatter;
public CustomContractResolver(JsonMediaTypeFormatter formatter)
{
this.formatter = formatter;
}
public JsonMediaTypeFormatter Formatter
{
[DebuggerStepThrough]
get { return this.formatter; }
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
this.ConfigureProperty(member, property);
return property;
}
private void ConfigureProperty(MemberInfo member, JsonProperty property)
{
if (Attribute.IsDefined(member, typeof(XmlElementAttribute), true))
{
var attribute = member.CustomAttributes.Where(x => x.AttributeType == typeof(XmlElementAttribute)).First();
// do something with your attribute here like apply a converter
property.Converter = new XmlAttributeJsonConverter();
}
}
}

Use specific property name with Json.NET when serializing

Using Json.NET in C#, I am having troubles when serializing a class, in which I have a need for a custom property name.
This is what I have now:
{
"baseName": {
"subName1": {
"name": null,
"longName": null,
"asd1": null,
"asd2": null
},
"subName2": [
{
"id": "ID_NUMBER",
"info": {
"someInfo1": "asd",
"someInfo2": "asd2"
}
}
]
}
}
This is what I want:
{
"baseName": {
"subName1": {
"name": null,
"longName": null,
"asd1": null,
"asd2": null
},
"subName2": [
{
"ID_NUMBER": {
"someInfo1": "asd",
"someInfo2": "asd2"
}
}
]
}
}
That is, instead of having a key with id and value ID_NUMBER, I want the ID_NUMBER to be the key of the object containing the keys someInfo1 and someInfo2.
The top JSON code is generated using these classes (sorry for the bad names):
class JSONTestClass
{
public JSONBaseTestClass baseName;
}
class JSONBaseTestClass
{
public JSONSubTestClass1 subName1;
public List<JSONSubTestClass2> subName2;
}
class JSONSubTestClass1
{
public string name;
public string longName;
public string asd1;
public string asd2;
}
class JSONSubTestClass2
{
public string id;
public JSONInfoTestClass info;
}
class JSONInfoTestClass
{
public string someInfo1;
public string someInfo2;
}
And this:
private void MakeJSON()
{
// This value can be changed at runtime
string specificId = "ID_NUMBER";
JSONInfoTestClass jitc = new JSONInfoTestClass();
jitc.someInfo1 = "asd";
jitc.someInfo2 = "asd2";
JSONTestClass jtc = new JSONTestClass();
JSONBaseTestClass jbtc = new JSONBaseTestClass();
JSONSubTestClass1 jstc1 = new JSONSubTestClass1();
JSONSubTestClass2 jstc2 = new JSONSubTestClass2();
jstc2.id = specificId;
jstc2.info = jitc;
List<JSONSubTestClass2> list = new List<JSONSubTestClass2>();
list.Add(jstc2);
jbtc.subName1 = jstc1;
jbtc.subName2 = list;
jtc.baseName = jbtc;
// Convert to JSON
string json = JsonConvert.SerializeObject(jtc, Formatting.Indented);
tbxJSONOutput.Text = json;
}
Which changes are needed so I can get a JSON output corresponding to the second JSON response mentioned above?
You can get the output you want by creating a custom JsonConverter for your JSONSubTestClass2 class like this:
class JSONSubTestClass2Converter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(JSONSubTestClass2));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JSONSubTestClass2 jstc2 = (JSONSubTestClass2)value;
JObject jo = new JObject();
jo.Add(jstc2.id, JObject.FromObject(jstc2.info));
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, serialize your classes like this:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new JSONSubTestClass2Converter());
settings.Formatting = Formatting.Indented;
// Convert to JSON
string json = JsonConvert.SerializeObject(jtc, settings);

Categories