I have a generic class, whose children I want to serialize with the value of only one of its attributes.
To this end, I wrote a custom JsonConverter and attached it to the base class with the JsonConverter(Type) Attribute - however, it does not ever seem to be called. For reference, as shown in the example below, I am serializing a List<> of the object using the System.Web.Mvc.Controller.Json() method.
If there is an altogether better way of achieving the same result, I'm absolutely open to suggestions.
Example
View function
public JsonResult SomeView()
{
List<Foo> foos = GetAListOfFoos();
return Json(foos);
}
Custom JsonConverter
class FooConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
System.Diagnostics.Debug.WriteLine("This never seems to be run");
// This probably won't work - I have been unable to test it due to mentioned issues.
serializer.Serialize(writer, (value as FooBase<dynamic, dynamic>).attribute);
}
public override void ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
System.Diagnostics.Debug.WriteLine("This never seems to be run either");
return objectType.IsGenericType
&& objectType.GetGenericTypeDefinition() == typeof(FooBase<,>);
}
}
Foo base class
[JsonConverter(typeof(FooConverter))]
public abstract class FooBase<TBar, TBaz>
where TBar : class
where TBaz : class
{
public TBar attribute;
}
Foo implementation
public class Foo : FooBase<Bar, Baz>
{
// ...
}
Current output
[
{"attribute": { ... } },
{"attribute": { ... } },
{"attribute": { ... } },
...
]
Desired output
[
{ ... },
{ ... },
{ ... },
...
]
What happened to me was, I added the using statement automatically as suggested by Visual Studio. And by mistake added using System.Text.Json.Serialization; instead of using Newtonsoft.Json;
So I was using System.Text.Json.Serialization.JsonConverterAttribute on the target class. Which is (correctly) ignored by Json.Net.
First of all System.Web.Mvc.Controller.Json() doesn't work with Json.NET - it uses JavaScriptSerializer that doesn't know anything about your Json.NET stuff. If you still want to use System.Web.Mvc.Controller.Json() call you should do something like this. Also change WriteJson to this:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((dynamic)value).attribute);
}
I think this should make your code work.
Documentation says:
To apply a JsonConverter to the items in a collection use either JsonArrayAttribute, JsonDictionaryAttribute or JsonPropertyAttribute and set the ItemConverterType property to the converter type you want to use.
http://james.newtonking.com/json/help/html/SerializationAttributes.htm
Maybe that will help.
Related
I am trying to deserialize a subclass in a .NET 5 api project. The model binder is handing back a null even though my custom JSON converter is being called and is returning the correct object.
So that I can have polymorphic deserialization, I'm using the NewtonSoft serializer, added like so:
services
.AddControllers()
.AddNewtonsoftJson(opts =>
{
opts.SerializerSettings.Converters.Insert(0, new RecordJsonConverter());
});
And I have Microsoft.AspNetCore.Mvc.NewtonsoftJson added to the project.
The classes are like:
public class RecordDTO
{
[...]
}
public class ARecordDTO : RecordDTO
{
[...]
}
The controller accepts the base class:
public async override Task<IActionResult> Post([FromBody] RecordDTO dto)
And the JSON Converter:
public class RecordJsonConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// Only objects of the base type RecordDTO need to be converted by this custom converter.
// If it is already knows which derived class it is, then we
// can just let it use the standard converter.
var recordType = typeof(RecordDTO);
return recordType.IsAssignableFrom(objectType) && !objectType.IsSubclassOf(recordType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
[...]
return target;
}
}
When I post an ARecordDTO object, I see ReadJson on my custom RecordJsonConverter being called and filling out an ARecord object and returning it correctly.
But then we're back inside the controller action, and the dto argument is now null instead of being the ARecordDTO that was just created for binding.
Has anyone else encountered this? What would cause the object returned by the JSON converter to be discarded during binding?
Well, in case anyone else shows up here and was similarly uninformed:
If there are any errors present in the ModelState, then the action argument binds as null (rather than just skipping the single field that had the problem and returning the rest of the object, which is the behavior I would've assumed.)
Suppose I have these classes:
public class Bar
{
public Foo MyFoo { get; set; }
}
public class Foo
{
public string[] Stuff { get; set; }
}
And I have this JSON structure:
{
"MyFoo":
{
"Stuff":"those,are,my,stuff"
}
}
And I have a code path where a JObject is being converted to Bar using code like below:
myJObject.ToObject(typeof(Bar))
Now what I need to do is to supply the ToObject with a custom serializer to convert the string property Stuff into an array of string (using string.Split(...).ToArray())
I was asked not to add attributes on the client class 'Bar' so after looking around it seemed like a ContractResolver is in order but the issue is that the resolver only lets me handle direct properties of the root Type, that is Bar in my example, and I can't register a JsonConverter on a nested property.
So my question to you guys is, is this even achievable using Json.net?
Note that I need to do this not only for the Bar class but to an unlimited amount of classes with unknown structure so I can't hard-code a solution that will work for one type of class.
Based on your description, I don't see why you would need a ContractResolver. You know that the properties you want to deserialize specially will always be of type string[], so just make a converter that handles that type. Maybe something like this:
public class CsvStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
return null;
if (token.Type == JTokenType.String)
return ((string)token).Split(',');
if (token.Type == JTokenType.Array)
return token.ToObject<string[]>(serializer);
throw new JsonException("Unexpected token type: " + token.Type);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to use it with your JObject, create a new JsonSerializer instance, add the converter to it, and pass the serializer to the ToObject() method like this:
var serializer = new JsonSerializer();
serializer.Converters.Add(new CsvStringConverter());
var bar = myJObject.ToObject<Bar>(serializer);
Working demo here: https://dotnetfiddle.net/qmeBoh
I'm using Json.NET to serialize validation data for data field. On the .NET side, validation data is a list of ValidationAttribute objects. However, I'd like to serialize them in a special form like this:
[
{ Type: 'Required', ErrorMessage: '{FieldName} is required' },
{ Type: 'RegularExpression', Pattern: '^\d+$', ErrorMessage: '...'
]
In an ideal solution I could simply intercept the object before serialization and, I could create a corresponding Dictionary<string, object> object to serialize instead of the original one.
Are there any solutions for this scenario?
You can implement your own JsonConverter class and convert your collection as you wish.
You just need to create you class and inherit it from JsonConverter
public class YourSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(YourClassName).IsAssignableFrom(objectType);
}
}
and then you need to decorate your class which will be serialized with the attribute (looks like it's not what you want)
[JsonConverter(typeof(YourSerializer))]
public class YourClassName
{
public string Name { get; set; }
public string Value { get; set; }
}
or, pass an instance of your serializer to Serialize methos:
string json = JsonConvert.SerializeObject(sourceObj, Formatting.Indented, new YourSerializer(typeof(yourClassName)));
Here is a few links:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/
Hope, it will help.
Using Json.Net, I have properties in my objects which need special care in order to serialize / deserialize them. Making a descendant of JsonConverter, I managed to accomplish this successfully. This is the common way of doing this:
public class SomeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
...
}
public override bool CanConvert(Type objectType)
{
...
}
}
class SomeClass
{
[JsonProperty, JsonConverter(typeof(SomeConverter))]
public SomeType SomeProperty;
}
//Later on, in code somewhere
SomeClass SomeObject = new SomeClass();
string json = JsonConvert.SerializeObject(SomeObject, new SomeConverter());
My problem with this code is that I need to introduce my custom converter in every serialization / deserialization. In my project there are many cases that I cannot do that. For instance, I'm using other external projects which make use of Json.Net as well and they will be working on my SomeClass instances. But since I don't want to or can't make change in their code, I have no way to introduce my converter.
Is there any way I can register my converter, using some static member perhaps, in Json.Net so no matter where serialization / deserialization happens, my converter is always present?
Yes, this is possible using Json.Net 5.0.5 or later. See JsonConvert.DefaultSettings.
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new SomeConverter() }
};
// Later on...
string json = JsonConvert.SerializeObject(someObject); // this will use SomeConverter
If you're using Web API, you can set up a converter globally like this instead:
var config = GlobalConfiguration.Configuration;
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.Converters.Add(new SomeConverter());
Another approach (which wins in priority over the one #Brian mentions above) is to implement a custom contract resolver
JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();
And the implementation is rather straightforward
public class CustomContractResolver : DefaultContractResolver
{
private static readonly JsonConverter _converter = new MyCustomConverter();
private static Type _type = typeof (MyCustomType);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == null || !_type.IsAssignableFrom(objectType)) // alternatively _type == objectType
{
return base.ResolveContractConverter(objectType);
}
return _converter;
}
}
Both methods are valid, this one is just a bigger hammer
This approach from the question ASP.NET Web API Custom JsonConverter is never called works with Web API:
// Add a custom converter for Entities.
foreach (var formatter in GlobalConfiguration.Configuration.Formatters)
{
var jsonFormatter = formatter as JsonMediaTypeFormatter;
if (jsonFormatter == null)
continue;
jsonFormatter.SerializerSettings.Converters.Add(new MyConverter());
}
Just put it somewhere into Global.asax.
The other answers didn't work for me. DefaultSettings has no effect on Web API actions, and the JsonFormatter configuration property does not seem to exist in the .NET framework version I use.
I'm using JSON.Net in the trial to serialize/deserialize a not-so-well-formed JSON.
Specifically I'm trying to serialize an attribute with a js variable as value, something like this:
{ ..., mapTypeId: google.maps.MapTypeId.ROADMAP, ... }
My c# classes are
public enum MapTypeId
{
HYBRID, ROADMAP, SATELLITE, TERRAIN
}
[JsonObject]
public class MapOptions
{
...
[JsonProperty]
[JsonConverter(typeof(MapTypeConverter))]
public MapTypeId mapTypeId { get; set; }
...
}
With a custom JsonConverter I've been able to control the serialization:
public class MapTypeConverter : JsonConverter
{
...
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue("google.maps.MapTypeId." + ((MapTypeId)value).ToString());
}
...
}
And the serialization works. But when I'm trying to deserialize the string with:
JsonConvert.DeserializeObject<MapOptions>(ser);
I get the error: Unexpected character encountered while parsing value: g. Path 'mapTypeId', line 1, position 169.
In my MapTypeConverter I've also tryed to override the ReadJson method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// no parse by now, just a try
return MapTypeId.TERRAIN;
}
without luck.
Suggestions?