I've pored through the docs, StackOverflow, etc., can't seem to find this...
What I want to do is serialize/deserialize a simple value-type of object as a value, not an object, as so:
public class IPAddress
{
byte[] bytes;
public override string ToString() {... etc.
}
public class SomeOuterObject
{
string stringValue;
IPAddress ipValue;
}
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new SomeOuterObject() {stringValue = "Some String", ipValue = ip};
string json = JsonConverter.SerializeObject(obj);
What I want is for the json to serialize like this:
// json = {"someString":"Some String","ipValue":"192.168.1.2"} <- value serialized as value, not subobject
Not where the ip becomes a nested object, ex:
// json = {"someString":"Some String","ipValue":{"value":"192.168.1.2"}}
Does anyone know how to do this? Thanks! (P.S. I am bolting Json serialization on a large hairy legacy .NET codebase, so I can't really change any existing types, but I can augment/factor/decorate them to facilitate Json serialization.)
You can handle this using a custom JsonConverter for the IPAddress class. Here is the code you would need:
public class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPAddress));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new IPAddress(JToken.Load(reader).ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken.FromObject(value.ToString()).WriteTo(writer);
}
}
Then, add a [JsonConverter] attribute to your IPAddress class and you're ready to go:
[JsonConverter(typeof(IPAddressConverter))]
public class IPAddress
{
byte[] bytes;
public IPAddress(string address)
{
bytes = address.Split('.').Select(s => byte.Parse(s)).ToArray();
}
public override string ToString()
{
return string.Join(".", bytes.Select(b => b.ToString()).ToArray());
}
}
Here is a working demo:
class Program
{
static void Main(string[] args)
{
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new SomeOuterObject() { stringValue = "Some String", ipValue = ip };
string json = JsonConvert.SerializeObject(obj);
Console.WriteLine(json);
}
}
public class SomeOuterObject
{
public string stringValue { get; set; }
public IPAddress ipValue { get; set; }
}
Output:
{"stringValue":"Some String","ipValue":"192.168.1.2"}
This is a answer to Customise NewtonSoft.Json for Value Object serialisation, in regards to value objects in DDD. But that question is marked as duplicate to this one, which i don't think is completely true.
I borrowed the code for the ValueObjectConverter from
https://github.com/eventflow/EventFlow, I have only done minor changes.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using Newtonsoft.Json;
using Xunit;
namespace Serialization
{
public class ValueObjectSerializationTests
{
class SomeClass
{
public IPAddress IPAddress { get; set; }
}
[Fact]
public void FactMethodName()
{
var given = new SomeClass
{
IPAddress = new IPAddress("192.168.1.2")
};
var jsonSerializerSettings = new JsonSerializerSettings()
{
Converters = new List<JsonConverter>
{
new ValueObjectConverter()
}
};
var json = JsonConvert.SerializeObject(given, jsonSerializerSettings);
var result = JsonConvert.DeserializeObject<SomeClass>(json, jsonSerializerSettings);
var expected = new SomeClass
{
IPAddress = new IPAddress("192.168.1.2")
};
json.Should().Be("{\"IPAddress\":\"192.168.1.2\"}");
expected.ShouldBeEquivalentTo(result);
}
}
public class IPAddress:IValueObject
{
public IPAddress(string value)
{
Value = value;
}
public object GetValue()
{
return Value;
}
public string Value { get; private set; }
}
public interface IValueObject
{
object GetValue();
}
public class ValueObjectConverter : JsonConverter
{
private static readonly ConcurrentDictionary<Type, Type> ConstructorArgumenTypes = new ConcurrentDictionary<Type, Type>();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is IValueObject valueObject))
{
return;
}
serializer.Serialize(writer, valueObject.GetValue());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var parameterType = ConstructorArgumenTypes.GetOrAdd(
objectType,
t =>
{
var constructorInfo = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
var parameterInfo = constructorInfo.GetParameters().Single();
return parameterInfo.ParameterType;
});
var value = serializer.Deserialize(reader, parameterType);
return Activator.CreateInstance(objectType, new[] { value });
}
public override bool CanConvert(Type objectType)
{
return typeof(IValueObject).IsAssignableFrom(objectType);
}
}
}
public class IPAddress
{
byte[] bytes;
public override string ToString() {... etc.
}
IPAddress ip = new IPAddress("192.168.1.2");
var obj = new () {ipValue = ip.ToString()};
string json = JsonConverter.SerializeObject(obj);
You are serializing the whole IP address instance. Maybe just try to serialize the address as a string. (This presumes that you have implemented the ToString-method.)
There are a couple of different ways to approach this depending on the level of effort you are able to expend and the tolerance for changes to existing classes.
One approach is to define your classes as DataContract and explicitly identify the elements within the class as DataMembers. Netwonsoft recognizes and uses these attributes in its serialization. The upside to this approach is that the classes will now be serializable using other approaches that use datacontract serialization.
[DataContract]
public class IPAddress
{
private byte[] bytes;
// Added this readonly property to allow serialization
[DataMember(Name = "ipValue")]
public string Value
{
get
{
return this.ToString();
}
}
public override string ToString()
{
return "192.168.1.2";
}
}
Here is the code that I used to serialize (I may be using an older version since I didn't see the SerializeObject method):
IPAddress ip = new IPAddress();
using (StringWriter oStringWriter = new StringWriter())
{
using (JsonTextWriter oJsonWriter = new JsonTextWriter(oStringWriter))
{
JsonSerializer oSerializer = null;
JsonSerializerSettings oOptions = new JsonSerializerSettings();
// Generate the json without quotes around the name objects
oJsonWriter.QuoteName = false;
// This can be used in order to view the rendered properties "nicely"
oJsonWriter.Formatting = Formatting.Indented;
oOptions.NullValueHandling = NullValueHandling.Ignore;
oSerializer = JsonSerializer.Create(oOptions);
oSerializer.Serialize(oJsonWriter, ip);
Console.WriteLine(oStringWriter.ToString());
}
}
Here is the output:
{
ipValue: "192.168.1.2"
}
Another approach is to create your own JsonConverter inheritor that can serialize exactly what you need without modifications to the internals of the class:
public class JsonToStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
writer.WriteStartObject();
writer.WritePropertyName(value.GetType().Name);
writer.WriteValue(Convert.ToString(value));
writer.WriteEndObject();
}
}
This class just writes the tostring value of the class along with the class name. Changing the name can be accomplished through additional attributes on the class, which I have not shown.
The class would then look like:
[JsonConverter(typeof(JsonToStringConverter))]
public class IPAddress
{
private byte[] bytes;
public override string ToString()
{
return "192.168.1.2";
}
}
And the output is:
{
IPAddress: "192.168.1.2"
}
With the Cinchoo ETL - an open source library to parsing / writing JSON files, you can control the serialization of each object member via ValueConverter or with callback mechanism.
Method 1:
The sample below shows how to serialize 'SomeOuterObject' using member level ValueConverters
public class SomeOuterObject
{
public string stringValue { get; set; }
[ChoTypeConverter(typeof(ToTextConverter))]
public IPAddress ipValue { get; set; }
}
And the value converter is
public class ToTextConverter : IChoValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
}
Finally to serialize the object to file
using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
)
{
var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
jr.Write(x1);
}
And the output is
[
{
"stringValue": "X1",
"ipValue": "12.23.21.23"
}
]
Method 2:
This the alternative method to hook up value converter callback to 'ipValue' property. This approach is lean and no need to create value converter for just this operation.
using (var jr = new ChoJSONWriter<SomeOuterObject>("ipaddr.json")
.WithField("stringValue")
.WithField("ipValue", valueConverter: (o) => o.ToString())
)
{
var x1 = new SomeOuterObject { stringValue = "X1", ipValue = IPAddress.Parse("12.23.21.23") };
jr.Write(x1);
}
Hope this helps.
Disclaimer: I'm the author of the library.
Here is a class for generic conversion of simple value objects that I plan to include in the next update of Activout.RestClient. A "simple value object" as an object that has:
No default constructor
A public property named Value
A constructor taking the same type as the Value property
var settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> {new SimpleValueObjectConverter()}
};
public class SimpleValueObjectConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var valueProperty = GetValueProperty(value.GetType());
serializer.Serialize(writer, valueProperty.GetValue(value));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var valueProperty = GetValueProperty(objectType);
var value = serializer.Deserialize(reader, valueProperty.PropertyType);
return Activator.CreateInstance(objectType, value);
}
public override bool CanConvert(Type objectType)
{
if (GetDefaultConstructor(objectType) != null) return false;
var valueProperty = GetValueProperty(objectType);
if (valueProperty == null) return false;
var constructor = GetValueConstructor(objectType, valueProperty);
return constructor != null;
}
private static ConstructorInfo GetValueConstructor(Type objectType, PropertyInfo valueProperty)
{
return objectType.GetConstructor(new[] {valueProperty.PropertyType});
}
private static PropertyInfo GetValueProperty(Type objectType)
{
return objectType.GetProperty("Value");
}
private static ConstructorInfo GetDefaultConstructor(Type objectType)
{
return objectType.GetConstructor(new Type[0]);
}
}
Related
I have a class which needs to serialize objects of any type to JSON.
If the object has one or more properties which are of type list I want to serialize the whole object but only serialize the first and last item in the list.
For example, I have the below code
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections;
using Newtonsoft.Json.Serialization;
using System.Linq;
using Newtonsoft.Json.Linq;
public class Program
{
public class Product{
public Product(string name, int price){
this.Name = name;
this.Price = price;
}
public string Name {get;set;}
public int Price {get;set;}
}
public class ProductResult{
public ProductResult(List<Product> products, int code){
this.Products = products;
this.Code = code;
}
public int Code {get;set;}
public List<Product> Products {get;set;}
}
public static string DoTheThing(object dynamicObject){
return JsonConvert.SerializeObject(dynamicObject);
}
public static void Main()
{
var list = new List<Product>(){new Product("product1",100),new Product("product2",100),new Product("product3",100),new Product("product4",100)};
var result = new ProductResult(list,0);
string jsonObj = DoTheThing(result);
Console.WriteLine(jsonObj);
// Output {"Code":0,"Products":[{"Name":"product1","Price":100},{"Name":"product2","Price":100},{"Name":"product3","Price":100},{"Name":"product4","Price":100}]}
}
}
I would like it to output the following, and I need it to be able to handle various object types.
{"Code":0,"Products":[{"Name":"product","Price":100},{"Name":"product","Price":100}]}
I took a look at using Custom JsonConverter and Custom ContractResolver but am not sure of how to implement these.
You can do this with a custom JsonConverter, for example:
public class FirstAndLastListConverter : JsonConverter
{
// We only care about List<> properties
public override bool CanConvert(Type objectType) =>
objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>);
// We are not deserialising, complete this if you really need to, but I don't see why you would
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)
{
// Convert the value to an IList
var elements = value as IList;
// Start a new array
writer.WriteStartArray();
// Serialise the first item
serializer.Serialize(writer, elements[0]);
// Serialise the last item
serializer.Serialize(writer, elements[elements.Count - 1]);
// End the array
writer.WriteEndArray();
}
}
And use it like this:
// A settings object to use our new converter
var settings = new JsonSerializerSettings
{
Converters = new [] { new FirstAndLastListConverter() }
};
// Use the settings when serialising
var json = JsonConvert.SerializeObject(result, settings);
You could use a Custom ContractResolver along with a ValueConverter. For example,
public class ListContractResolver : DefaultContractResolver
{
public new static readonly ListContractResolver Instance = new ListContractResolver ();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType.IsGenericType && property.PropertyType.GetInterfaces().Contains(typeof(IList)))
{
property.ValueProvider = new FilterListValueProvider(member as PropertyInfo);
}
return property;
}
}
public class FilterListValueProvider : IValueProvider
{
PropertyInfo _propertyInfo;
public FilterListValueProvider(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
}
public object GetValue(object target)
{
return GetList(target);
}
public IEnumerable GetList(object target)
{
var list = _propertyInfo.GetValue(target) as IList;
yield return list[0];
yield return list[list.Count - 1];
}
public void SetValue(object target, object value)
{
_propertyInfo.SetValue(target, value);
}
}
Updating your DoTheThing method to use the ListContractResolver we defined as
public static string DoTheThing(object dynamicObject)
{
return JsonConvert.SerializeObject(dynamicObject,
Newtonsoft.Json.Formatting.Indented,
new JsonSerializerSettings
{ ContractResolver = new ListContractResolver() });
}
Sample Output
{
"Code": 0,
"Products": [
{
"Name": "product1",
"Price": 100
},
{
"Name": "product4",
"Price": 100
}
]
}
Suppose I have a class like this:
public class Example {
public int TypedProperty { get; set; }
public object UntypedProperty { get; set; }
}
And suppose someone comes along and writes:
var example = new Example
{
TypedProperty = 5,
UntypedProperty = Guid.NewGuid()
}
If I serialize this with JsonConvert.SerializeObject(example), I get
{
"TypedProperty": 5,
"UntypedProperty": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
Ideally, I'd like to get something like this:
{
"TypedProperty": 5,
"UntypedProperty":
{
"$type": "System.Guid,mscorlib",
"$value": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
}
But TypeNameHandling doesn't work in this scenario. How can I (de)serialize an untyped property?
If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto,
then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.
The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:
public class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}
abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:
public class Example
{
public int TypedProperty { get; set; }
[JsonConverter(typeof(UntypedToTypedValueConverter))]
public object UntypedProperty { get; set; }
}
If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:
public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedPropertyContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }
public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.PropertyType == typeof(object)
&& property.Converter == null)
{
property.Converter = property.MemberConverter = converter;
}
}
return contract;
}
}
And use it as follows:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};
var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);
var example2 = JsonConvert.DeserializeObject<Example>(json, settings);
In both cases the JSON created looks like:
{
"TypedProperty": 5,
"UntypedProperty": {
"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
"Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
}
}
Lookup SerializeWithJsonConverters.htm and ReadingWritingJSON.
Call: JsonConvert.SerializeObject(example, new ObjectConverter());
class ObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Example);
}
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)
{
Example e = (Example)value;
writer.WriteStartObject();
writer.WritePropertyName("TypedProperty");
writer.WriteValue(e.TypedProperty);
writer.WritePropertyName("UntypedProperty");
writer.WriteStartObject();
writer.WritePropertyName("$type");
writer.WriteValue(e.UntypedProperty.GetType().FullName);
writer.WritePropertyName("$value");
writer.WriteValue(e.UntypedProperty.ToString());
writer.WriteEndObject();
writer.WriteEndObject();
}
}
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; }
}
I have easy example of my real code. I need serialize to JSON and deserialize back object of class TestClass, which is derived from Letters. Both classes have constructor with parameter.
public class TestClass : Letters
{
public string[] Names { get; private set; }
public TestClass(string[] names)
: base(names)
// : base(new [] { "A", "B", })
// : base(names.Select(a => a.Substring(0, 1)).ToArray())
{
Names = names;
}
}
public abstract class Letters
{
public string[] FirstLetters { get; private set; }
protected Letters(string[] letters)
{
FirstLetters = letters;
}
}
Object of TestClass is serialized to valid JSON, but when I try it deserialize back to object, NotSupportedException is throw with message Collection was of a fixed size.
Here is my test
[Fact]
public void JsonNamesTest()
{
var expected = new TestClass(new [] { "Alex", "Peter", "John", });
var serialized = JsonConvert.SerializeObject(expected);
Console.WriteLine(serialized);
Assert.False(string.IsNullOrWhiteSpace(serialized));
var actual = JsonConvert.DeserializeObject<TestClass>(serialized);
AssertEx.PrimitivePropertiesEqual(expected, actual);
}
Json.Net needs all classes to have a parameterless constructor in order to deserialize them, otherwise it doesn't know how to call the constructor. One way to get around this without changing your classes is to make a custom JsonConverter that will create the object instance from the JSON. For example:
class TestClassConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(TestClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string[] names = jo["Names"].ToObject<string[]>();
return new TestClass(names);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Then, deserialize your class like this and it should work:
var actual = JsonConvert.DeserializeObject<TestClass>(serialized, new TestClassConverter());
Thank, it works! I modified your code for more general usage in my example.
I suppose
is only one public constructor
serialized parameters have same name as constructor parameters (ignore case)
public class ParametersContructorConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Letters).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
var contructor = objectType.GetConstructors().FirstOrDefault();
if (contructor == null)
{
return serializer.Deserialize(reader);
}
var parameters = contructor.GetParameters();
var values = parameters.Select(p => jo.GetValue(p.Name, StringComparison.InvariantCultureIgnoreCase).ToObject(p.ParameterType)).ToArray();
return contructor.Invoke(values);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Is there any way to serialize objects of the following class and somehow ignore the exception being thrown?
public class HardToSerialize
{
public string IAmNotTheProblem { get; set; }
public string ButIAm { get { throw new NotImplementedException(); } }
}
Not suprisingly Newtonsoft throws an error when it tries to serialize the value of the ButIAm property.
I don't have access to the class so I can't decorate it with any attributes.
Clarification: I want this to work for any object that has properties that throws a NotImplementedException. The HardToSerialize class is just one example.
I found a solution that worked for me. Is there any major problems doing it like this?
var settings = new JsonSerializerSettings();
settings.Error += (o, args) => {
if(args.ErrorContext.Error.InnerException is NotImplementedException)
args.ErrorContext.Handled = true;
};
var s = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented, settings);
I would go for a surrogate class and a custom JsonConverter :
public class HardToSerializeSurrogate
{
public string IAmNotTheProblem { get; set; }
public string ButIAm { get; set; }
}
public class HardToSerializeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HardToSerialize);
}
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)
{
var item = (HardToSerialize)value;
// fill the surrogate with the values of the original object
var surrogate = new HardToSerializeSurrogate();
surrogate.IAmNotTheProblem = item.IAmNotTheProblem;
serializer.Serialize(writer, surrogate);
}
}
Usage:
static void Main(string[] args)
{
var hardToSerialize = new HardToSerialize() { IAmNotTheProblem = "Foo" };
var s = JsonConvert.SerializeObject(hardToSerialize,
new HardToSerializeConverter());
}
Of course implementing a custom JsonConverter is really worth if you must serialize a list of HardToSerialize objects, or an object that contains this type.
On the other hand, if you just want to serialize one HardToSerialize object each time, just create a surrogate copy of the object and serialize that without implementing a custom JsonConverter.
A possible workaround would be to create another object from EasyToSerialize and then serialize it.
[Serializable]
public class EasyToSerialize
{
public string IAmNotTheProblem { get; set; }
// other serializable properties
}
HardToSerialize x = ...;
var foo2 = new EasyToSerialize {
IAmNotTheProblem = x.IAmNotTheProblem
// other properties here
};