I have a list of JSON objects with the simplified structure shown here:
{
"id": 7,
"type": "int32",
"default": 110
}
The "type" property could vary (int16, int32, float, etc) and the "default" property should correctly reflect the type. Here is my generic C# class:
public class ConfigurationParameter<T>
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("default")]
public T Default { get; set; }
}
My goal here is to read in a list of these JSON objects deserialize them into the typed version of ConfigurationParameter based on the "type" property. I started by creating a custom JSON converter:
public class ConfigurationParameterConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
if (obj["type"] != null)
{
if(obj["type"].ToString() == "int32")
{
return obj.ToObject<ConfigurationParameter<int>>();
}
else if (obj["type"].ToString() == "float")
{
return obj.ToObject<ConfigurationParameter<float>>();
}
}
return null;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
But when I am actually deserializing the JSON string, I am not sure what type to request back since I won't know what the outputted strongly typed object will be?
//doesn't compile
var typedConfigurationParameter = JsonConvert.DeserializeObject<ConfigurationParameter<T>>(json, settings);
I am not quite sure if this is something I can't do because it can't be figured out at runtime? Or if I am possibly just not approaching the solution correctly?
Im using restsharp 105.2.3 and I have a model class's property (CalcDate) bound to a DateTime type but I am using a converter to parse the incoming rest response to build the timestamp. This works well, unless the svc doesn't return "calcDate"; When it's missing, the model fails to deserialized. The error that i get from IRestResponse.ErrorMessage is:
"Value to add was out of range.\r\nParameter name: value"
Interestingly if I use the raw json (with the missing calcDate) and try to construct it using jsonConvert, then it works as expected and the model is built with nulled calcDate.
> Newtonsoft.Json.JsonConvert.DeserializeObject<MyModel>(json) // works
Code:
public class MyModel {
[JsonProperty("id")]
public int Id { get; set; }
[JsonConverter(typeof(TimestampConverter))]
[JsonProperty("calcDate")]
public DateTime? CalcDate { get; set; }
}
public class TimestampConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jvalue = JValue.Load(reader);
if (jvalue.Type == JTokenType.String)
{
long val = 0;
if (long.TryParse(jvalue.ToString(), out val)) {
DateTimeOffset dto = DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(val));
return dto.DateTime;
}
}
return DateTime.MinValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Question: How can i make restsharp use jsonConvert to deserialize the json?
I am receiving json from a server which I am converting to an object using Json.Net. For one member I am using the StringEnumConverter which works really perfect.
However all of a sudden the server decided to use strings which also can start with a number which results in a JsonSerilizationException - obviously because enums cannot start with a number in .Net.
Now I am trying to find a solution to handle that.My first approach was to add a "_" when Reading the Json (so my enums in the code would have a starting _ when they are followed by a number) and when writing the json I would delete the starting _ (if a number is following). To achieve this I copied the StringEnumConverter into my namespace and tried to change the according part in the WriteJson and ReadJson methods. However I cannot use the StringEnumConverter since there are other dependencies I cannot access in my own namespace.
Is there any elegant solution to this problem?
You can create a JsonConverter and trim the digits from the front
public class Json_34159840
{
public static string JsonStr = #"{""enum"":""1Value"",""name"":""James"",""enum2"":""1""}";
public static void ParseJson()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EnumConverter() }
};
// Later on...
var result = JsonConvert.DeserializeObject<JsonClass>(JsonStr);
Console.WriteLine(result.Enum);
Console.WriteLine(result.Enum2);
Console.WriteLine(result.Name);
}
}
public class EnumConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var str = value.ToString();
if (Regex.IsMatch(str, #"^_"))
{
writer.WriteValue(str.Substring(1));
}
else
{
writer.WriteValue(str);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value.ToString();
if (Regex.IsMatch(value, #"^\d+$"))
{
return Enum.Parse(objectType, value);
}
if (Regex.IsMatch(value, #"^\d+"))
{
value = "_" + value;
}
return Enum.Parse(objectType, value);
}
public override bool CanConvert(Type objectType)
{
//You might want to do a more specific check like
//return objectType == typeof(JsonEnum);
return objectType.IsEnum;
}
}
public enum JsonEnum
{
_0Default,
_1Value
}
public class JsonClass
{
public string Name { get; set; }
public JsonEnum Enum { get; set; }
public JsonEnum Enum2 { get; set; }
}
Hope this helps.
EDIT: Added support for integers :D
A simple strategy is to deserialize the value into a string property and then convert into your own data type (enum or otherwise) via an accessor method or a secondary getter-only property.
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]);
}
}
using the code...
[Test]
public void test()
{
var entity = new Foo();
var json = Newtonsoft.Json.JsonConvert.SerializeObject(entity);
}
against the following trivial class structure...
public class Foo
{
public Foo() { FooDate = new DateTimeWrapper(); }
public DateTimeWrapper FooDate { get; set; }
}
public class DateTimeWrapper
{
public DateTimeWrapper() { DateTime = DateTime.Now; }
public DateTime DateTime { get; set; }
}
...sets the json variable to...
{"FooDate":{"DateTime":"2013-08-30T13:36:15.4862093-05:00"}}
The JSON I want to return is...
{"FooDate":"2013-08-30T13:36:15.4862093-05:00"}
without the embedded DateTime part. How can JSON.net be used to serialize to this custom JSON and subsequently deserialize the above string to the original object?
EDIT
I am aware the object structure can be simplified to produce the desired output. However, I want to produce the output with the given object structure. This code is boiled down to highlight the problem. I didn't put all the code nor a lengthy explanation of it b/c it isn't pertinent to the question.
As I said in comments, It can easily be done by creating a custom JsonConverter
var entity = new Foo();
var json = JsonConvert.SerializeObject(entity, new DateTimeWrapperConverter());
public class DateTimeWrapperConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTimeWrapper);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//Left as an exercise to the reader :)
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var obj = value as DateTimeWrapper;
serializer.Serialize(writer, obj.DateTime);
}
}