C# WebAPI serialize a List of Strings - c#

This should be a relatively easy question I derped online for a while and still can't find a solution.
Right now my webapi returns an output like this
<Merchant>
<Cuisine xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:string>Japanese</d3p1:string>
<d3p1:string>Korean</d3p1:string>
<d3p1:string>French</d3p1:string>
</Cuisine>
</Merchant>
I want it to return like this
<Merchant>
<Cuisines>
<Cuisine>Japanese</Cuisine>
<Cuisine>Korean</Cuisine>
<Cuisine>French</Cuisine>
</Cuisines>
</Merchant>
What is the easiest way to accomplish such a task?
So basically there is two things I want to do
1)Get rid of the namespace xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
2)change the name of outter element from
<Cuisine>
to
<Cuisines>
3)Change the name of inner element from
<d2p1:string>
to
<Cuisine>
And my datamember within the Merchant class is like this
[DataMember(EmitDefaultValue = false)]
public List<String> WebCuisine { get; set; }
Thank you in advnace

You have to use your own serializer.
Create a data structure
[XmlRoot("Merchant")]
public class Merchant
{
[XmlArray("Cuisines"), XmlArrayItem("Cuisine")]
public List<String> WebCuisine { get; set; }
}
Create a class inherited from XmlObjectSerializer
public class MerchantSerializer : XmlObjectSerializer
{
XmlSerializer serializer;
public MerchantSerializer()
{
this.serializer = new XmlSerializer(typeof(Merchant));
}
public override void WriteObject(XmlDictionaryWriter writer, object graph)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(writer, graph, ns);
}
public override bool IsStartObject(XmlDictionaryReader reader)
{
throw new NotImplementedException();
}
public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
{
throw new NotImplementedException();
}
public override void WriteEndObject(XmlDictionaryWriter writer)
{
throw new NotImplementedException();
}
public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
{
throw new NotImplementedException();
}
public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
{
throw new NotImplementedException();
}
}
As you can see I am only interested to write , but not to read. However, you can easy implement ReadObject if you need.
After in WebApiConfig in public static void Register(HttpConfiguration config) you add
config.Formatters.XmlFormatter.SetSerializer<Merchant>(new MerchantSerializer());
And you should get
<Merchant>
<Cuisines>
<Cuisine>Japanese</Cuisine>
<Cuisine>Korean</Cuisine>
<Cuisine>French</Cuisine>
</Cuisines>
</Merchant>

I don't know if this will help anyone but I took the Merchant Serializer and modified it into a Generic Serializer
using System;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;
namespace NoNamespaceXml
{
public class GenericSerializer : XmlObjectSerializer
{
#region Private Variables
private XmlSerializer serializer;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a GenericSerializer
/// </summary>
/// <param name="objectToSerialize"></param>
public GenericSerializer (object objectToSerialize)
{
// If the objectToSerialize object exists
if (objectToSerialize != null)
{
// Create the Serializer
this.Serializer = new XmlSerializer(objectToSerialize.GetType());
}
}
#endregion
#region Methods
#region IsStartObject(XmlDictionaryReader reader)
/// <summary>
/// This method Is Start Object
/// </summary>
public override bool IsStartObject(XmlDictionaryReader reader)
{
throw new NotImplementedException();
}
#endregion
#region ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
/// <summary>
/// This method Read Object
/// </summary>
public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
{
throw new NotImplementedException();
}
#endregion
#region WriteEndObject(XmlDictionaryWriter writer)
/// <summary>
/// This method Write End Object
/// </summary>
public override void WriteEndObject(XmlDictionaryWriter writer)
{
throw new NotImplementedException();
}
#endregion
#region WriteObject(XmlDictionaryWriter writer, object graph)
/// <summary>
/// This method Write Object
/// </summary>
public override void WriteObject(XmlDictionaryWriter writer, object graph)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(writer, graph, ns);
}
#endregion
#region WriteObjectContent(XmlDictionaryWriter writer, object graph)
/// <summary>
/// This method Write Object Content
/// </summary>
public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
{
throw new NotImplementedException();
}
#endregion
#region WriteStartObject(XmlDictionaryWriter writer, object graph)
/// <summary>
/// This method Write Start Object
/// </summary>
public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
{
throw new NotImplementedException();
}
#endregion
#endregion
#region Properties
#region HasSerializer
/// <summary>
/// This property returns true if this object has a 'Serializer'.
/// </summary>
public bool HasSerializer
{
get
{
// initial value
bool hasSerializer = (this.Serializer != null);
// return value
return hasSerializer;
}
}
#endregion
#region Serializer
/// <summary>
// This property gets or sets the value for 'Serializer'.
/// </summary>
public XmlSerializer Serializer
{
get { return serializer; }
set { serializer = value; }
}
#endregion
#endregion
}
#endregion
}
Then all you have to do is register any types you want to use this serializser:
// Set the Serializer for certain objects
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer<NetworkSearchResponse>(serializer);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SetSerializer<SynxiBooleanResponse>(serializer);

Related

In System.Text.Json is it possible to specify custom indentation rules?

Edit: I made an issue at the .Net runtime repo yesterday which was closed to by "layomia" with this message: "Adding extension points like this comes with a performance cost at the lower-level reader and writer and does not present a good balance between perf and functionality/benefit. Providing such configuration is not on the System.Text.Json roadmap."
When setting JsonSerializerOptions.WriteIndented = true indentation looks like this when writing json...
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [
304,
16,
16,
16
],
"SCREEN_BOUNDS": [
485,
159,
64,
64
]
}
}
}
Is there a way to change the automatic indentation to something like this...
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES":
{
"TILE_1":
{
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [304, 16, 16,16],
"SCREEN_BOUNDS": [485, 159, 64, 64]
}
}
}
Update for .NET 6
While custom indentation rules are not supported by System.Text.Json, as of .NET 6 and later it is possible to disable indentation when serializing a particular member or type. By using Utf8JsonWriter.WriteRawValue(), you can create a custom JsonConverter that generates a default serialization for your value without indentation to a utf8 byte buffer, then writes the buffer to the incoming Utf8JsonWriter as-is.
First define the following converters:
public class NoIndentationConverter : NoIndentationConverter<object>
{
public override bool CanConvert(Type typeToConvert) => true;
}
public class NoIndentationConverter<T> : DefaultConverterFactory<T>
{
protected override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions)
{
// TODO: investigate https://learn.microsoft.com/en-us/dotnet/api/microsoft.toolkit.highperformance.buffers.arraypoolbufferwriter-1
var bufferWriter = new ArrayBufferWriter<byte>();
using (var innerWriter = new Utf8JsonWriter(bufferWriter))
JsonSerializer.Serialize(innerWriter, value, modifiedOptions);
writer.WriteRawValue(bufferWriter.WrittenSpan, skipInputValidation : true);
}
protected override JsonSerializerOptions ModifyOptions(JsonSerializerOptions options) { (options = base.ModifyOptions(options)).WriteIndented = false; return options; }
}
public abstract class DefaultConverterFactory<T> : JsonConverterFactory
{
// Adapted from this answer https://stackoverflow.com/a/65430421/3744182
// To https://stackoverflow.com/questions/65430420/how-to-use-default-serialization-in-a-custom-system-text-json-jsonconverter
class DefaultConverter : JsonConverter<T>
{
readonly JsonSerializerOptions modifiedOptions;
readonly DefaultConverterFactory<T> factory;
public DefaultConverter(JsonSerializerOptions modifiedOptions, DefaultConverterFactory<T> factory) => (this.modifiedOptions, this.factory) = (modifiedOptions, factory);
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => factory.Write(writer, value, modifiedOptions);
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => factory.Read(ref reader, typeToConvert, modifiedOptions);
public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert);
}
protected virtual JsonSerializerOptions ModifyOptions(JsonSerializerOptions options)
=> options.CopyAndRemoveConverter(this.GetType());
protected virtual T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions modifiedOptions)
=> (T?)JsonSerializer.Deserialize(ref reader, typeToConvert, modifiedOptions);
protected virtual void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions modifiedOptions)
=> JsonSerializer.Serialize(writer, value, modifiedOptions);
public override bool CanConvert(Type typeToConvert) => typeof(T) == typeToConvert;
public sealed override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new DefaultConverter(ModifyOptions(options), this);
}
public static class JsonSerializerExtensions
{
public static JsonSerializerOptions CopyAndRemoveConverter(this JsonSerializerOptions options, Type converterType)
{
var copy = new JsonSerializerOptions(options);
for (var i = copy.Converters.Count - 1; i >= 0; i--)
if (copy.Converters[i].GetType() == converterType)
copy.Converters.RemoveAt(i);
return copy;
}
}
And now you can either apply NoIndentationConverter directly to your model (demo #1 here):
public partial class Tile1
{
[JsonPropertyName("NAME")]
public string Name { get; set; }
[JsonPropertyName("TEXTURE_BOUNDS")]
[JsonConverter(typeof(NoIndentationConverter))]
public List<long> TextureBounds { get; set; }
[JsonPropertyName("SCREEN_BOUNDS")]
[JsonConverter(typeof(NoIndentationConverter))]
public List<long> ScreenBounds { get; set; }
}
Or disable indentation for all List<long> values by adding NoIndentationConverter<List<long>> to JsonSerializerOptions.Converters as follows (demo #2 here):
var options = new JsonSerializerOptions
{
Converters = { new NoIndentationConverter<List<long>>() },
WriteIndented = true,
};
Both approaches result in your model being serialized as follows:
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [304,16,16,16],
"SCREEN_BOUNDS": [485,159,64,64]
}
}
}
Notes:
If your arrays are very large, the temporary ArrayBufferWriter<byte> may consume substantial memory. You might look into using ArrayPoolBufferWriter<T> instead.
This approach does not work for a value that already has a custom JsonConverter applied. But you could rewrite that converter to use the same approach above.
You cannot disable indentation for a type by applying [JsonConverter(typeof(NoIndentationConverter))] directly to the type. Once a converter has been applied to a type, it is impossible to generate a "default" serialization using System.Text.Json. For details see this answer to How to use default serialization in a custom System.Text.Json JsonConverter?.
Original Answer
This is not possible currently with System.Text.Json (as of .NET 5). Let's consider the possibilities:
JsonSerializerOptions has no method to control indentation other than the Boolean property WriteIndented:
Gets or sets a value that defines whether JSON should use pretty printing.
Utf8JsonWriter has no method to modify or control indentation, as Options is a get-only struct-valued property.
In .Net Core 3.1, if I create a custom JsonConverter<T> for your TEXTURE_BOUNDS and SCREEN_BOUNDS lists and attempt set options.WriteIndented = false; during serialization, a System.InvalidOperationException: Serializer options cannot be changed once serialization or deserialization has occurred exception will be thrown.
Specifically, if I create the following converter:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection;
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
var old = options.WriteIndented;
try
{
options.WriteIndented = false;
JsonSerializer.Serialize(writer, new CollectionSurrogate<TCollection, TItem>(value), options);
}
finally
{
options.WriteIndented = old;
}
}
}
public class CollectionSurrogate<TCollection, TItem> : ICollection<TItem> where TCollection : ICollection<TItem>, new()
{
public TCollection BaseCollection { get; }
public CollectionSurrogate() { this.BaseCollection = new TCollection(); }
public CollectionSurrogate(TCollection baseCollection) { this.BaseCollection = baseCollection ?? throw new ArgumentNullException(); }
public void Add(TItem item) => BaseCollection.Add(item);
public void Clear() => BaseCollection.Clear();
public bool Contains(TItem item) => BaseCollection.Contains(item);
public void CopyTo(TItem[] array, int arrayIndex) => BaseCollection.CopyTo(array, arrayIndex);
public int Count => BaseCollection.Count;
public bool IsReadOnly => BaseCollection.IsReadOnly;
public bool Remove(TItem item) => BaseCollection.Remove(item);
public IEnumerator<TItem> GetEnumerator() => BaseCollection.GetEnumerator();
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((IEnumerable)BaseCollection).GetEnumerator();
}
And the following data model:
public partial class Root
{
[JsonPropertyName("TILESET")]
public string Tileset { get; set; }
[JsonPropertyName("TILES")]
public Tiles Tiles { get; set; }
}
public partial class Tiles
{
[JsonPropertyName("TILE_1")]
public Tile1 Tile1 { get; set; }
}
public partial class Tile1
{
[JsonPropertyName("NAME")]
public string Name { get; set; }
[JsonPropertyName("TEXTURE_BOUNDS")]
[JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))]
public List<long> TextureBounds { get; set; }
[JsonPropertyName("SCREEN_BOUNDS")]
[JsonConverter(typeof(CollectionFormattingConverter<List<long>, long>))]
public List<long> ScreenBounds { get; set; }
}
Then serializing Root throws the following exception:
Failed with unhandled exception:
System.InvalidOperationException: Serializer options cannot be changed once serialization or deserialization has occurred.
at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable()
at System.Text.Json.JsonSerializerOptions.set_WriteIndented(Boolean value)
at CollectionFormattingConverter`2.Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
at System.Text.Json.JsonPropertyInfoNotNullable`4.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer)
at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer)
at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteCore(Utf8JsonWriter writer, Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.WriteCore(PooledByteBufferWriter output, Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.WriteCoreString(Object value, Type type, JsonSerializerOptions options)
at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
Demo fiddle #1 here.
In .Net Core 3.1, if I create a custom JsonConverter<T> that creates a pre-formatted JsonDocument and then writes that out, the document will be reformatted as it is written.
I.e. if I create the following converter:
class CollectionFormattingConverter<TCollection, TItem> : JsonConverter<TCollection> where TCollection : class, ICollection<TItem>, new()
{
public override TCollection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> JsonSerializer.Deserialize<CollectionSurrogate<TCollection, TItem>>(ref reader, options)?.BaseCollection;
public override void Write(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options)
{
var copy = options.Clone();
copy.WriteIndented = false;
using var doc = JsonExtensions.JsonDocumentFromObject(new CollectionSurrogate<TCollection, TItem>(value), copy);
Debug.WriteLine("Preformatted JsonDocument: {0}", doc.RootElement);
doc.WriteTo(writer);
}
}
public static partial class JsonExtensions
{
public static JsonSerializerOptions Clone(this JsonSerializerOptions options)
{
if (options == null)
return new JsonSerializerOptions();
//In .Net 5 a copy constructor will be introduced for JsonSerializerOptions. Use the following in that version.
//return new JsonSerializerOptions(options);
//In the meantime copy manually.
var clone = new JsonSerializerOptions
{
AllowTrailingCommas = options.AllowTrailingCommas,
DefaultBufferSize = options.DefaultBufferSize,
DictionaryKeyPolicy = options.DictionaryKeyPolicy,
Encoder = options.Encoder,
IgnoreNullValues = options.IgnoreNullValues,
IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
MaxDepth = options.MaxDepth,
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
PropertyNamingPolicy = options.PropertyNamingPolicy,
ReadCommentHandling= options.ReadCommentHandling,
WriteIndented = options.WriteIndented,
};
foreach (var converter in options.Converters)
clone.Converters.Add(converter);
return clone;
}
// Copied from this answer https://stackoverflow.com/a/62998253/3744182
// To https://stackoverflow.com/questions/62996999/convert-object-to-system-text-json-jsonelement
// By https://stackoverflow.com/users/3744182/dbc
public static JsonDocument JsonDocumentFromObject<TValue>(TValue value, JsonSerializerOptions options = default)
=> JsonDocumentFromObject(value, typeof(TValue), options);
public static JsonDocument JsonDocumentFromObject(object value, Type type, JsonSerializerOptions options = default)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, options);
return JsonDocument.Parse(bytes);
}
}
Fully indented JSON is generated despite the fact that the intermediate JsonDocument doc was serialized without indentation:
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [
304,
16,
16,
16
],
"SCREEN_BOUNDS": [
485,
159,
64,
64
]
}
}
}
Demo fiddle #2 here.
And finally, in .Net Core 3.1, if I create a custom JsonConverter<T> that clones the incoming JsonSerializerOptions, modifies WriteIndented on the copy, then recursively serializes using the copied settings -- the modified value for WriteIndented is ignored.
Demo fiddle #3 here.
Apparently the JsonConverter architecture is going to be extensively enhanced in .Net 5 so you might re-test this option when it is released.
You might want to open an issue requesting this functionality, as there are multiple popular questions about how to do this with Json.NET (where it can be done with a converter):
How to apply indenting serialization only to some properties?
Newtonsoft inline formatting for subelement while serializing
Creating JSON without array indentation
Faced with the same problem. I need to write arrays in one row for json simplicity.
Latest version is here: https://github.com/micro-elements/MicroElements.Metadata/blob/master/src/MicroElements.Metadata.SystemTextJson/SystemTextJson/Utf8JsonWriterCopier.cs
Solution:
I Use reflection to create clone of Utf8JsonWriter with desired options (see class Utf8JsonWriterCopier.cs)
To check that API was not changed Clone calls Utf8JsonWriterCopier.AssertReflectionStateIsValid, also you can use it in your tests
Usage:
Create NotIndented copy of Utf8JsonWriter
Write array
Copy internal state back to original writer
Sample:
if (Options.WriteArraysInOneRow && propertyType.IsArray && writer.Options.Indented)
{
// Creates NotIndented writer
Utf8JsonWriter writerCopy = writer.CloneNotIndented();
// PropertyValue
JsonSerializer.Serialize(writerCopy, propertyValue.ValueUntyped, propertyType, options);
// Needs to copy internal state back to writer
writerCopy.CopyStateTo(writer);
}
Utf8JsonWriterCopier.cs
/// <summary>
/// Helps to copy <see cref="Utf8JsonWriter"/> with other <see cref="JsonWriterOptions"/>.
/// This is not possible with public API so Reflection is used to copy writer internals.
/// See also: https://stackoverflow.com/questions/63376873/in-system-text-json-is-it-possible-to-specify-custom-indentation-rules.
/// Usage:
/// <code>
/// if (Options.WriteArraysInOneRow and propertyType.IsArray and writer.Options.Indented)
/// {
/// // Create NotIndented writer
/// Utf8JsonWriter writerCopy = writer.CloneNotIndented();
///
/// // Write array
/// JsonSerializer.Serialize(writerCopy, array, options);
///
/// // Copy internal state back to writer
/// writerCopy.CopyStateTo(writer);
/// }
/// </code>
/// </summary>
public static class Utf8JsonWriterCopier
{
private class Utf8JsonWriterReflection
{
private IReadOnlyCollection<string> FieldsToCopyNames { get; } = new[] { "_arrayBufferWriter", "_memory", "_inObject", "_tokenType", "_bitStack", "_currentDepth" };
private IReadOnlyCollection<string> PropertiesToCopyNames { get; } = new[] { "BytesPending", "BytesCommitted" };
private FieldInfo[] Fields { get; }
private PropertyInfo[] Properties { get; }
internal FieldInfo OutputField { get; }
internal FieldInfo StreamField { get; }
internal FieldInfo[] FieldsToCopy { get; }
internal PropertyInfo[] PropertiesToCopy { get; }
public Utf8JsonWriterReflection()
{
Fields = typeof(Utf8JsonWriter).GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
Properties = typeof(Utf8JsonWriter).GetProperties(BindingFlags.Instance | BindingFlags.Public);
OutputField = Fields.FirstOrDefault(info => info.Name == "_output")!;
StreamField = Fields.FirstOrDefault(info => info.Name == "_stream")!;
FieldsToCopy = FieldsToCopyNames
.Select(name => Fields.FirstOrDefault(info => info.Name == name))
.Where(info => info != null)
.ToArray();
PropertiesToCopy = PropertiesToCopyNames
.Select(name => Properties.FirstOrDefault(info => info.Name == name))
.Where(info => info != null)
.ToArray();
}
public void AssertStateIsValid()
{
if (OutputField == null)
throw new ArgumentException("Field _output is not found. API Changed!");
if (StreamField == null)
throw new ArgumentException("Field _stream is not found. API Changed!");
if (FieldsToCopy.Length != FieldsToCopyNames.Count)
throw new ArgumentException("Not all FieldsToCopy found in Utf8JsonWriter. API Changed!");
if (PropertiesToCopy.Length != PropertiesToCopyNames.Count)
throw new ArgumentException("Not all FieldsToCopy found in Utf8JsonWriter. API Changed!");
}
}
private static readonly Utf8JsonWriterReflection _reflectionCache = new Utf8JsonWriterReflection();
/// <summary>
/// Checks that reflection API is valid.
/// </summary>
public static void AssertReflectionStateIsValid()
{
_reflectionCache.AssertStateIsValid();
}
/// <summary>
/// Clones <see cref="Utf8JsonWriter"/> with new <see cref="JsonWriterOptions"/>.
/// </summary>
/// <param name="writer">Source writer.</param>
/// <param name="newOptions">Options to use in new writer.</param>
/// <returns>New copy of <see cref="Utf8JsonWriter"/> with new options.</returns>
public static Utf8JsonWriter Clone(this Utf8JsonWriter writer, JsonWriterOptions newOptions)
{
AssertReflectionStateIsValid();
Utf8JsonWriter writerCopy;
// Get internal output to use in new writer
IBufferWriter<byte>? output = (IBufferWriter<byte>?)_reflectionCache.OutputField.GetValue(writer);
if (output != null)
{
// Create copy
writerCopy = new Utf8JsonWriter(output, newOptions);
}
else
{
// Get internal stream to use in new writer
Stream? stream = (Stream?)_reflectionCache.StreamField.GetValue(writer);
// Create copy
writerCopy = new Utf8JsonWriter(stream, newOptions);
}
// Copy internal state
writer.CopyStateTo(writerCopy);
return writerCopy;
}
/// <summary>
/// Clones <see cref="Utf8JsonWriter"/> and sets <see cref="JsonWriterOptions.Indented"/> to false.
/// </summary>
/// <param name="writer">Source writer.</param>
/// <returns>New copy of <see cref="Utf8JsonWriter"/>.</returns>
public static Utf8JsonWriter CloneNotIndented(this Utf8JsonWriter writer)
{
JsonWriterOptions newOptions = writer.Options;
newOptions.Indented = false;
return Clone(writer, newOptions);
}
/// <summary>
/// Clones <see cref="Utf8JsonWriter"/> and sets <see cref="JsonWriterOptions.Indented"/> to true.
/// </summary>
/// <param name="writer">Source writer.</param>
/// <returns>New copy of <see cref="Utf8JsonWriter"/>.</returns>
public static Utf8JsonWriter CloneIndented(this Utf8JsonWriter writer)
{
JsonWriterOptions newOptions = writer.Options;
newOptions.Indented = true;
return Clone(writer, newOptions);
}
/// <summary>
/// Copies internal state of one writer to another.
/// </summary>
/// <param name="sourceWriter">Source writer.</param>
/// <param name="targetWriter">Target writer.</param>
public static void CopyStateTo(this Utf8JsonWriter sourceWriter, Utf8JsonWriter targetWriter)
{
foreach (var fieldInfo in _reflectionCache.FieldsToCopy)
{
fieldInfo.SetValue(targetWriter, fieldInfo.GetValue(sourceWriter));
}
foreach (var propertyInfo in _reflectionCache.PropertiesToCopy)
{
propertyInfo.SetValue(targetWriter, propertyInfo.GetValue(sourceWriter));
}
}
/// <summary>
/// Clones <see cref="JsonSerializerOptions"/>.
/// </summary>
/// <param name="options">Source options.</param>
/// <returns>New instance of <see cref="JsonSerializerOptions"/>.</returns>
public static JsonSerializerOptions Clone(this JsonSerializerOptions options)
{
JsonSerializerOptions serializerOptions = new JsonSerializerOptions()
{
AllowTrailingCommas = options.AllowTrailingCommas,
WriteIndented = options.WriteIndented,
PropertyNamingPolicy = options.PropertyNamingPolicy,
DefaultBufferSize = options.DefaultBufferSize,
DictionaryKeyPolicy = options.DictionaryKeyPolicy,
Encoder = options.Encoder,
IgnoreNullValues = options.IgnoreNullValues,
IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
MaxDepth = options.MaxDepth,
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
ReadCommentHandling = options.ReadCommentHandling,
};
foreach (JsonConverter jsonConverter in options.Converters)
{
serializerOptions.Converters.Add(jsonConverter);
}
return serializerOptions;
}
}
I use a custom converter to collapse Arrays and Below is an example for a hash set.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace MyProject.Core.Converters
{
public class HashSetConverter : JsonConverter<HashSet<string>?>
{
public override HashSet<string>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, HashSet<string>? value, JsonSerializerOptions options)
{
if (value is not null)
{
writer.WriteRawValue($"[\"{string.Join("\", \"", value)}\"]");
}
}
}
public class ArrayConverter : JsonConverter<int[]?>
{
public override int[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, int[]? value, JsonSerializerOptions options)
{
if (value is not null)
{
writer.WriteRawValue($"[{string.Join(", ", value)}]");
}
}
}
}
Then I setup my Serializer
var serializerOptions = new JsonSerializerOptions()
{
Converters = { new HashSetConverter(), new ArrayConverter() },
WriteIndented = true,
};
var json = JsonSerializer.Serialize(
new
{
Name = "auto_tile_18",
TEXTURE_BOUNDS = new int[]
{
304,
16,
16,
16,
},
},
serializerOptions);
Resulting Json
{
"Name": "auto_tile_18",
"TEXTURE_BOUNDS": [304, 16, 16, 16]
}
If you don't mind using an open source package or source file, take a look at FracturedJson ( nuget ), ( project home ). I wrote it specifically to create output that is easy to read without wasting tons of vertical space.
Here's what the output looks like for the original poster's data, using default options:
{
"TILESET": "tilesets/HOW_TO_GET_TILESET_NAME_?",
"TILES": {
"TILE_1": {
"NAME": "auto_tile_18",
"TEXTURE_BOUNDS": [304, 16, 16, 16],
"SCREEN_BOUNDS": [485, 159, 64, 64]
}
}
}

How to create a dynamic object via composition rather than inheritence

I am aware that the classic way to create a dynamic object is to inherit from DynamicObject. However if I already have a class and I wish to add dynamic properties to subclasses of that then I am stuck.
Say I have a class ReactiveObject And I wish to add dynamic properties to it using DynamicObject. So I do this
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
public DynamicMetaObject GetMetaObject(Expression parameter)
{
...
}
}
I thought the easy way to do this might be to create an instance of DynamicObject and proxy the call to that.
public class MyDynamicObject : DynamicObject{}
public class MyReactiveObject : ReactiveObject, IDynamicMetaObjectProvider{
MyDynamicObject DynamicObject = new MyDynamicObject();
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return this.DynamicObject.GetMetaObject(parameter);
}
}
except that is not going to work because the returned meta object doesn't know anything about the methods on MyReactiveObject. Is there any easy way to do this without fully reimplementing DynamicObject.
I came across the following gist.
https://gist.github.com/breezhang/8954586
public sealed class ForwardingMetaObject : DynamicMetaObject
{
private readonly DynamicMetaObject _metaForwardee;
public ForwardingMetaObject(
Expression expression,
BindingRestrictions restrictions,
object forwarder,
IDynamicMetaObjectProvider forwardee,
Func<Expression, Expression> forwardeeGetter
) : base(expression, restrictions, forwarder)
{
// We'll use forwardee's meta-object to bind dynamic operations.
_metaForwardee = forwardee.GetMetaObject(
forwardeeGetter(
Expression.Convert(expression, forwarder.GetType()) // [1]
)
);
}
// Restricts the target object's type to TForwarder.
// The meta-object we are forwarding to assumes that it gets an instance of TForwarder (see [1]).
// We need to ensure that the assumption holds.
private DynamicMetaObject AddRestrictions(DynamicMetaObject result)
{
var restricted = new DynamicMetaObject(
result.Expression,
BindingRestrictions.GetTypeRestriction(Expression, Value.GetType()).Merge(result.Restrictions),
_metaForwardee.Value
);
return restricted;
}
// Forward all dynamic operations or some of them as needed //
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
return AddRestrictions(_metaForwardee.BindGetMember(binder));
}
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
return AddRestrictions(_metaForwardee.BindSetMember(binder, value));
}
public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder)
{
return AddRestrictions(_metaForwardee.BindDeleteMember(binder));
}
public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)
{
return AddRestrictions(_metaForwardee.BindGetIndex(binder, indexes));
}
public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)
{
return AddRestrictions(_metaForwardee.BindSetIndex(binder, indexes, value));
}
public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes)
{
return AddRestrictions(_metaForwardee.BindDeleteIndex(binder, indexes));
}
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindInvokeMember(binder, args));
}
public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindInvoke(binder, args));
}
public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args)
{
return AddRestrictions(_metaForwardee.BindCreateInstance(binder, args));
}
public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder)
{
return AddRestrictions(_metaForwardee.BindUnaryOperation(binder));
}
public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
{
return AddRestrictions(_metaForwardee.BindBinaryOperation(binder, arg));
}
public override DynamicMetaObject BindConvert(ConvertBinder binder)
{
return AddRestrictions(_metaForwardee.BindConvert(binder));
}
}
and so I wrote
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using ReactiveUI;
namespace Weingartner.Lens
{
public class Dyno : DynamicObject
{
private readonly DynamicNotifyingObject _D;
public Dyno(DynamicNotifyingObject d)
{
_D = d;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
bool ret = base.TryGetMember(binder, out result);
if (ret == false)
{
result = _D.GetPropertyValue(binder.Name);
if (result != null)
{
ret = true;
}
}
return ret;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
bool ret = base.TrySetMember(binder, value);
if (ret == false)
{
_D.SetPropertyValue(binder.Name, value);
ret = true;
}
return ret;
}
}
And the main object that inherits from ReactiveObject but we can also add dynamic properties to.
/// <summary>
/// An object you can add properties to at runtime which raises INPC events when those
/// properties are changed.
/// </summary>
[DataContract]
public class DynamicNotifyingObject : ReactiveObject, IDynamicMetaObjectProvider
{
#region Private Members
[DataMember]
private Dictionary<string, object> _dynamicProperties;
[DataMember]
private Dictionary<string, Type> _dynamicPropertyTypes;
[IgnoreDataMember]
private Dyno _dynamicObject { get; set; }
public Dyno DynamicObject
{
get
{
lock (this)
{
return _dynamicObject ?? (_dynamicObject = new Dyno(this));
}
}
}
#endregion Private Members
#region Constructor
public DynamicNotifyingObject() : this(new Tuple<string,Type>[] { }) { }
public DynamicNotifyingObject(IEnumerable<Tuple<string,Type>> propertyNames)
{
if (propertyNames == null)
{
throw new Exception("propertyNames is empty");
}
_dynamicProperties = new Dictionary<string, object>();
_dynamicPropertyTypes = new Dictionary<string, Type>();
foreach ( var prop in propertyNames )
{
AddProperty(prop.Item1, prop.Item2);
}
}
#endregion Constructor
#region Public Methods
public void AddProperty<T>( string propertyName, T initialValue )
{
_dynamicProperties.Add(propertyName, initialValue);
_dynamicPropertyTypes.Add(propertyName, typeof(T));
this.RaisePropertyChanged(propertyName);
}
public void AddProperty<T>( string propertyName)
{
AddProperty(propertyName, typeof(T));
}
public void AddProperty( string propertyName, Type type)
{
_dynamicProperties.Add(propertyName, null);
_dynamicPropertyTypes.Add(propertyName, type);
this.RaisePropertyChanged(propertyName);
}
public void SetPropertyValue<T>(string propertyName, T raw)
{
if (!_dynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist on " + GetType().Name);
}
var converter = DynamicLens2INPC.CreateConverter<T>(raw.GetType(), _dynamicPropertyTypes[propertyName]);
var value = converter(raw);
if (!value.Equals(_dynamicProperties[propertyName]))
{
_dynamicProperties[propertyName] = (object) value;
this.RaisePropertyChanged(propertyName);
}
}
public object GetPropertyValue(string propertyName)
{
if (!_dynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist " + GetType().Name);
}
return _dynamicProperties.ContainsKey(propertyName) ? _dynamicProperties[propertyName] : null;
}
#endregion Public Methods
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter)
{
return new ForwardingMetaObject(parameter, BindingRestrictions.Empty, this, DynamicObject,
// B's meta-object needs to know where to find the instance of B it is operating on.
// Assuming that an instance of A is passed to the 'parameter' expression
// we get the corresponding instance of B by reading the "B" property.
exprA => Expression.Property(exprA, nameof(DynamicObject))
);
}
}
public static class DynamicNotifyingObjectMixin
{
public static TRet RaiseAndSetIfChanged<TObj, TRet>(this TObj This, TRet newValue, ref TRet backingField, [CallerMemberName] string property = "")
where TObj : DynamicNotifyingObject
{
if (EqualityComparer<TRet>.Default.Equals(newValue, backingField))
{
return newValue;
}
This.RaisePropertyChanging(property);
backingField = newValue;
This.RaisePropertyChanged(property);
return newValue;
}
}
}
with a test case
using FluentAssertions;
using Xunit;
namespace Weingartner.Lens.Spec
{
public class DynamicNotifyingObjectSpec
{
class Fixture : DynamicNotifyingObject
{
public Fixture ():
base()
{
this.AddProperty<string>("A");
this.AddProperty<string>("B");
this.SetPropertyValue("A", "AAA");
this.SetPropertyValue("B", "BBB");
}
}
[Fact]
public void ShouldBeAbleToAddPropertiesLaterOn()
{
var ff = new Fixture();
ff.AddProperty<string>("newProp");
ff.AddProperty<string>("XXXX");
dynamic f = ff;
ff.SetPropertyValue("newProp", "CCC");
((object)(f.newProp)).Should().Be("CCC");
f.XXXX = "XXXX";
f.newProp = "DDD";
((object)(f.newProp)).Should().Be("DDD");
((object)(f.XXXX)).Should().Be("XXXX");
}
[Fact]
public void ShouldGenerateNotificationOnPropertyChange()
{
var a = new string []{"A"};
var b = new string []{"B"};
object oa = null;
object ob = null;
var f = new Fixture();
dynamic fd = f;
f.PropertyChanged += (sender, ev) =>
{
dynamic s = sender;
oa = s.A;
ob = s.B;
};
oa.Should().Be(null);
ob.Should().Be(null);
fd.A = "A";
oa.Should().Be("A");
ob.Should().Be("BBB");
fd.B = "B";
oa.Should().Be("A");
ob.Should().Be("B");
}
}
}
Another possibility is just to use this library
https://github.com/remi/MetaObject
using System;
using System.Dynamic;
public class MyClass : Whatever, IDynamicMetaObjectProvider {
// This 1 line is *ALL* you need to add support for all of the DynamicObject methods
public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression e)
{ return new MetaObject(e, this); }
// Now, if you want to handle dynamic method calls,
// you can implement TryInvokeMember, just like you would in DynamicObject!
public bool TryInvokeMember
(InvokeMemberBinder binder, object[] args, out object result) {
if (binder.Name.Contains("Cool")) {
result = "You called a method with Cool in the name!";
return true;
} else {
result = null;
return false;
}
}
}
and for my particular use case which is inheriting from ReactiveUI.Reactive object and having dynamic properties which support INPC I generated
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using ReactiveUI;
namespace Weingartner.Lens
{
/// <summary>
/// An object you can add properties to at runtime which raises INPC events when those
/// properties are changed.
/// </summary>
[DataContract]
public class DynamicNotifyingObject : ReactiveObject, IDynamicMetaObjectProvider
{
#region Private Members
[DataMember]
private Dictionary<string, object> _DynamicProperties;
[DataMember]
private Dictionary<string, Type> _DynamicPropertyTypes;
#endregion Private Members
#region Constructor
public DynamicNotifyingObject() : this(new Tuple<string,Type>[] { }) { }
public DynamicNotifyingObject(IEnumerable<Tuple<string,Type>> propertyNames)
{
if (propertyNames == null)
{
throw new Exception("propertyNames is empty");
}
_DynamicProperties = new Dictionary<string, object>();
_DynamicPropertyTypes = new Dictionary<string, Type>();
foreach ( var prop in propertyNames )
{
AddProperty(prop.Item1, prop.Item2);
}
}
#endregion Constructor
public void AddProperty<T>( string propertyName, T initialValue )
{
_DynamicProperties.Add(propertyName, initialValue);
_DynamicPropertyTypes.Add(propertyName, typeof(T));
this.RaisePropertyChanged(propertyName);
}
/// <summary>
/// Set the property. Will throw an exception if the property does not exist.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <param name="raw"></param>
public void SetPropertyValue<T>(string propertyName, T raw)
{
if (!_DynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist on " + GetType().Name);
}
var converter = DynamicLens2INPC.CreateConverter<T>(raw.GetType(), _DynamicPropertyTypes[propertyName]);
var value = converter(raw);
if (!value.Equals(_DynamicProperties[propertyName]))
{
this.RaisePropertyChanging(propertyName);
_DynamicProperties[propertyName] = (object) value;
this.RaisePropertyChanged(propertyName);
}
}
/// <summary>
/// Get the property. Will throw an exception if the property does not exist.
/// </summary>
/// <param name="propertyName"></param>
/// <returns></returns>
public object GetPropertyValue(string propertyName)
{
if (!_DynamicProperties.ContainsKey(propertyName))
{
throw new ArgumentException(propertyName + " property does not exist " + GetType().Name);
}
return _DynamicProperties.ContainsKey(propertyName) ? _DynamicProperties[propertyName] : null;
}
public bool HasDynamicProperty(string propertyName) => _DynamicProperties.ContainsKey(propertyName);
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression e) { return new MetaObject(e, this); }
/// <summary>
/// Support for MetaObject. See https://github.com/remi/MetaObject
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public bool TryGetMember(GetMemberBinder binder, out object result)
{
if (HasDynamicProperty(binder.Name))
{
result = GetPropertyValue(binder.Name);
return true;
}
// This path will return any real properties on the object
result = null;
return false;
}
/// <summary>
/// Support for MetaObject. See https://github.com/remi/MetaObject
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool TrySetMember(SetMemberBinder binder, object value)
{
if (HasDynamicProperty(binder.Name))
{
SetPropertyValue(binder.Name, value);
return true;
}
// This path will try to set any real properties on the object
return false;
}
}
}

Deserialization of JSON from previous data model with Custom Converters fails when deserializing collections of Interfaces

I am responsible for maintaining a game system where users persist JSON serialized POCOs in a local cache to preserve state of, e.g., a Character.
The newest version of the code has changed the data model of these serialized objects. In particular, a new interface was created. This is creating issues when deserializing old copies of characters into the new code. I am attempting to resolve these with custom converters, but I'm running into trouble.
old, serialized version:
public class Character{
public Skill Parent {get;set;}
public Dictionary<string,Skill} Skills {get;set;}
}
public class Skill {
//normal stuff here.
}
new version:
public class Character{
[JsonProperty, JsonConverter(typeof(ConcreteTypeConverter<Dictionary<string,Skill>>))]
public Dictionary<string,ISkill} Skills {get;set;}
}
public class Skill:ISkill {
//normal stuff here.
}
public interface ISkill{
//stuff that all skill-like things have here
}
I have further defined a custom converter class (having read this and this,
but i'm still running into trouble deserializing collections.
public class Extensions
{
//a lot of serializer extensions here including the Deserialize method
private static readonly CustomSerializationBinder Binder = new CustomSerializationBinder();
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple,
Binder = Binder,
};
}
public class CustomSerializationBinder : DefaultSerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return base.BindToType(assemblyName, typeName);
}
}
public class ConcreteTypeConverter<T> : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer,value); // serialization isnt't the problem.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (typeof (T)==typeof(Dictionary<string,Skill>))
{
var retVal = new object();
if (reader.TokenType == JsonToken.StartObject)
{
T instance = (T)serializer.Deserialize(reader, typeof(T)); //crashes here
retVal = new List<T>() { instance };
return retVal;
}
}
return serializer.Deserialize<T>(reader);
}
public override bool CanConvert(Type objectType)
{
return true; // kind of a hack
}
}
So i have an old Dictionary<string,Skill> and I can't cast that to Dictionary<string,ISkill> in any code-path that I can see. How should I resolve this?
Since your legacy JSON already contains type information for all objects including dictionary objects, what you need to do is to strip the type information for dictionaries and allow the deserialized dictionary type to be controlled by the code, not the JSON.
The following converter should do the job:
public class IgnoreDictionaryTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.GetDictionaryKeyValueTypes().Count() == 1;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var obj = JObject.Load(reader);
obj.Remove("$type");
using (var subReader = obj.CreateReader())
{
serializer.Populate(subReader, existingValue);
}
return existingValue;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class TypeExtensions
{
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<Type[]> GetDictionaryKeyValueTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
yield return intType.GetGenericArguments();
}
}
}
}
Then you could add it in settings, or apply it to the dictionary property in question:
public class Character
{
[JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
public IDictionary<string, ISkill> Skills { get; set; }
}
For the future, you might also want disable emitting of type information for dictionaries, since dictionaries are collections, and collection types are better specified by the code, not the JSON:
public class Character
{
[JsonConverter(typeof(IgnoreDictionaryTypeConverter))]
[JsonProperty(TypeNameHandling = TypeNameHandling.None)]
public IDictionary<string, ISkill> Skills { get; set; }
}

Serialisation of Object to Array

I have an object:
[JsonConverter(typeof(MessageConverter))]
public class Message
{
[JsonProperty(Order = 1)]
public long Id { get; set; }
[JsonProperty(Order = 2)]
public string Msg { get; set; }
[JsonProperty(Order = 3)]
public int Timestamp { get; set; }
}
Which I would like to serialise into an array in JSON of the following form:
[long, string, int]
This class would be nested in a hierarchy, so automatic conversion would be preferred.
I am currently using the following code, but this seems to contain a significant amount of repetition.
I was wondering if there were an attribute/more compact solution that would allow JSON.NET to use the provided attributes to provide the same functionality without the converter.
public class MessageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Message);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var arr = serializer.Deserialize<JToken>(reader) as JArray;
if (arr == null || arr.Count != 3)
throw new JsonSerializationException("Expected array of length 3");
return new Message
{
Id = (long)arr[0],
Msg = (string)arr[1],
Timestamp = (int)arr[2]
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var msg = value as Message;
var obj = new object[] { msg.Id, msg.Msg, msg.Timestamp };
serializer.Serialize(writer, obj);
}
}
If you are looking for that particular output, I don't think you'll find a cleaner solution than what you have. If your real goal is to serialize JSON in a compact way, look to standards like BSON or ProtoBuf
This should do your job. I just wrote for WRITE i.e. output.
namespace Test
{
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Reflection;
/// <summary>
/// Defines the custom JSON Converter of collection type that serialize collection to an array of ID for Ember.
/// </summary>
public class CustomConverter : JsonConverter
{
/// <summary>
/// Define the property name that is define in the collection type.
/// </summary>
private readonly string IDKEY = "Id";
private readonly string MSGKEY = "Msg";
private readonly string TIMEKEY = "Timestamp";
/// <summary>
/// It is write only convertor and it cannot be read back.
/// </summary>
public override bool CanRead
{
get { return false; }
}
/// <summary>
/// Validate that this conversion can be applied on IEnumerable type only.
/// </summary>
/// <param name="objectType">type of object</param>
/// <returns>Validated value in boolean</returns>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Message);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
/// <summary>
/// Write JSON data from IEnumerable to Array.
/// </summary>
/// <param name="writer">JSON Writer</param>
/// <param name="value">Value of an object</param>
/// <param name="serializer">JSON Serializer object</param>
public override void WriteJson(JsonWriter writer, object item, JsonSerializer serializer)
{
JArray array = new JArray();
PropertyInfo prop = item.GetType().GetProperty(IDKEY);
if (prop != null && prop.CanRead)
{
array.Add(JToken.FromObject(prop.GetValue(item, null)));
}
prop = item.GetType().GetProperty(MSGKEY);
if (prop != null && prop.CanRead)
{
array.Add(JToken.FromObject(prop.GetValue(item, null)));
}
prop = item.GetType().GetProperty(TIMEKEY);
if (prop != null && prop.CanRead)
{
array.Add(JToken.FromObject(prop.GetValue(item, null)));
}
array.WriteTo(writer);
}
}
}
You are telling JSON.NET that you want to use custom convertor. so where ever you want to use it, you will have to call through attribute.
I am also doing something similar and I have to call converter manually.
/// <summary>
/// Gets or sets collection of documents.
/// </summary>
[JsonConverter(typeof(IDWriteListConverter))]
public ICollection<Document> Documents { get; set; }
/// <summary>
/// Gets or sets collection of comments.
/// </summary>
[JsonConverter(typeof(IDWriteListConverter))]
public ICollection<Comment> Comments { get; set; }
/// <summary>
/// Gets or sets the collection of transactions.
/// </summary>
[JsonConverter(typeof(IDWriteListConverter))]
public virtual ICollection<Transaction> Transactions { get; set; }

How to convert booleans in the decoded json-file into lower case strings?

The class I'm decoding to uses string fields and the Newtonsoft default decoder converts the booleans in the json-file into uppercase strings. It probably invokes the ToString() of the Boolean type which results in either "True" or "False".
void Main()
{
var foo = JsonConvert.DeserializeObject<Foo>("{Prop:true}");
Console.WriteLine(foo.Prop); // output: True, desired output: true
}
public class Foo
{
public string Prop{get;set;}
}
Since the field can be either string or boolean in the json, I like to have a custom decoder that always converts json-booleans to "true" or "false" depending on the value.
Any help would be appreciated.
How about something like this:
class BoolStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(string) == objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
string str = token.Value<string>();
if (string.Equals("true", str, StringComparison.OrdinalIgnoreCase) ||
string.Equals("false", str, StringComparison.OrdinalIgnoreCase))
{
str = str.ToLower();
}
return str;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Demo:
class Program
{
static void Main(string[] args)
{
string json = #"
{
""Bool1"": true,
""Bool2"": ""TRUE"",
""Bool3"": false,
""Bool4"": ""FALSE"",
""Other"": ""Say It Isn't True!""
}";
Foo foo = JsonConvert.DeserializeObject<Foo>(json, new BoolStringConverter());
Console.WriteLine("Bool1: " + foo.Bool1);
Console.WriteLine("Bool2: " + foo.Bool2);
Console.WriteLine("Bool3: " + foo.Bool3);
Console.WriteLine("Bool4: " + foo.Bool4);
Console.WriteLine("Other: " + foo.Other);
}
}
class Foo
{
public string Bool1 { get; set; }
public string Bool2 { get; set; }
public string Bool3 { get; set; }
public string Bool4 { get; set; }
public string Other { get; set; }
}
Output:
Bool1: true
Bool2: true
Bool3: false
Bool4: false
Other: Say It Isn't True!
/// <summary>
/// Implements a <see cref="JsonStringBoolConverter"/> that will handle serialization of Json Boolean values to strings
/// with capital letter.
/// </summary>
/// <summary>
/// Starting from Newtonsoft.Json v9.0.1 default converting logic has been changed
/// Old logic:
/// json boolean 'true' => .NET string "True"
///
/// New logic:
/// json boolean 'true' => .NET string "true"
///
/// Details: https://github.com/JamesNK/Newtonsoft.Json/issues/1019
/// </summary>
public sealed class JsonBooleanStringConverter : JsonConverter
{
/// <summary>
/// See <see cref="JsonConverter.CanConvert"/>.
/// </summary>
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
/// <summary>
/// Specifies that this converter will not participate in writting.
/// </summary>
public override bool CanWrite
{
get
{
return false;
}
}
/// <summary>
/// See <see cref="JsonConverter.ReadJson"/>.
/// </summary>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
string str = token.Value<string>();
if (token.Type == JTokenType.Boolean)
{
if (string.Equals("true", str, StringComparison.OrdinalIgnoreCase))
{
str = "True";
}
else if (string.Equals("false", str, StringComparison.OrdinalIgnoreCase))
{
str = "False";
}
}
return str;
}
/// <summary>
/// See <see cref="JsonConverter.WriteJson"/>.
/// </summary>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

Categories