Can Json.NET deserialize a flattened JSON string with dot notation?
I have a flattened JSON:
{
"CaseName" : "John Doe v. State",
"CaseDate" : "<some date>",
"开发者_JS百科Client.FirstName" : "John",
"Client.LastName" : "Doe",
"Client.Email" : "johndoe@gmail.com"
etc...
}
I want to deserialize it back to this entity:
public class Case()
{
public string CaseName { get; set; }
public string CaseDate { get; set; }
public Client Client { get; set; }
}
where Client.FirstName
, Client.LastName
, and Client.Email
are properties in the Client
object. Using Json.NET, is there any way to get it to parse the dot notation and deserialize this entity correctly? Currently, using the default settings, it tells me that Client.FirstName
is not a property in type Case
.
Yes, you can. You would derive a class from JsonConverter
and override the CanConvert
method to indicae that you can convert the Client
type.
Then, you would override the ReadJson
and WriteJson
methods to read and write the fields of the JSON literal.
For a JSON literal like this, it's more than likely you will need to create a JsonConverter
for the Case
type, as you will need to cache all the properties of the Client
object during serialization until you have enough information to actually create the Client
instance.
Then, you would call the Add
method on the JsonConverterCollection
instance exposed by the Converters
property on the JsonSerializer
instance you are using to perform your serialization/deserialization.
Note that if you need to do this for a number of different classes that might be represented in this manner, then you can write one JsonConverter
implementation, and have it scan for an attribute on the property. If the property has the attribute and exposes another object with properties, it would expect to read/write the dot-notation.
It should be noted that while you are using the dot-notation for the identifier, it's very uncommon to do so. If possible, on the side that is constructing the JSON literal, it should be doing it in this manner:
{
CaseName: "John Doe v. State",
CaseDate: "<some date>",
Client:
{
FirstName: "John",
LastName: "Doe",
Email: "johndoe@gmail.com"
}
}
But that's assuming that you have control over that end. If you don't, then there's not much you can do.
If you do have control, then constructing your JSON literals in that manner would negate the need for a custom JsonConverter
implementation.
simple code
public class SimpleDotJsonPropertyConverter : JsonConverter
{
public const string SplitChar = ".";
private readonly NamingStrategy _namingStrategy;
public SimpleDotJsonPropertyConverter() : this(new DefaultNamingStrategy())
{
}
public SimpleDotJsonPropertyConverter(Type namingStrategyType) : this(namingStrategyType.CreateInstance() as NamingStrategy)
{
}
public SimpleDotJsonPropertyConverter(NamingStrategy namingStrategy)
{
_namingStrategy = namingStrategy;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteRaw("{}");
return;
}
writer.WriteStartObject();
var type = value.GetType();
var props = type.GetPublicProperties();
foreach (var p in props)
{
if (p.PropertyType.IsValueType || p.PropertyType == typeof(string))
{
writer.WritePropertyName(_namingStrategy.GetPropertyName(p.Name, false));
writer.WriteValue(p.GetValue(value));
}
else
{
var parentName = p.Name;
var subProps = p.PropertyType.GetPublicProperties();
var memberValue = p.GetValue(value);
foreach (var sp in subProps)
{
writer.WritePropertyName(_namingStrategy.GetPropertyName(parentName, false) + SplitChar + _namingStrategy.GetPropertyName(sp.Name, false));
writer.WriteValue(sp.GetValue(memberValue));
}
}
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var jsonObject = JObject.Load(reader);
var result = objectType.CreateInstance();
foreach (var item in jsonObject)
{
if (item.Key.IsNullOrEmpty()) continue;
var nameArrays = item.Key.Split(SplitChar, StringSplitOptions.RemoveEmptyEntries);
if (nameArrays.Length <= 1)
{
var p = objectType.GetProperty(item.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (p != null)
{
if (item.Value == null)
p.SetValue(result, objectType.GetDefaultValue());
else
p.SetValue(result, item.Value.ToObject(p.PropertyType));
}
}
else
{
var name = nameArrays[0];
var subName = nameArrays[1];
var p = objectType.GetProperty(name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (p == null) continue;
var subProp = p.PropertyType.GetProperty(subName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (subProp == null) continue;
if (!subProp.PropertyType.IsValueType && subProp.PropertyType != typeof(string)) throw new NotSupportedException("Nested complex objects are not supported ");
var member = p.GetValue(result);
if (member == null)
{
member = p.PropertyType.CreateInstance();
p.SetValue(result, member);
}
if (item.Value == null)
subProp.SetValue(member, objectType.GetDefaultValue());
else
subProp.SetValue(member, item.Value.ToObject(subProp.PropertyType));
}
}
return result;
}
public override bool CanConvert(Type objectType) => objectType.IsClass;
}
Although only half the problem (i.e. not helping with the fact your object has been flattened)
You can deal with dot notation in a very quick and dirty way with a simple
MyTargetClass retVal
= JsonConvert.DeserializeObject<MyTargetClass>(jsonAsString.Replace(".", "_"));
In combo with appropriate "_" property names on the MyTargetClass e.g.
public class MyTargetClass
{
public string CaseName {get; set;}
public DateTime CaseDate {get; set;}
public string Client_FirstName {get; set;}
//Other Properties
}
精彩评论