Alternate Property Name While Deserializing

Alternate property name while deserializing

One way to accomplish this is to create a custom JsonConverter. The idea is to have the converter enumerate the JSON property names for objects we are interested in, strip the non-alphanumeric characters from the names and then try to match them up with the actual object properties via reflection. Here is how it might look in code:

public class LaxPropertyNameMatchingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsClass;
}

public override bool CanWrite
{
get { return false; }
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
PropertyInfo[] props = objectType.GetProperties();

JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
string name = Regex.Replace(jp.Name, "[^A-Za-z0-9]+", "");

PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && string.Equals(pi.Name, name, StringComparison.OrdinalIgnoreCase));

if (prop != null)
prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}

return instance;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

To use the custom converter with a particular class, you can decorate that class with a [JsonConverter] attribute like this:

[JsonConverter(typeof(LaxPropertyNameMatchingConverter))]
public class MyClass
{
public string MyProperty { get; set; }
public string MyOtherProperty { get; set; }
}

Here is a simple demo of the converter in action:

class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""my property"" : ""foo"",
""my-other-property"" : ""bar"",
},
{
""(myProperty)"" : ""baz"",
""myOtherProperty"" : ""quux""
},
{
""MyProperty"" : ""fizz"",
""MY_OTHER_PROPERTY"" : ""bang""
}
]";

List<MyClass> list = JsonConvert.DeserializeObject<List<MyClass>>(json);

foreach (MyClass mc in list)
{
Console.WriteLine(mc.MyProperty);
Console.WriteLine(mc.MyOtherProperty);
}
}
}

Output:

foo
bar
baz
quux
fizz
bang

While this solution should do the job in most cases, there is an even simpler solution if you are OK with the idea of changing the Json.Net source code directly. It turns out you can accomplish the same thing by adding just one line of code to the Newtonsoft.Json.Serialization.JsonPropertyCollection class. In this class, there is a method called GetClosestMatchProperty() which looks like this:

public JsonProperty GetClosestMatchProperty(string propertyName)
{
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);

return property;
}

At the point where this method is called by the deserializer, the JsonPropertyCollection contains all the properties from the class being deserialized, and the propertyName parameter contains the name of the JSON property name being matched. As you can see, the method first tries an exact name match, then it tries a case-insensitive match. So we already have a many-to-one mapping being done between the JSON and class property names.

If you modify this method to strip out all non-alphanumeric characters from the property name prior to matching it, then you can get the behavior you desire, without any special converters or attributes needed. Here is the modified code:

public JsonProperty GetClosestMatchProperty(string propertyName)
{
propertyName = Regex.Replace(propertyName, "[^A-Za-z0-9]+", "");
JsonProperty property = GetProperty(propertyName, StringComparison.Ordinal);
if (property == null)
property = GetProperty(propertyName, StringComparison.OrdinalIgnoreCase);

return property;
}

Of course, modifying the source code has its problems as well, but I figured it was worth a mention.

.NET NewtonSoft JSON deserialize map to a different property name

Json.NET - Newtonsoft has a JsonPropertyAttribute which allows you to specify the name of a JSON property, so your code should be:

public class TeamScore
{
[JsonProperty("eighty_min_score")]
public string EightyMinScore { get; set; }
[JsonProperty("home_or_away")]
public string HomeOrAway { get; set; }
[JsonProperty("score ")]
public string Score { get; set; }
[JsonProperty("team_id")]
public string TeamId { get; set; }
}

public class Team
{
public string v1 { get; set; }
[JsonProperty("attributes")]
public TeamScore TeamScores { get; set; }
}

public class RootObject
{
public List<Team> Team { get; set; }
}

Documentation: Serialization Attributes

Use different name for serializing and deserializing with Json.Net

You can make use of the JsonSerializerSettings, the ContractResolver and the NamingStrategy.

public class ErrorDetails
{
public int Id { get; set; }
public string ErrorMessage { get; set; }
}

var json = "{'Id': 1,'error_message': 'An error has occurred!'}";

For dezerialization you could use the SnakeCaseNamingStrategy.

var dezerializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
};
var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);

To serialize the object again you dont have to change the JsonSerializerSettings as the default will use the property name.

var jsonNew = JsonConvert.SerializeObject(obj);

jsonNew = "{'Id': 1,'ErrorMessage': 'An error has occurred!'}"


Or you could create a contract resolver which can decide which name to use. Then you can decide when you dezerialize and serialize if you want to use the pascal case name format or the one with the underscore.

public class CustomContractResolver : DefaultContractResolver
{
public bool UseJsonPropertyName { get; }

public CustomContractResolver(bool useJsonPropertyName)
{
UseJsonPropertyName = useJsonPropertyName;
}

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (!UseJsonPropertyName)
property.PropertyName = property.UnderlyingName;

return property;
}
}

public class ErrorDetails
{
public int Id { get; set; }
[JsonProperty("error_message")]
public string ErrorMessage { get; set; }
}

var json = "{'Id': 1,'error_message': 'An error has occurred!'}";
var serializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CustomContractResolver(false)
};
var dezerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver(true)
};

var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);
var jsonNew = JsonConvert.SerializeObject(obj, serializerSettings);

jsonNew = "{'Id': 1,'ErrorMessage': 'An error has occurred!'}"

Different names of JSON property during serialization and deserialization

Just tested and this works:

public class Coordinates {
byte red;

@JsonProperty("r")
public byte getR() {
return red;
}

@JsonProperty("red")
public void setRed(byte red) {
this.red = red;
}
}

The idea is that method names should be different, so jackson parses it as different fields, not as one field.

Here is test code:

Coordinates c = new Coordinates();
c.setRed((byte) 5);

ObjectMapper mapper = new ObjectMapper();
System.out.println("Serialization: " + mapper.writeValueAsString(c));

Coordinates r = mapper.readValue("{\"red\":25}",Coordinates.class);
System.out.println("Deserialization: " + r.getR());

Result:

Serialization: {"r":5}
Deserialization: 25

multiple JsonProperty Name assigned to single property

A simple solution which does not require a converter: just add a second, private property to your class, mark it with [JsonProperty("name2")], and have it set the first property:

public class Specifications
{
[JsonProperty("name1")]
public string CodeModel { get; set; }

[JsonProperty("name2")]
private string CodeModel2 { set { CodeModel = value; } }
}

Fiddle: https://dotnetfiddle.net/z3KJj5

How can I change property names when serializing with Json.net?

You could decorate the property you wish controlling its name with the [JsonProperty] attribute which allows you to specify a different name:

using Newtonsoft.Json;
// ...

[JsonProperty(PropertyName = "FooBar")]
public string Foo { get; set; }

Documentation: Serialization Attributes

Deserialize JSON into a Objects by property name aswell as JsonProperty

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;
}
}


Related Topics



Leave a reply



Submit