Assuming a json string like the following:
string json = '{"string_property":"foo_bar", ... other objects here ...}';
I was wondering if there's a way to run a transformation on the parsed object so that instead of getting foo_bar, I'll get foo bar after running the following method (can be anything really)
public string Transform(string s) {
return s.Replace("_"," ");
}
I can manually alter my poco after deserializing, but wondered what would be a "cleaner" approach?
You can transform your string properties as you deserialize your root object by using a custom JsonConverter targeted at all string type values:
public class ReplacingStringConverter : JsonConverter
{
readonly string oldValue;
readonly string newValue;
public ReplacingStringConverter(string oldValue, string newValue)
{
if (string.IsNullOrEmpty(oldValue))
throw new ArgumentException("string.IsNullOrEmpty(oldValue)");
if (newValue == null)
throw new ArgumentNullException("newValue");
this.oldValue = oldValue;
this.newValue = newValue;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return s.Replace(oldValue, newValue);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then use it like:
var settings = new JsonSerializerSettings { Converters = new[] { new ReplacingStringConverter("_", "") } };
var result = JsonConvert.DeserializeObject<RootObject>(json, settings);
Note however that if individual string-type properties have their own converters applied directly with [JsonConverter(Type)], those converters will be used in preference to the ReplacingStringConverter in the Converters list.
I've ended up doing the following:
First, create a converter that only reads and all it does is url decode the string.
public class UrlDecoderConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JToken.Load(reader);
return HttpUtility.UrlDecode(s);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, simply add the following to the POCO properties that need to be decoded:
[JsonConverter(typeof(UrlDecoderConverter))]
public string url { get; set; }
Related
Here is my dto of action for json input :
public class GreetRequest
{
public string Message{get;set;}
}
When the json is post from the client:
{
"Message":true
}
the json.net would convert the Boolean to string automatically:
true -> “true”
By far,I wrote a converter to enforce the rule:
public class StrictStringConverter : JsonConverter
{
readonly JsonSerializer defaultSerializer = new JsonSerializer();
public override bool CanConvert(Type objectType)
{
return objectType.IsStringType();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.String:
case JsonToken.Null:
return defaultSerializer.Deserialize(reader, objectType);
default:
throw new JsonSerializationException();
}
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static bool IsStringType(this Type type)
{
type = Nullable.GetUnderlyingType(type) ?? type;
if (type == typeof(string))
return true;
return false;
}
}
the converter is configure at startup.cs
services.Configure<MvcNewtonsoftJsonOptions>(options => {
options.SerializerSettings.Converters.Add(new StrictStringConverter());
});
Question:
Is there any out of box way of strict conversion for json input, without writing any converter manually.
Some time ago I needed a custom generic json-converter which automatically would convert object to collection if needed. My solution was:
public class SingleValueCollectionConverter<T> : 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)
{
return reader.TokenType == JsonToken.StartArray ? serializer.Deserialize(reader, objectType) : new List<T> { (T)serializer.Deserialize(reader, typeof(T)) };
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
It worked fine until I've got the situation when I needed to use it for the property with generic type. I've got an error for using it like this:
[JsonConverter(typeof(SingleValueCollectionConverter<TData>))]
public List<TData> Data { get; set; }
It turned out that generic parameters cannot appear in attribute declarations. I found this pull-request to newtonsoftJsonConverter - https://github.com/JamesNK/Newtonsoft.Json/issues/1332 where the decision was suggested but it was not approved by the author.
So, I created non-generic converter which works universally like generic with the help of reflection:
public class SingleValueCollectionConverter : 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)
{
foreach (var prop in objectType.GetProperties())
{
var type = prop.PropertyType;
if (!type.IsClass)
continue;
var destination = Activator.CreateInstance(objectType);
var result = reader.TokenType == JsonToken.StartArray
? serializer.Deserialize(reader, objectType)
: new List<object> { serializer.Deserialize(reader, type) };
return Mapper.Map(result, destination, result.GetType(), destination.GetType());
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
Using like this:
[JsonConverter(typeof(SingleValueCollectionConverter))]
public List<TData> Data { get; set; }
It works fine, but maybe somebody has better decision. I'll be appreciated for any suggestions.
I try to create custom JSON converter by using code for some answered question.
public class SingleValueArrayConverter<T> : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject)
{
T instance = serializer.Deserialize<T>(reader);
return new List<T>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize<List<T>>(reader);
}
return null;
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
This is my sample model class.
public class Foo
{
[JsonProperty("type")]
public string Type;
}
I use the following code to execute. It causes error about stack overflow.
var converter = new SingleValueArrayConverter<Foo>();
var test1 = "[{ \"type\": \"test\" }]";
var result1 = JsonConvert.DeserializeObject<List<Foo>>(test1, converter);
var test2 = "{ \"type\": \"test\" }";
var result2 = JsonConvert.DeserializeObject<List<Foo>>(test2, converter);
The easiest way to convert any JSON to .NET object is using "ToObject" method of JToken object.
public class FooConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<Foo>>();
}
var item = token.ToObject<Foo>();
return new List<Foo> { item };
}
public override bool CanConvert(Type objectType)
{
return true;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
PS. JToken object is the base class of any JSON data so we can convert JToken object to any appropriated type.
JToken - abstract base class
JContainer - abstract base class of JTokens that can contain other JTokens
JArray - represents an JSON array (contains an ordered list of JTokens)
JObject - represents an JSON object (contains a collection of JProperties)
JProperty - represents a JSON property (a name/JToken pair inside a JObject)
JValue - represents a primitive JSON value (string, number, boolean, null)
I have this structure :
List<dynamic> lst = new List<dynamic>();
lst.Add(new{objId = 1,myOtherColumn = 5});
lst.Add(new{objId = 2,myOtherColumn = 6});
lst.Add(new{lala = "asd" ,lala2 = 7});
I'm serializing it via :
string st= JsonConvert.SerializeObject(lst);
Question:
How can I cause the serializer to change only values of the "objId" property , to something else ?
I know I should use class Myconverter : JsonConverter , but I didn't find any example which keeps the default behavior and in addition - allow me to add condition logic of serialization.
Here's a converter that will handle it, at least for simple objects as per your example. It looks for objects containing objId properties and then serialises all properties it finds on them. You may need to expand it to deal with other member types/more complex properties as required:
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var prop in value.GetType().GetProperties()) {
writer.WritePropertyName(prop.Name);
if (prop.Name == "objId") {
//modify objId values for example
writer.WriteValue(Convert.ToInt32(prop.GetValue(value, null)) + 10);
} else {
writer.WriteValue(prop.GetValue(value, null));
}
}
writer.WriteEndObject();
}
public override bool CanConvert(Type objectType)
{
//only attempt to handle types that have an objId property
return (objectType.GetProperties().Count(p => p.Name == "objId") == 1);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Alternatively, you can use a converter that specifies it will only convert int types, then query where in the JSON path you are before doing any conversions. This has the benefit of not needing to deal with all the other members of the anonymous type.
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (writer.Path.EndsWith(".objId")) {
writer.WriteValue(Convert.ToInt32(value) + 10);
}
else {
writer.WriteValue(value);
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (int);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Is it possible in Json.NET to serialize a root-level type in the serialization hierarchy differently than a reference encountered at a lower level in the hierarchy?
For example, with this type
class Serialized {
public Serialized Serialized;
public int A;
}
in this setup
var serialized = new Serialized() { A = 1 };
var serialized2 = new Serialized() { A = 2 };
serialized.Serialized = serialized2;
string json = GetJson(serialized);
where json is
{
"A":1
"Serialized": {
"ref":2
}
}
Specifically, the root-level serialization should use the default serialization strategy and the lower-level ones should use a custom converter (or similar).
You need to define custom JsonConverter and decorate the Serialized property with it:
public class PropertyConverter : 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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
writer.WriteEndObject();
}
}
And your class:
class Serialized {
[JsonConverter(typeof(PropertyConverter))
public Serialized Serialized;
public int A;
}
This will work only for one level of nesting. If you need it to work of multiple nested levels, you need to modify the converter:
public class PropertyConverter : 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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
if(serialized.Serialized != null)
{
writer.WritePropertyName("nested");
writer.WriteValue(JsonConvert.SerializeObject(serialized.Serialized, new JsonConverter[] {new PropertyConverter()}));
}
writer.WriteEndObject();
}
}