使用 Newtonsoft.JSON 自定义转换器读取具有不同输入的 json
Using Newtonsoft.JSON custom converters to read json with different input
我正在使用 NewtonSoft.Json 来 read/write 我们的数据 json。一个(非常简化的)例子是:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"AirportName": "KLAX",
"AirportRunway": "25L"
}
使用模仿属性的 C# DTO class。请注意,我们使用 TypeNameHandling
.
我们想将 C# class 更改为更复杂的设置:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class LandingEvent
{
public DateTime TimeOfLanding { get; set; }
public Airport Airport { get; set; }
}
这将导致新数据将写入 JSON 为:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"Airport": {
"Name": "KLAX",
"Runway": "25L"
}
}
但我们仍然需要能够读取旧的 JSON 数据并解析为新的 class 结构。 这就是我目前的工作奋斗.
我知道要走的路可能是专门的 JsonConverter。在这方面我有几个问题:
- 如何读取
$type
属性 并实例化正确的类型? (我的重写 CanConvert()
方法被提供了一个 base-class 的名称(由于实际上下文比这个例子更复杂)。
- 如果 属性
AirportName
存在,我只想进行自定义读取。如果不是这种情况,我该如何回退到默认反序列化?
编辑:需要进行一些说明。如果我创建自定义 JsonConverter
,那么 CanConvert
将接收类型 EventBase
,但 $type
实际上可以包含 "MyNamespace.LandingEvent, MyAssembly"
或 "MyNamespace.TakeoffEvent, MyAssembly"
。因此,我可能需要根据这个值自己实例化返回的对象。不过,我不确定如何。
类 变化,这种 Json 字符串变化,将在未来的版本中获得额外的功能。你会不断调整你的声明。使用 Newtonsoft,您可以为不同的 class 继承添加自定义处理程序并继续使用反序列化,但您必须维护该代码。
对于动态 Json,我发现使用 JObject、JArray 和 JToken 更容易自由地解析 Json 字符串。特别是如果您只对某些领域感兴趣。
我只能给你举个例子,我觉得这和你的项目有(一点点)关系,但不是同一个部分(笑脸)
我使用以下代码解码 Blender 生成的 MSFS 转换格式的 glTF 3d 对象文件的一部分。这种 Json-like 格式由 部分 组成。每个 Json 部分看起来像这样,
"asset" : {
"extensions" : {
"ASOBO_normal_map_convention" : {
"tangent_space_convention" : "DirectX"
}
},
"generator" : "Extended Khronos glTF Blender I/O v1.0.0",
"version" : "2.0"
},
.. 但这些部分及其字段大多是可选的,并且在某些 GLtf 中它们未填写。它不是 classes 的“可序列化或反序列化”。
我声明一些
public JObject AssetObject;
..从Json字符串sJson中填充如下:
dynamic stuff = JObject.Parse(sJson);
var pp = stuff.Children();
Dictionary<string, bool> d = new Dictionary<string, bool>();
foreach (JProperty jo in pp) d[jo.Name] = true; // all sections
string cSection= "asset";
if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
else
{
AssetObject = (JObject)stuff[cSection];
ParseGLBAsset();
}
首先注意动态声明的使用,一个部分将通过强制转换落入JObject。我将该部分的各个部分存储到字符串属性中。解析本身发生在 ParseGLBAsset() 中,此函数如下所示:
public void ParseGLBAsset()
{
foreach (JProperty jbp in AssetObject.Children())
if (jbp.Name == "generator")
{ GLBGenerator = jbp.Value.ToString(); }
else
if (jbp.Name == "extensions")
{
GLBAssetExtensions = jbp.Value.ToString();
LogLine(0, "Asset extensions: " + GLBAssetExtensions);
}
else
if (jbp.Name == "version")
{ GLBVersion = jbp.Value.ToString(); }
LogLine(1, "Found asset.generator=" + GLBGenerator);
LogLine(1, "Found asset.version=" + GLBVersion);
}
您可以使用自定义 JsonConverter
来处理多态事件类型和变化的 JSON 格式。下面是一个例子。它通过将数据加载到 JObject
来工作,它可以在其中读取 $type
属性 并实例化正确的事件类型。从那里,它将尝试从 JSON 填充事件对象。如果 Airport
反序列化失败,它将尝试读取遗留机场属性并从中填充一个新的 Airport
实例。
class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseEvent).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string type = (string)obj["$type"];
BaseEvent baseEvent;
if (type.Contains(nameof(TakeoffEvent)))
{
baseEvent = new TakeoffEvent();
}
else
{
baseEvent = new LandingEvent();
}
serializer.Populate(obj.CreateReader(), baseEvent);
if (baseEvent.Airport == null)
{
baseEvent.Airport = new Airport
{
Name = (string)obj["AirportName"],
Runway = (string)obj["AirportRunway"]
};
}
return baseEvent;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
注意:这假设您的 class 结构实际如下所示:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class BaseEvent
{
public Airport Airport { get; set; }
}
class TakeoffEvent : BaseEvent
{
public DateTime TimeOfTakeoff { get; set; }
}
class LandingEvent : BaseEvent
{
public DateTime TimeOfLanding { get; set; }
}
要使用转换器,请将其添加到 JsonSerializerSettings
中的 Converters
集合,然后将设置传递给 DeserializeObject()
:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = new List<JsonConverter> { new EventConverter() }
};
var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);
这是一个工作演示:https://dotnetfiddle.net/jSaq4T
另请参阅:Adding backward compatibility support for an older JSON structure
我正在使用 NewtonSoft.Json 来 read/write 我们的数据 json。一个(非常简化的)例子是:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"AirportName": "KLAX",
"AirportRunway": "25L"
}
使用模仿属性的 C# DTO class。请注意,我们使用 TypeNameHandling
.
我们想将 C# class 更改为更复杂的设置:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class LandingEvent
{
public DateTime TimeOfLanding { get; set; }
public Airport Airport { get; set; }
}
这将导致新数据将写入 JSON 为:
{
"$type": "MyNamespace.LandingEvent, MyAssembly",
"TimeOfLanding": "2021-04-11T15:00:00.000Z",
"Airport": {
"Name": "KLAX",
"Runway": "25L"
}
}
但我们仍然需要能够读取旧的 JSON 数据并解析为新的 class 结构。 这就是我目前的工作奋斗.
我知道要走的路可能是专门的 JsonConverter。在这方面我有几个问题:
- 如何读取
$type
属性 并实例化正确的类型? (我的重写CanConvert()
方法被提供了一个 base-class 的名称(由于实际上下文比这个例子更复杂)。 - 如果 属性
AirportName
存在,我只想进行自定义读取。如果不是这种情况,我该如何回退到默认反序列化?
编辑:需要进行一些说明。如果我创建自定义 JsonConverter
,那么 CanConvert
将接收类型 EventBase
,但 $type
实际上可以包含 "MyNamespace.LandingEvent, MyAssembly"
或 "MyNamespace.TakeoffEvent, MyAssembly"
。因此,我可能需要根据这个值自己实例化返回的对象。不过,我不确定如何。
类 变化,这种 Json 字符串变化,将在未来的版本中获得额外的功能。你会不断调整你的声明。使用 Newtonsoft,您可以为不同的 class 继承添加自定义处理程序并继续使用反序列化,但您必须维护该代码。
对于动态 Json,我发现使用 JObject、JArray 和 JToken 更容易自由地解析 Json 字符串。特别是如果您只对某些领域感兴趣。
我只能给你举个例子,我觉得这和你的项目有(一点点)关系,但不是同一个部分(笑脸)
我使用以下代码解码 Blender 生成的 MSFS 转换格式的 glTF 3d 对象文件的一部分。这种 Json-like 格式由 部分 组成。每个 Json 部分看起来像这样,
"asset" : {
"extensions" : {
"ASOBO_normal_map_convention" : {
"tangent_space_convention" : "DirectX"
}
},
"generator" : "Extended Khronos glTF Blender I/O v1.0.0",
"version" : "2.0"
},
.. 但这些部分及其字段大多是可选的,并且在某些 GLtf 中它们未填写。它不是 classes 的“可序列化或反序列化”。
我声明一些
public JObject AssetObject;
..从Json字符串sJson中填充如下:
dynamic stuff = JObject.Parse(sJson);
var pp = stuff.Children();
Dictionary<string, bool> d = new Dictionary<string, bool>();
foreach (JProperty jo in pp) d[jo.Name] = true; // all sections
string cSection= "asset";
if (!d.ContainsKey(cSection)) { LogLine(98, "Warning: BPG Json has no " + cSection + " section."); return false; }
else
{
AssetObject = (JObject)stuff[cSection];
ParseGLBAsset();
}
首先注意动态声明的使用,一个部分将通过强制转换落入JObject。我将该部分的各个部分存储到字符串属性中。解析本身发生在 ParseGLBAsset() 中,此函数如下所示:
public void ParseGLBAsset()
{
foreach (JProperty jbp in AssetObject.Children())
if (jbp.Name == "generator")
{ GLBGenerator = jbp.Value.ToString(); }
else
if (jbp.Name == "extensions")
{
GLBAssetExtensions = jbp.Value.ToString();
LogLine(0, "Asset extensions: " + GLBAssetExtensions);
}
else
if (jbp.Name == "version")
{ GLBVersion = jbp.Value.ToString(); }
LogLine(1, "Found asset.generator=" + GLBGenerator);
LogLine(1, "Found asset.version=" + GLBVersion);
}
您可以使用自定义 JsonConverter
来处理多态事件类型和变化的 JSON 格式。下面是一个例子。它通过将数据加载到 JObject
来工作,它可以在其中读取 $type
属性 并实例化正确的事件类型。从那里,它将尝试从 JSON 填充事件对象。如果 Airport
反序列化失败,它将尝试读取遗留机场属性并从中填充一个新的 Airport
实例。
class EventConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseEvent).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
string type = (string)obj["$type"];
BaseEvent baseEvent;
if (type.Contains(nameof(TakeoffEvent)))
{
baseEvent = new TakeoffEvent();
}
else
{
baseEvent = new LandingEvent();
}
serializer.Populate(obj.CreateReader(), baseEvent);
if (baseEvent.Airport == null)
{
baseEvent.Airport = new Airport
{
Name = (string)obj["AirportName"],
Runway = (string)obj["AirportRunway"]
};
}
return baseEvent;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
注意:这假设您的 class 结构实际如下所示:
class Airport
{
public string Name { get; set; }
public string Runway { get; set; }
}
class BaseEvent
{
public Airport Airport { get; set; }
}
class TakeoffEvent : BaseEvent
{
public DateTime TimeOfTakeoff { get; set; }
}
class LandingEvent : BaseEvent
{
public DateTime TimeOfLanding { get; set; }
}
要使用转换器,请将其添加到 JsonSerializerSettings
中的 Converters
集合,然后将设置传递给 DeserializeObject()
:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
Converters = new List<JsonConverter> { new EventConverter() }
};
var baseEvent = JsonConvert.DeserializeObject<BaseEvent>(json, settings);
这是一个工作演示:https://dotnetfiddle.net/jSaq4T
另请参阅:Adding backward compatibility support for an older JSON structure