Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternate property name while deserializing

Tags:

In reference to this question:

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

Sure, great, but can I have the cake and eat it?

What I'm looking for is an eye pleasing way have an alternate name for a property in such a way that the string may contain either.

Something like:

[BetterJsonProperty(PropertyName = "foo_bar")] public string FooBar { get; set; } 

Both

{      "FooBar": "yup" } 

and

{            "foo_bar":"uhuh" } 

would deserialize as expected.

As solution with no attribute would work or an attribute on the class like:

 [AllowCStylePropertyNameAlternatives] 
like image 732
Martin Avatar asked Nov 05 '13 15:11

Martin


People also ask

What is property name in JSON?

Objects are the mapping type in JSON. They map “keys” to “values”. In JSON, the “keys” must always be strings. Each of these pairs is conventionally referred to as a “property”.

What is JSON property C#?

JsonPropertyAttribute indicates that a property should be serialized when member serialization is set to opt-in. It includes non-public properties in serialization and deserialization. It can be used to customize type name, reference, null, and default value handling for the property value.

What is the difference between serialize and deserialize JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object). If you serialize this result it will generate a text with the structure and the record returned.

What is Jsonconvert DeserializeObject?

DeserializeObject<T>(String,JsonConverter[]) Deserializes the JSON to the specified . NET type using a collection of JsonConverter. DeserializeObject(String, JsonSerializerSettings) Deserializes the JSON to a .


2 Answers

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.

like image 96
Brian Rogers Avatar answered Sep 30 '22 13:09

Brian Rogers


Another way of accomplishing this is intercepting the serialization/deserialization process early, by doing some overrides the JsonReader and JsonWriter

public class CustomJsonWriter : JsonTextWriter {     private readonly Dictionary<string, string> _backwardMappings;      public CustomJsonWriter(TextWriter writer, Dictionary<string, string> backwardMappings)         : base(writer)     {         _backwardMappings = backwardMappings;     }      public override void WritePropertyName(string name)     {         base.WritePropertyName(_backwardMappings[name]);     } }  public class CustomJsonReader : JsonTextReader {     private readonly Dictionary<string, string> _forwardMappings;       public CustomJsonReader(TextReader reader, Dictionary<string, string> forwardMappings )         : base(reader)     {         _forwardMappings = forwardMappings;     }      public override object Value     {         get         {             if (TokenType != JsonToken.PropertyName)                 return base.Value;              return _forwardMappings[base.Value.ToString()];         }     } } 

After doing this, you can serialize by doing

var mappings = new Dictionary<string, string> {     {"Property1", "Equivalent1"},     {"Property2", "Equivalent2"}, }; var builder = new StringBuilder(); JsonSerializer.Create().Serialize(new CustomJsonWriter(new StringWriter(builder), mappings), your_object); 

and deserialize by doing

var mappings = new Dictionary<string, string> {     {"Equivalent1", "Property1"},     {"Equivalent2", "Property2"}, }; var txtReader = new CustomJsonReader(new StringReader(jsonString), mappings); var your_object = JsonSerializer.Create().Deserialize<Your_Type>(txtReader); 
like image 32
Adrian Petrescu Avatar answered Sep 30 '22 13:09

Adrian Petrescu