Next, we need to create a converter to go between bool values and Visibility. Here's that:
namespace DictionaryApp.Model
{
public class VisiableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
bool visibility = (bool)value;
return visibility ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
Visibility visibility = (Visibility)value;
return (visibility == Visibility.Visible);
}
}
}
Next, add the converter in the UserControl's resources. In my simple project, I had to create this section - but hopefully you're already using this in your real apps!
You may also need to add an xmlns attribute in page.xaml for your project's namespace.
x:Class="DictionaryApp.PageDictionary"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DictionaryApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ms="using:DictionaryApp.Model"
mc:Ignorable="d">
<UserControl.Resources>
<ms:VisiableConverter x:Key="VisiableConverter"></ms:VisiableConverter>
</UserControl.Resources>
The name “XYZ” does not exist in the namespace “axy
Related
I have a bunch of enums that I want Json.NET to serialize as camelcased strings. I have the following in my Global.asax.cs file and it's working great:
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter { CamelCaseText = true });
This makes it so an enum like this:
public enum FavoriteWebSite {
StackOverflow,
GoogleNews
// Etc
}
will serialize to values like "stackOverflow", "googleNews", etc.
However, I have a couple enums that are bitwise masks. To make this a simple example, suppose one looks like this:
public enum Hobbies {
Walking = 0x01,
Biking = 0x02,
// Etc
}
What happens when I serialize instances of this enum depends on what kind of values are in it. For example:
Hobbies set1 = Hobbies.Walking; // Serializes as "walking" -- bad
Hobbies set2 = Hobbies.Walking | Hobbies.Biking; // Serializes as "3" -- good!
I want to override the serialization on this enum to just serialize as an int, while leaving the global setting to use camelcased strings intact.
I tried removing the global configuration so that enums by default are serialized as ints, then adding only [JsonConverter(typeof(StringEnumConverter))] to the non-bitmask enums. However, this results in PascalCased, rather than CamelCased serialization for those. I didn't see any way to get the CamelCaseText attribute set when using StringEnumConverter in a method decoration like above.
So, to recap, the goal is:
Have single-value enums be serialized as pascalCased strings.
Have bitmask enums be serialized as ints.
Thank you!
Your main difficulty appears to be that you are not decorating your flag enums with FlagsAttribute, like so:
[Flags]
public enum Hobbies
{
Walking = 0x01,
Biking = 0x02,
// Etc
}
This is the recommended best practice for flag enums:
Designing Flag Enums
√ DO apply the System.FlagsAttribute to flag enums. Do not apply this attribute to simple enums.
See also here. If you don't do this, many enum-related .Net utilities may not work as expected for flag enumerations.
Having done this, StringEnumConverter will serialize flag enums with composite values as a set of comma-separated values instead of as the numeric value you are currently seeing:
{
"Hobbies": "walking, biking"
}
If you don't want this and still prefer to see default, numeric values for flag enums in your JSON, you can subclass StringEnumConverter to only convert non-flag enums:
public class NonFlagStringEnumConverter : StringEnumConverter
{
public override bool CanConvert(Type objectType)
{
if (!base.CanConvert(objectType))
return false;
return !HasFlagsAttribute(objectType);
}
static bool HasFlagsAttribute(Type objectType)
{
return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
}
}
Then use it like:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new NonFlagStringEnumConverter { CamelCaseText = true });
This will cause Json.NET to fall back on any global default JSON converter for enums, or to numeric serialization if there is no applicable fallback. Demo fiddle #1 here.
Additionally, if you need to supersede a converter applied at a higher level and force numeric serialization for flag enums, use the following:
public class ForceNumericFlagEnumConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (!(Nullable.GetUnderlyingType(objectType) ?? objectType).IsEnum)
return false;
return HasFlagsAttribute(objectType);
}
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return false; } }
static bool HasFlagsAttribute(Type objectType)
{
return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Demo fiddle #2 here.
This blog post explains pretty well that there is not built in way to override global StringEnumConverter. You need write you own converter that does nothing, then when converting JSON.NET will go back to default converter for that type (which for enums is serializing to it's numeric value).
So in case you have global converter:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter { CamelCaseText = true });
You can define this ForceDefaultConverter converter
public class ForceDefaultConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => throw new NotImplementedException();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
And use it on the property you want to override your default StringEnumConverter.
public class ExampleDto
{
[JsonConverter(typeof(ForceDefaultConverter))]
public TestEnum EnumValue { get; set; }
}
or on enum type itself if you want this numeric values when serializing all objects with this enum type.
[JsonConverter(typeof(ForceDefaultConverter))]
public enum TestEnum
{
Foo = 1,
Bar = 2
}
I implemented a custom JsonConverter for Guids.
If I declare it on the properties (of type Guid) of the class serialized like so
[JsonConverter(typeof(JsonGuidConverter))]
then it gets called and works fine.
However, I'd like to use it "automatically", without needed the attributes, so I do this:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new JsonGuidConverter());
Unfortunately this results in my converter never getting called. I'm using WebApi 2.1 in a MVC 5.1 project.
Any ideas?
Edit: here is the converter code
public class JsonGuidConverter : JsonConverter
{
public override bool CanRead
{
get
{
// We only need the converter for writing Guids without dashes, for reading the default mechanism is fine
return false;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Guid) || objectType == typeof(Guid?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
// We declared above CanRead false so the default serialization will be used
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
if (value == null || Guid.Empty.Equals(value))
{
writer.WriteValue(string.Empty);
}
else
{
writer.WriteValue(((Guid)value).ToStringNoDashes());
}
}
}
Additional note, not even the CanRead/CanWrite properties and CanConvert method are called when trying to use it by adding it to the converters collection.
Maybe it has something to do with how I return data from the webapi controller?
public async Task<IHttpActionResult> GetSettings()
{
...
return Json(something);
}
Since your are using formatters, do not use Json(something) when returning from action, but rather use Content(something) in this case. The Content helper will honor the formatters' settings.
I agree that Json helper is confusing here and something which I wished we never included in our product.
I reflected the JSON.NET JavaScriptDateTimeConverter class code, copied it, and renamed the class AS3DateTimeConverter so that I could modify it to format DateTime objects in a more precise and strongly-typed manor.
I have it output a type according to how JSON.NET outputs strongly-typed objects like so:
{"$type":"System.DateTime, mscorlib","ticks":0}
The overridden WriteJson method of the JsonConverter runs to produce that value.
However, when I try to deserialize the string using the exact same settings with the same converter, the overridden ReadJson method never gets a chance to run and construct a DateTime from the ticks property, because the following errors occurs:
Cannot deserialize the current JSON object (e.g. {"name":"value"})
into type 'System.DateTime' because the type requires a JSON primitive
value (e.g. string, number, boolean, null) to deserialize correctly.
To fix this error either change the JSON to a JSON primitive value
(e.g. string, number, boolean, null) or change the deserialized type
so that it is a normal .NET type (e.g. not a primitive type like
integer, not a collection type like an array or List) that can be
deserialized from a JSON object. JsonObjectAttribute can also be added
to the type to force it to deserialize from a JSON object.
Path 'ticks', line 1, position 45.
Is this some kind of bug or limitation, which will not allow me to revive a DateTime type because it is a value-type? Or am I missing something?
Here are the serialization settings:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add( new AS3DateTimeConverter() );
//settings.Binder = new AS3SerializationBinder();
string s = JsonConvert.SerializeObject( new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc ), settings );
object o = JsonConvert.DeserializeObject( s, settings ); //s = "{\"$type\":\"System.DateTime, mscorlib\",\"ticks\":0}" //ERROR OCCURS HERE
The problem seems to have something to do with deserializing a bare date. When the Date is wrapped in another object, it seems to work. This code works for me:
public class Program
{
public static void Main(string[] args)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add(new AS3DateTimeConverter());
TestObject obj = new TestObject { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
string s = JsonConvert.SerializeObject(obj, settings);
Console.WriteLine(s);
object o = JsonConvert.DeserializeObject(s, settings);
Console.WriteLine(((TestObject)o).Date.ToString());
}
}
public class TestObject
{
public DateTime Date { get; set; }
}
public class AS3DateTimeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jo = new JObject();
jo.Add("$type", "System.DateTime, mscorlib");
jo.Add("ticks", ((DateTime)value).Ticks);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new DateTime(jo["ticks"].Value<long>());
}
}
Output:
{"$id":"1","$type":"Q20224027.TestObject, JsonTest","Date":{"$type":"System.DateTime, mscorlib","ticks":621355968000000000}}
1/1/1970 12:00:00 AM
UPDATE
To test the theory of whether converters get called for custom top level objects with embedded type information, I made a converter for the date wrapper object and serialized that instead. This worked, but only if I gave it a hint by using DeserializeObject<T> instead of DeserializeObject. Here is the code:
namespace Q20224027
{
public class Program
{
public static void Main(string[] args)
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
settings.PreserveReferencesHandling = PreserveReferencesHandling.All;
settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
settings.ConstructorHandling = ConstructorHandling.Default;
settings.TypeNameHandling = TypeNameHandling.All;
settings.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
settings.DateParseHandling = DateParseHandling.DateTime;
settings.Converters.Add(new DateWrapperConverter());
DateWrapper obj = new DateWrapper { Date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) };
string s = JsonConvert.SerializeObject(obj, settings);
Console.WriteLine(s);
object o = JsonConvert.DeserializeObject<DateWrapper>(s, settings);
Console.WriteLine(((DateWrapper)o).Date.ToString());
}
}
public class DateWrapper
{
public DateTime Date { get; set; }
}
public class DateWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateWrapper);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateWrapper obj = (DateWrapper)value;
JObject jo = new JObject();
jo.Add("$type", typeof(DateWrapper).AssemblyQualifiedName);
jo.Add("ticks", obj.Date.Ticks);
jo.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
return new DateWrapper { Date = new DateTime(jo["ticks"].Value<long>()) };
}
}
}
Output:
{"$type":"Q20224027.DateWrapper, JsonTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","ticks":621355968000000000}
1/1/1970 12:00:00 AM
Workaround I found.
If the object is wrapped in a list before being serialized, then it works ok, but only if you decorate the class with a JsonConverter attribute specifying the converter. Adding the converter to the list of converters for the serializers settings is insufficient.
For example, if you have a "Node" class which has a "Child" Node member (i.e. the type has members of it's own type), and you nest some nodes, then what I've found is that the converter is not called during serialization for anything but the top node when you only add the converter to the list of converters. On the other hand, if you explicitly decorate the class with the converter, then all the child nodes are run through the converter's WriteJson method as expected. So this basically renders the "Converters" collection of the serializer's settings non-functional.
When the objects are members of an array and their type is decorated with an explicit converter, then their converter's ReadJson and WriteJson methods are called when the types are encountered during serialization and deserialization.
When receiving a JSON string from a client, there are only two options to get it to work. Either you manually wrap the string in an object including a generic List "$type" and embed the received value as the sole value in the "$values" array, or you must just avoid all this and hard-code the expected recieved object type by calling typed DeserializeObject<T> method. What a mess.
The only reason I can fathom any of this making sense is if the DeserializeObject (non-generic) method was explicitly intended to NOT call converters for the top-level object, presumably so that it could be used in custom converter's WriteJson method without causing recursive calls to the converter. If that's the case, the design is terrible, because it leads to all the problems I've discussed.
I had written these two converters but they are not' invoked... Do I need to add extra piece of code in minisetup.cs file for them to work
public class BooleanNegationValueConverter : IMvxValueConverter
{
#region IMvxValueConverter implementation
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (!(Boolean)(value));
}
public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException ();
}
#endregion
I am using this in the usual way
set.Bind(toolbar.hidden).To(vm => vm.toolbarstate).WithConversion("BooleanNegation");
I am using MvvmcrossCore
Figured it out on my own..
//Register the converters
IMvxValueConverterRegistry converterRegistery = ioc.Resolve<IMvxValueConverterRegistry>();
converterRegistery.AddOrOverwrite ("BooleanNegationConverter", new BooleanNegationValueConverter ());
converterRegistery.AddOrOverwrite ("ExchangeRatetoStringValueConverter", new ExchangeRatetoStringValueConverter ());
I am using webapi with DbGeography spatial data and want to serialize to json.
By default, DbGeography serializes to null. So I implemented my own converter for it.
Here is what I have so far, but it doesn't seem to work.
Basically, with the following code, my DbGeographyConverter.WriteJson method is never under debug and the Location property is serialized as null
Customer converter:
public class DbGeographyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DbGeography contextObj = value as DbGeography;
writer.WriteStartObject();
writer.WritePropertyName("Lat");
serializer.Serialize(writer, contextObj.Latitude);
writer.WritePropertyName("Long");
serializer.Serialize(writer, contextObj.Longitude);
writer.WriteEndObject();
}
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(DbGeography))
{
return true;
}
return false;
}
public override bool CanRead
{
get
{
return true;
}
}
public override bool CanWrite
{
get
{
return true;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
Add convert in Global.ascx.cs
protected void Application_Start()
{
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(
new DbGeographyConverter()
}
Finally, apply converter to the data model class property
public DataModelClass1
{
[JsonConverter (typeof(DbGeographyConverter))]
public DbGeography Location { get; set; }
}
First, since you're adding your custom converter to the SerializerSettings.Converters collection, you don't need to decorate your DataModelClass1's Location property with the JsonConverterAttribute- The JsonFormatter will run through the aforementioned collection until it finds the derived JsonConverter you added without the attribute.
Now back to your question- which browser are you testing this in and how? If I were to speculate, I'd say you're using either Chrome or Firefox with GET requests, both of which prioritize application/xml over application/json in the accept header they send to the server. For that reason Web API will see that the browsers prefer XML over JSON and the JsonFormatter will never be touched, let alone your custom JsonConverter.
There are a few workarounds to this. On the browser side the easiest way is to make ajax GET requests with jQuery and specify that you want JSON back. On the server side, you can remove application/xml from the SupportedMediaTypes.
I spent quite awhile on this. You'd only use the write method if you wanted to change the default output format of the Json for DbGeography from this
"geography": {
"coordinateSystemId": 4326,
"wellKnownText": "POINT (77.6599502563474 12.9602302518557)"
}
to something else like "77.22, 12.8" - just a single string.
If you're looking to convert a string like that to DbGeography on reading Json from the request, the code below is what you're after
public class DbGeographyConverter : JsonConverter
{
public override bool CanConvert ( Type objectType )
{
return objectType.IsAssignableFrom( typeof( string ) );
}
public override object ReadJson ( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
if ( reader.Value == null ) {
return null;
}
return Parser.ToDbGeography( reader.Value.ToString() );
}
public override void WriteJson ( JsonWriter writer, object value, JsonSerializer serializer )
{
// Base serialization is fine
serializer.Serialize( writer, value );
}
}
This is the code for the Converter if you're passing in a string value - 12,99
Which will be you're lat and lng
This sample application has the answer you're looking for.
https://code.msdn.microsoft.com/windowsazure/HTML-ASPNET-Web-API-Bing-58c97f9f
The Converter code from this can be found here
https://github.com/Azure-Samples/SQLDatabase-Spatial-WebAPI-BingMaps/blob/master/SpatialTypesWithWebAPI/Models/DbGeographyConverter.cs