Reputation: 45
I have a large amount of JSON data stored in a database. To reduce the size of this data we are using [JsonProperty("")] tags. However legacy properties still use the full name, and changing those to a tag based system requires extensive workaround to slowly rebuilt the data when it's accessed.
Is there a setting or easy way to deserialize into a property based on its name and the JsonProperty tag.
Or will I have to write a custom deserializer?
Example:
This JSON
{
"coins": {
"Total": 1004
}
}
Will not deserialize into this object
[JsonProperty("c")]
public Coins coins { get; set;}
public Class Coins
{
public int Total { get; set;}
}
Removing the [JsonProperty("c")]
allows it to deserialize again.
However to convert the existing data we have to a more space efficient method I need to be able to read the existing data via the property name while the JsonProperty tag is on
Upvotes: 2
Views: 3604
Reputation: 1910
I'm not positive I'm following, but what I think is going on is that you have this JSON:
{
"coins": {
"Total": 1004
}
}
And
{
"c": {
"Total": 1004
}
}
And models like this:
public class CoinsWrapper
{
[JsonProperty("c")]
public Coins coins { get; set;}
}
public class Coins
{
public int Total { get; set;}
}
Solution 1 - Create Secondary Propety
public class CoinsWrapper
{
[JsonProperty("c")]
public Coins coins { get; set;}
[JsonProperty("coins")]
private Coins legacyCoins { set { coins = value; } }
}
And then when you deserialize it will assign the value to coins, and when you serialize it will ignore legacyCoins.
Solution 2 - use JsonExtensionData
and reflection to 'map' automatically
I decided to have a bit of fun with this... obviously you'll need to be careful with this because if your naming has any overlap you could cause things to blow up pretty easily.
void Main()
{
string json = @"{
""coins"": {
""Total"": 1004
}
}";
var wrapper = JsonConvert.DeserializeObject<CoinsWrapper>(json);
wrapper.Dump();
}
public class CoinsWrapper : LegacyAutoMap
{
[JsonProperty("c")]
public Coins coins { get; set; }
}
public abstract class LegacyAutoMap
{
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData;
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_additionalData == null) return;
var properties = this.GetType().GetProperties();
foreach (var entry in _additionalData)
{
var prop = properties.FirstOrDefault(p => p.Name.ToLowerInvariant() == entry.Key.ToLowerInvariant());
if (prop == null) continue;
JToken token = entry.Value;
MethodInfo ifn = typeof(JToken).GetMethod("ToObject", new Type[0]).MakeGenericMethod(new[] { prop.PropertyType });
prop.SetValue(this, ifn.Invoke(token, null));
}
_additionalData = null;
}
}
Solution 3 - Introduce a new custom attribute combined with solution 2
This is the most flexible and probably safest solution. Flexible because you can provide multiple alternate names, and safest because only those fields you specify will be 'auto mapped'.
void Main()
{
string json = @"
[
{
""coins"":
{
""Total"": 1004
}
},
{
""c"":
{
""Total"": 1004
}
},
{
""coinz"":
{
""Total"": 1004
}
}
]";
var wrapper = JsonConvert.DeserializeObject<CoinsWrapper[]>(json);
wrapper.Dump();
}
public class CoinsWrapper : LegacyAutoMap
{
[JsonProperty("c")]
[AlternateJSONName("coins")]
[AlternateJSONName("coinz")]
public Coins coins { get; set; }
}
public class Coins
{
public int Total { get; set; }
}
public abstract class LegacyAutoMap
{
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData;
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_additionalData == null) return;
var mappableProps = this.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(AlternateJSONNameAttribute)))
.Select(p =>
{
var attrs = p.GetCustomAttributes(typeof(AlternateJSONNameAttribute)).Cast<AlternateJSONNameAttribute>();
return attrs.Select(attr => new { AlternateName = attr.JSONKey.ToLowerInvariant(), Property = p });
})
.SelectMany(attrs => attrs);
foreach (var entry in _additionalData)
{
var prop = mappableProps.FirstOrDefault(p => p.AlternateName == entry.Key.ToLowerInvariant());
if (prop == null) continue;
JToken token = entry.Value;
MethodInfo ifn = typeof(JToken).GetMethod("ToObject", new Type[0]).MakeGenericMethod(new[] { prop.Property.PropertyType });
prop.Property.SetValue(this, ifn.Invoke(token, null));
}
_additionalData = null;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class AlternateJSONNameAttribute : Attribute
{
public string JSONKey { get; }
public AlternateJSONNameAttribute(string keyName)
{
this.JSONKey = keyName;
}
}
Upvotes: 3
Reputation: 22733
Here is a general purpose deserialization class which can be use for any object
public class JsonHelper
{
public static string JsonSerializer<T> (T t)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, t);
string jsonString = Encoding.UTF8.GetString(ms.ToArray());
ms.Close();
return jsonString;
}
public static T JsonDeserialize<T> (string jsonString)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj = (T)ser.ReadObject(ms);
return obj;
}
}
By using this, you can deserialize any data to any class,
Student student = JsonHelper.JsonDeserialize<Student>(jsonStudent);
For more detail with example see this blog
Upvotes: -1