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; }
Related
I have a JSON input similar to this simplified example.
{
"model1": {
"$type": "MyType, MyAssembly",
"A": 5
},
"model2": {
"C": "something"
}
What I'd like to achieve is a "hybrid" result, which I mean a top-level ExpandoObject, having two properties model1 and model2, BUT model1 would have a strong type of MyType (based on the Json.NET type information. As model2 doesn't have type information, it would be a nested ExpandoObject. This logic should be the same across deeper nesting levels as well (see my update), the example is simplified in this regard.
My problem is that I can't achieve the "hybridness". One way I could have a completely strongly typed result (if the top-level object would be strongly typed), the other way I can have a completely dynamic result (everything is ExpandoObject, or the third way I could have a JObject which is meaningless in this scenario.
// this will give a fully dynamic result, regardless the child type information
var result = JsonConvert.DeserializeObject<ExpandoObject>(input, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
UPDATE
I've just experimented with deserializing into a generic IDictionary, and that way I can get strongly typed results for top-level child properties, which technically solves my example. However, at lower levels it's still not working, and gives a JObject result for untyped child properties. So overall it's not a good solution for my real use case.
The problem is that Json.NET's ExpandoObjectConverter simply does not handle any of its own metadata properties such as "$type", "id" or "$ref".
However, since Json.NET is open source and its MIT license allows modification, the easiest solution may be to make your own copy of ExpandoObjectConverter and adapt it to your needs, along the lines of Json.NET Deserialization into dynamic object with referencing. You'll need to copy some low-level JSON utilities as well:
/// <summary>
/// Converts an ExpandoObject to and from JSON.
/// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/ExpandoObjectConverter.cs
/// License: https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md
/// </summary>
public class TypeNameHandlingExpandoObjectConverter : JsonConverter
{
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// can write is set to false
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadValue(reader, serializer);
}
private object ReadValue(JsonReader reader, JsonSerializer serializer)
{
if (!reader.MoveToContent())
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
switch (reader.TokenType)
{
case JsonToken.StartObject:
return ReadObject(reader, serializer);
case JsonToken.StartArray:
return ReadList(reader, serializer);
default:
if (JsonTokenUtils.IsPrimitiveToken(reader.TokenType))
{
return reader.Value;
}
throw JsonSerializationExceptionHelper.Create(reader, string.Format("Unexpected token when converting ExpandoObject: {0}", reader.TokenType));
}
}
private object ReadList(JsonReader reader, JsonSerializer serializer)
{
IList<object> list = new List<object>();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
default:
object v = ReadValue(reader, serializer);
list.Add(v);
break;
case JsonToken.EndArray:
return list;
}
}
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
private object ReadObject(JsonReader reader, JsonSerializer serializer)
{
if (serializer.TypeNameHandling != TypeNameHandling.None)
{
var obj = JObject.Load(reader);
Type polymorphicType = null;
var polymorphicTypeString = (string)obj["$type"];
if (polymorphicTypeString != null)
{
if (serializer.TypeNameHandling != TypeNameHandling.None)
{
string typeName, assemblyName;
ReflectionUtils.SplitFullyQualifiedTypeName(polymorphicTypeString, out typeName, out assemblyName);
polymorphicType = serializer.Binder.BindToType(assemblyName, typeName);
}
obj.Remove("$type");
}
if (polymorphicType == null || polymorphicType == typeof(ExpandoObject))
{
using (var subReader = obj.CreateReader())
return ReadExpandoObject(subReader, serializer);
}
else
{
using (var subReader = obj.CreateReader())
return serializer.Deserialize(subReader, polymorphicType);
}
}
else
{
return ReadExpandoObject(reader, serializer);
}
}
private object ReadExpandoObject(JsonReader reader, JsonSerializer serializer)
{
IDictionary<string, object> expandoObject = new ExpandoObject();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
if (!reader.Read())
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
object v = ReadValue(reader, serializer);
expandoObject[propertyName] = v;
break;
case JsonToken.Comment:
break;
case JsonToken.EndObject:
return expandoObject;
}
}
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading ExpandoObject.");
}
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(ExpandoObject));
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite
{
get { return false; }
}
}
internal static class JsonTokenUtils
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/JsonTokenUtils.cs
public static bool IsPrimitiveToken(this JsonToken token)
{
switch (token)
{
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Undefined:
case JsonToken.Null:
case JsonToken.Date:
case JsonToken.Bytes:
return true;
default:
return false;
}
}
}
internal static class JsonReaderExtensions
{
// Adapted from internal bool JsonReader.MoveToContent()
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonReader.cs#L1145
public static bool MoveToContent(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
JsonToken t = reader.TokenType;
while (t == JsonToken.None || t == JsonToken.Comment)
{
if (!reader.Read())
{
return false;
}
t = reader.TokenType;
}
return true;
}
}
internal static class JsonSerializationExceptionHelper
{
public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs
var lineInfo = reader as IJsonLineInfo;
var path = (reader == null ? null : reader.Path);
var message = string.Format(CultureInfo.InvariantCulture, format, args);
if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
{
message = message.Trim();
if (!message.EndsWith(".", StringComparison.Ordinal))
message += ".";
message += " ";
}
message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
if (lineInfo != null && lineInfo.HasLineInfo())
message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
message += ".";
return new JsonSerializationException(message);
}
}
internal static class ReflectionUtils
{
// Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs
// I couldn't find a way to access these directly.
public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName)
{
int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName);
if (assemblyDelimiterIndex != null)
{
typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim();
assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim();
}
else
{
typeName = fullyQualifiedTypeName;
assemblyName = null;
}
}
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
{
int scope = 0;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
scope++;
break;
case ']':
scope--;
break;
case ',':
if (scope == 0)
{
return i;
}
break;
}
}
return null;
}
}
Then use it like:
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
TypeNameHandling = TypeNameHandling.Auto,
Converters = new [] { new TypeNameHandlingExpandoObjectConverter() },
};
var expando2 = JsonConvert.DeserializeObject<ExpandoObject>(input, settings);
Prototype fiddle.
Finally, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:
TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.
For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.
Here's how I would do it:
void Main()
{
var json = "{\r\n \"model1\": {\r\n \"$type\": \"MyType, MyAssembly\",\r\n \"A\": 5\r\n },\r\n \"model2" +
"\": {\r\n \"C\": \"something\"\r\n}}";
var result = JsonConvert.DeserializeObject<Result>(json);
}
public class Result
{
public MyType Model1 { get; set; }
public ExpandoObject Model2 { get; set;}
}
public class MyType { public int A { get; set;} }
You can also give Result.Model2 a type of dynamic (which allows you to access its properties using syntax like result.Model2.something), or JSON.NET's JObject, which is more JSON-oriented.
However, if you're saying that you don't want a class like Result, but you want the JSON's $type to be able to determine a specific instance type, you can use the TypeNameHandling setting.
var result = JsonConvert.DeserializeObject<ExpandoObject>(
json,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
Just be aware that there are security implications if you're allowing client-provided JSON values to instantiate arbitrary types in your .NET environment.
A bit of an old thread, but here is what can be done.
First construct your ExpandoObject. For instance:
dynamic someObject = new ExpandoObject();
someObject.Name = "My Expando Object";
someObject.SomeProperty = 123;
I would then suggest to use "JsonConvert" in order to serialise this object (I know you want to do it the other way around, but bear with me).
So let's serialise "someObject" into a test.json file as per:
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All // Or Use .Auto for light weigth output
};
File.WriteAllText(#"e:\test.json", JsonConvert.SerializeObject(someObject, settings));
Now, if you open the resulting Json file, you will be able to see the exact syntax required by this JsonConverter. You are now able to write your own Json and do the inverse operation. ie. Deserializing your own Json file into a dynamic object as here below:
dynamic test = JsonConvert.DeserializeObject<ExpandoObject>(File.ReadAllText(#"e:\yourOwnJsonFile.json"), settings);
Finally, in order to access your object's properties, do something of the sort:
((dynamic)test.Name = "My Own Expando Object";
Converting JSON DateTime Values with only zeros (e.g. "0000-00-00 00:00:00") does not work with the standard Json.net IsoDateTimeConverter. I developed a custom converter saving this values DateTime.MinValue. Also a DateTime.MinValue will be written as the a "ZeroDateString". All other strings are handled by the base IsoDateTimeConverter class.
I use this converter on JsonNet annotations for DateTime properties.
Is there a better, easier way to handle this, e.g. on a mor basic level, where no annotations are needed?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace DataLayer
{
/// <summary>
/// Custom IsoDateTimeConverter for DateTime strings with zeros.
///
/// Usage Sample
/// [JsonConverter(typeof(ZerosIsoDateTimeConverter), "yyyy-MM-dd hh:mm:ss", "0000-00-00 00:00:00")]
/// public DateTime Zerodate { get; set; }
/// </summary>
public class ZerosIsoDateTimeConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter
{
/// <summary>
/// The string representing a datetime value with zeros. E.g. "0000-00-00 00:00:00"
/// </summary>
private readonly string _zeroDateString;
/// <summary>
/// Initializes a new instance of the <see cref="ZerosIsoDateTimeConverter"/> class.
/// </summary>
/// <param name="dateTimeFormat">The date time format.</param>
/// <param name="zeroDateString">The zero date string.
/// Please be aware that this string should match the date time format.</param>
public ZerosIsoDateTimeConverter(string dateTimeFormat, string zeroDateString)
{
DateTimeFormat = dateTimeFormat;
_zeroDateString = zeroDateString;
}
/// <summary>
/// Writes the JSON representation of the object.
/// If a DateTime value is DateTime.MinValue than the zeroDateString will be set as output value.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateTime && (DateTime) value == DateTime.MinValue)
{
value = _zeroDateString;
serializer.Serialize(writer, value);
}
else
{
base.WriteJson(writer, value, serializer);
}
}
/// <summary>
/// Reads the JSON representation of the object.
/// If an input value is same a zeroDateString than DateTime.MinValue will be set as return value
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return reader.Value.ToString() == _zeroDateString
? DateTime.MinValue
: base.ReadJson(reader, objectType, existingValue, serializer);
}
}
}
I ran into the same issue and I used a custom converter for this...
class MyDateConverter: Newtonsoft.Json.Converters.IsoDateTimeConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value != null && reader.Value.ToString().StartsWith("0000")) return null;
else return base.ReadJson(reader, objectType, existingValue, serializer);
}
}
You can do that using a ContractResolver as explained in the documentation:
The IContractResolver interface provides a way to customize how the JsonSerializer serializes and deserializes .NET objects to JSON without placing attributes on your classes.
Anything that can be set on an object, collection, property, etc, using attributes or methods to control serialization can also be set using an IContractResolver.
http://james.newtonking.com/json/help/index.html?topic=html/ContractResolver.htm
Example:
using System;
using System.Windows.Forms;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private string json = #"
{
""Date"": ""0000-00-00 00:00:00""
}
";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var myClass = new MyClass();
var deserializeObject = JsonConvert.DeserializeObject<MyClass>(json,
new JsonSerializerSettings {ContractResolver = new CustomDateContractResolver()});
string serializeObject = JsonConvert.SerializeObject(myClass, Formatting.Indented,
new JsonSerializerSettings {ContractResolver = new CustomDateContractResolver()});
}
}
internal class MyClass
{
public DateTime DateTime { get; set; }
}
internal class CustomDateContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
JsonContract contract = base.CreateContract(objectType);
bool b = objectType == typeof (DateTime);
if (b)
{
contract.Converter = new ZerosIsoDateTimeConverter("yyyy-MM-dd hh:mm:ss", "0000-00-00 00:00:00");
}
return contract;
}
}
}
But as #Jeroen Mostert pointed out, you should just use the 'regular' behavior for potentially not running into trouble later and having to follow this custom logic wherever you'll use dates.
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();
}
}
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);
I have the following app that shows that the key part of a Dictionary is not sent to JsonConverter, but it is called ToString() on. This is an issue for me as I can't deserialize my Json string .
Any ideas?
class Program
{
static void Main(string[] args)
{
var coll = new Dictionary<Tuple<string,string>, string>();
coll.Add(Tuple.Create("key1", "KEY1"), "Value1");
coll.Add(Tuple.Create("key2", "KEY2"), "Value2");
string json = JsonConvert.SerializeObject(coll);
Dictionary<Tuple<string, string>, string> coll2;
Console.WriteLine(json);
//coll2 = JsonConvert.DeserializeObject<Dictionary<Tuple<string, string>, string>>(json);
// It throws an exception here
//foreach (var k in coll2)
//{
// Console.WriteLine("<{0}|{1}>",k.Key, k.Value);
//}
var t = Tuple.Create("key1", "key2");
Console.WriteLine(t.ToString());
string json2 = JsonConvert.SerializeObject(t);
Console.WriteLine(json2);
}
}
Output :
{"(key1, KEY1)":"Value1","(key2, KEY2)":"Value2"} (key1, key2)
{"Item1":"key1","Item2":"key2"}
Press any key to continue . . .
I also had the same problem with Deserializing a Dictionary with Tuple as key. JSON converts the tuple into a mere string. But in my case, i cannot avoid using Tuple as key in the dictionary. So i made a custom JSON convertor to Deserialize the Dictionary with Tuple as key and it worked well.
I have modified the same as per your code. Hope it will work fine and can give you an idea about JSON CustomConverter. Also explained better with comments.
public class TupleKeyConverter : JsonConverter
{
/// <summary>
/// Override ReadJson to read the dictionary key and value
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType"></param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns></returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
Tuple<string, string> _tuple = null;
string _value = null;
var _dict = new Dictionary<Tuple<string, string>, string>();
//loop through the JSON string reader
while (reader.Read())
{
// check whether it is a property
if (reader.TokenType == JsonToken.PropertyName)
{
string readerValue = reader.Value.ToString();
if (reader.Read())
{
// check if the property is tuple (Dictionary key)
if (readerValue.Contains('(') && readerValue.Contains(')'))
{
string[] result = ConvertTuple(readerValue);
if (result == null)
continue;
// Custom Deserialize the Dictionary key (Tuple)
_tuple = Tuple.Create<string, string>(result[0].Trim(), result[1].Trim());
// Custom Deserialize the Dictionary value
_value = (string)serializer.Deserialize(reader, _value.GetType());
_dict.Add(_tuple, _value);
}
else
{
// Deserialize the remaining data from the reader
serializer.Deserialize(reader);
break;
}
}
}
}
return _dict;
}
/// <summary>
/// To convert Tuple
/// </summary>
/// <param name="_string"></param>
/// <returns></returns>
public string[] ConvertTuple(string _string)
{
string tempStr = null;
// remove the first character which is a brace '('
if (_string.Contains('('))
tempStr = _string.Remove(0, 1);
// remove the last character which is a brace ')'
if (_string.Contains(')'))
tempStr = tempStr.Remove(tempStr.Length - 1, 1);
// seperate the Item1 and Item2
if (_string.Contains(','))
return tempStr.Split(',');
return null;
}
/// <summary>
/// WriteJson needs to be implemented since it is an abstract function.
/// </summary>
/// <param name="writer"></param>
/// <param name="value"></param>
/// <param name="serializer"></param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
/// <summary>
/// Check whether to convert or not
/// </summary>
/// <param name="objectType"></param>
/// <returns></returns>
public override bool CanConvert(Type objectType)
{
return true;
}
}
Now declare a property as follows. JsonConvertor Property is important.
[JsonConverter(typeof(TupleKeyConverter))]
public Dictionary<Tuple<int,string>,string> MyDict {get; set;}
Or you could try this to replace this in your code. though i never tested.
coll2 = JsonConvert.DeserializeObject<Dictionary<Tuple<string, string>, string>>("", new TupleKeyConverter());
Based on the information you have provided, I would suggest that instead of using a Tuple as your key, use a custom struct or object and override the ToString method. Then you can serialize/deserialize as you wish.