我定义了两个JsonConverter
类。一个我附在课堂上,另一个我附在那个班级的财产上。如果我只将转换器附加到属性,它可以正常工作。只要我将一个单独的转换器附加到该类,它就会忽略附加到该属性的转换器。 如何让它不跳过这样的JsonConverterAttributes?
这是类级转换器(我改编自:Alternate property name while deserializing)。我将它附加到测试类,如下:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
然后这是FuzzyMatchingJsonConverter
本身:
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Optimizer.models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Permit the property names in the Json to be deserialized to have spelling variations and not exactly match the
/// property name in the object. Thus puntuation, capitalization and whitespace differences can be ignored.
///
/// NOTE: As implemented, this can only deserialize objects from a string, not serialize from objects to a string.
/// </summary>
/// <seealso cref="https://stackoverflow.com/questions/19792274/alternate-property-name-while-deserializing"/>
public class FuzzyMatchingJsonConverter<T> : JsonConverter
{
/// <summary>
/// Map the json property names to the object properties.
/// </summary>
private static DictionaryToObjectMapper<T> Mapper { get; set; } = null;
private static object SyncToken { get; set; } = new object();
static void InitMapper(IEnumerable<string> jsonPropertyNames)
{
if (Mapper == null)
lock(SyncToken)
{
if (Mapper == null)
{
Mapper = new DictionaryToObjectMapper<T>(
jsonPropertyNames,
EnumHelper.StandardAbbreviations,
ModelBase.ACCEPTABLE_RELATIVE_EDIT_DISTANCE,
ModelBase.ABBREVIATION_SCORE
);
}
}
else
{
lock(SyncToken)
{
// Incremental mapping of additional attributes not seen the first time for the second and subsequent objects.
// (Some records may have more attributes than others.)
foreach (var jsonPropertyName in jsonPropertyNames)
{
if (!Mapper.CanMatchKeyToProperty(jsonPropertyName))
throw new MatchingAttributeNotFoundException(jsonPropertyName, typeof(T).Name);
}
}
}
}
public override bool CanConvert(Type objectType) => objectType.IsClass;
/// <summary>
/// If false, this class cannot serialize (write) objects.
/// </summary>
public override bool CanWrite { get => false; }
/// <summary>
/// Call the default constructor for the object and then set all its properties,
/// matching the json property names to the object attribute names.
/// </summary>
/// <param name="reader"></param>
/// <param name="objectType">This should match the type parameter T.</param>
/// <param name="existingValue"></param>
/// <param name="serializer"></param>
/// <returns>The deserialized object of type T.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Note: This assumes that there is a default (parameter-less) constructor and not a constructor tagged with the JsonCOnstructorAttribute.
// It would be better if it supported those cases.
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
JObject jo = JObject.Load(reader);
InitMapper(jo.Properties().Select(jp => jp.Name));
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
不要陷入DictionaryToObjectMapper
(它是专有的,但使用模糊匹配逻辑来处理拼写变化)。这是下一个JsonConverter
,它会将"Y"
,"Yes"
,"T"
,"True"
等更改为布尔值。我从这个来源改编了它:https://gist.github.com/randyburden/5924981
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Optimizer.Serialization
{
/// <summary>
/// Handles converting JSON string values into a C# boolean data type.
/// </summary>
/// <see cref="https://gist.github.com/randyburden/5924981"/>
public class BooleanJsonConverter : JsonConverter
{
private static readonly string[] Truthy = new[] { "t", "true", "y", "yes", "1" };
private static readonly string[] Falsey = new[] { "f", "false", "n", "no", "0" };
/// <summary>
/// Parse a Boolean from a string where alternative spellings are permitted, such as 1, t, T, true or True for true.
///
/// All values that are not true are considered false, so no parse error will occur.
/// </summary>
public static Func<object, bool> ParseBoolean
= (obj) => { var b = (obj ?? "").ToString().ToLower().Trim(); return Truthy.Any(t => t.Equals(b)); };
public static bool ParseBooleanWithValidation(object obj)
{
var b = (obj ?? "").ToString().ToLower().Trim();
if (Truthy.Any(t => t.Equals(b)))
return true;
if (Falsey.Any(t => t.Equals(b)))
return false;
throw new ArgumentException($"Unable to convert ${obj}into a Boolean attribute.");
}
#region Overrides of JsonConverter
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
// Handle only boolean types.
return objectType == typeof(bool);
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> ParseBooleanWithValidation(reader.Value);
/// <summary>
/// Specifies that this converter will not participate in writing results.
/// </summary>
public override bool CanWrite { get { return false; } }
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//TODO: Implement for serialization
//throw new NotImplementedException("Serialization of Boolean");
// I have no idea if this is correct:
var b = (bool)value;
JToken valueToken;
valueToken = JToken.FromObject(b);
valueToken.WriteTo(writer);
}
#endregion Overrides of JsonConverter
}
}
以下是我创建单元测试中使用的测试类的方法:
[JsonConverter(typeof(FuzzyMatchingJsonConverter<JsonTestData>))]
public class JsonTestData: IEquatable<JsonTestData>
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
[JsonConstructor]
public JsonTestData()
{
TestId = null;
MinimumDistance = double.NaN;
TaxIncluded = false;
IsMetsFan = false;
}
public JsonTestData(string testId, double minimumDistance, bool taxIncluded, bool isMetsFan)
{
TestId = testId;
MinimumDistance = minimumDistance;
TaxIncluded = taxIncluded;
IsMetsFan = isMetsFan;
}
public override bool Equals(object obj) => Equals(obj as JsonTestData);
public bool Equals(JsonTestData other)
{
if (other == null) return false;
return ((TestId ?? "") == other.TestId)
&& (MinimumDistance == other.MinimumDistance)
&& (TaxIncluded == other.TaxIncluded)
&& (IsMetsFan == other.IsMetsFan);
}
public override string ToString() => $"TestId: {TestId}, MinimumDistance: {MinimumDistance}, TaxIncluded: {TaxIncluded}, IsMetsFan: {IsMetsFan}";
public override int GetHashCode()
{
return -1448189120 + EqualityComparer<string>.Default.GetHashCode(TestId);
}
}
答案 0 :(得分:1)
应用于您的属性的[JsonConverter(typeof(BooleanJsonConverter))]
不起作用的原因是您为包含类型提供了JsonConverter
,并且没有为其中的成员调用应用的转换器ReadJson()
方法。
当转换器不应用于某个类型时,在(反)序列化之前,Json.NET使用反射为类型创建JsonContract
,该类型指定如何映射类型和JSON。对于具有属性的对象,生成JsonObjectContract
,其中包括构造和填充类型的方法,并列出要序列化的类型的所有成员,包括其名称和任何应用的转换器。构建合同后,方法JsonSerializerInternalReader.PopulateObject()
使用它来实际反序列化对象。
当转换器 应用于某个类型时,将跳过上述所有逻辑。相反,JsonConverter.ReadJson()
必须执行所有操作,包括反序列化和设置所有成员值。如果这些成员碰巧应用了转换器,ReadJson()
将需要注意这一事实并手动调用转换器。这就是你的转换器需要在这里做的事情:
foreach (JProperty jp in jo.Properties())
{
var prop = Mapper.KeyToProperty[jp.Name];
// Check for and use [JsonConverter(typeof(...))] if applied to the member.
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
那么,怎么做?一种方法是使用c#反射工具来检查适当的属性。幸运的是,Json.NET已经为您构建了JsonObjectContract
;您只需致电:{/ p>即可在ReadJson()
内访问该帐户
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
完成后,您可以使用已构建的合同来指导反序列化。
由于您没有提供FuzzyMatchingJsonConverter
的实际示例,因此我创建了类似的内容,以便将蛇案例和pascal案例属性反序列化为具有驼峰案例命名的对象:
public abstract class FuzzyMatchingJsonConverterBase : JsonConverter
{
protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException(string.Format("Contract for type {0} is not a JsonObjectContract", objectType));
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
existingValue = existingValue ?? contract.DefaultCreator();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var propertyName = (string)reader.Value;
reader.ReadAndAssert();
var jsonProperty = FindProperty(contract, propertyName);
if (jsonProperty == null)
continue;
object itemValue;
if (jsonProperty.Converter != null && jsonProperty.Converter.CanRead)
{
itemValue = jsonProperty.Converter.ReadJson(reader, jsonProperty.PropertyType, jsonProperty.ValueProvider.GetValue(existingValue), serializer);
}
else
{
itemValue = serializer.Deserialize(reader, jsonProperty.PropertyType);
}
jsonProperty.ValueProvider.SetValue(existingValue, itemValue);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
throw new JsonReaderException("Unexpected EOF!");
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public abstract class FuzzySnakeCaseMatchingJsonConverterBase : FuzzyMatchingJsonConverterBase
{
protected override JsonProperty FindProperty(JsonObjectContract contract, string propertyName)
{
// Remove snake-case underscore.
propertyName = propertyName.Replace("_", "");
// And do a case-insensitive match.
return contract.Properties.GetClosestMatchProperty(propertyName);
}
}
// This type should be applied via attributes.
public class FuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
// This type can be used in JsonSerializerSettings.Converters
public class GlobalFuzzySnakeCaseMatchingJsonConverter : FuzzySnakeCaseMatchingJsonConverterBase
{
readonly IContractResolver contractResolver;
public GlobalFuzzySnakeCaseMatchingJsonConverter(IContractResolver contractResolver)
{
this.contractResolver = contractResolver;
}
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string))
return false;
var contract = contractResolver.ResolveContract(objectType);
return contract is JsonObjectContract;
}
}
public static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected EOF!");
}
}
然后你会按如下方式应用它:
[JsonConverter(typeof(FuzzySnakeCaseMatchingJsonConverter))]
public class JsonTestData
{
public string TestId { get; set; }
public double MinimumDistance { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool TaxIncluded { get; set; }
[JsonConverter(typeof(BooleanJsonConverter))]
public bool IsMetsFan { get; set; }
}
注意:
我避免将JSON预加载到中间JToken
层次结构中,因为没有必要这样做。
您没有提供自己的转换器的工作示例,因此我无法在此答案中为您修复此问题,但您希望将其从FuzzyMatchingJsonConverterBase
继承并且然后写下你protected abstract JsonProperty FindProperty(JsonObjectContract contract, string propertyName);
的版本。
您可能还需要检查并使用JsonProperty
的其他属性,例如JsonProperty.ItemConverter
,JsonProperty.Ignored
,JsonProperty.ShouldDeserialize
等。但如果你这样做,你最终可能会重复JsonSerializerInternalReader.PopulateObject()
的整个逻辑。
应在null
的开头附近检查ReadJson()
JSON值。
示例工作.Net fiddle,表明可以成功反序列化以下JSON,从而调用类型和成员转换器:
{
"test_id": "hello",
"minimum_distance": 10101.1,
"tax_included": "yes",
"is_mets_fan": "no"
}