你的情况和100号的情况基本相同. 你的根JSON对象有一组固定的属性,然后是0.. n个具有固定 struct 的动态名称的属性.因此,您可以使用来自this answer的转换器将JSON读入包含Dictionary<TKey, TValue> DynamicValues { get; init; }
属性的模型,以捕获动态命名的属性. 来自该答案的转换器需要稍微调整,以考虑到根模型具有泛型参数这一事实.
首先定义以下数据模型:
[JsonConverter(typeof(TypedExtensionDataConverter))]
public class Root<TKey, TValue> where TValue : DynamicKey where TKey : notnull
{
// The fixed properties from the JSON:
public string id { get; set; }
public Properties properties { get; set; }
// A dictionary to hold the properties with dynamic names but a fixed structure in the JSON:
[JsonTypedExtensionData]
public Dictionary<TKey, TValue> DynamicValues { get; init; } = new();
}
public class Properties // Properties was not shown in your question
{
public string createtime { get; set; }
}
public abstract class DynamicKey; // DynamicKey was not shown in your question
public class IdAndMetadata : DynamicKey
{
public string id { get; set; }
public Metadata metadata { get; set; }
}
public class Metadata
{
public string metadata0 { get; set; }
public string metadata1 { get; set; }
}
并介绍以下转换器:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public sealed class JsonTypedExtensionDataAttribute : Attribute;
public class TypedExtensionDataConverter : JsonConverter
{
// TypedExtensionDataConverter taken from this answer https://stackoverflow.com/a/40094403/3744182
// to https://stackoverflow.com/questions/40088941/how-to-deserialize-a-child-object-with-dynamic-numeric-key-names
// and adjusted so it can be applied to a class with a generic parameter.
public override bool CanConvert(Type objectType) => throw new NotImplementedException(); // CanConvert() is not called when the converter is applied via attributes
static JsonProperty GetExtensionJsonProperty(JsonObjectContract contract)
{
try
{
return contract.Properties.Where(p => p.PropertyName != null && p.AttributeProvider?.GetAttributes(typeof(JsonTypedExtensionDataAttribute), false).Any() == true).Single();
}
catch (InvalidOperationException ex)
{
throw new JsonSerializationException(string.Format("Exactly one property with JsonTypedExtensionDataAttribute is required for type {0}", contract.UnderlyingType), ex);
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (!(serializer.ContractResolver.ResolveContract(objectType) is JsonObjectContract contract))
throw new JsonSerializationException($"Contract for {objectType} is not a JsonObjectContract");
if (!(contract.DefaultCreator is {} creator))
throw new JsonSerializationException($"No default creator found for {objectType}");
var extensionJsonProperty = GetExtensionJsonProperty(contract);
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var extensionJProperty = (JProperty?)null;
for (int i = jObj.Count - 1; i >= 0; i--)
{
var property = (JProperty)jObj.AsList()[i];
if (contract.Properties.GetClosestMatchProperty(property.Name) == null)
{
if (extensionJProperty == null)
{
extensionJProperty = new JProperty(extensionJsonProperty.PropertyName!, new JObject());
jObj.Add(extensionJProperty);
}
((JObject)extensionJProperty.Value).Add(property.RemoveFromLowestPossibleParent());
}
}
var value = existingValue ?? creator();
using (var subReader = jObj.CreateReader())
serializer.Populate(subReader, value);
return value;
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
if (!(serializer.ContractResolver.ResolveContract(value.GetType()) is JsonObjectContract contract))
throw new JsonSerializationException($"Contract for {value.GetType()} is not a JsonObjectContract");
var extensionJsonProperty = GetExtensionJsonProperty(contract);
JObject jObj;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
jObj = JObject.FromObject(value!, serializer);
}
var extensionValue = (jObj[extensionJsonProperty.PropertyName!] as JObject)?.RemoveFromLowestPossibleParent();
if (extensionValue != null)
for (int i = extensionValue.Count - 1; i >= 0; i--)
{
var property = (JProperty)extensionValue.AsList()[i];
jObj.Add(property.RemoveFromLowestPossibleParent());
}
jObj.WriteTo(writer);
}
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite => !Disabled;
public override bool CanRead => !Disabled;
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
ArgumentNullException.ThrowIfNull(reader);
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
public static TJToken? RemoveFromLowestPossibleParent<TJToken>(this TJToken? node) where TJToken : JToken
{
if (node == null)
return null;
JToken toRemove;
var property = node.Parent as JProperty;
if (property != null)
{
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
toRemove = property;
property.Value = null!;
}
else
{
toRemove = node;
}
if (toRemove.Parent != null)
toRemove.Remove();
return node;
}
public static IList<JToken> AsList(this IList<JToken> container) => container;
}
internal struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose() => setValue?.Invoke(oldValue);
}
现在,您将能够成功地将问题中的JSON序列化和重新序列化:
var json =
"""
{
"id": "dataset0",
"properties": {
"createtime": "datetime format"
},
"dynamic0": {
"id": "123-456",
"metadata": {
"metadata0": "value0",
"metadata1": "value1"
}
},
"dynamic1": {
"id": "456-789",
"metadata": {
"metadata0": "value0"
}
}
}
""";
var root = JsonConvert.DeserializeObject<Root<string, IdAndMetadata>>(json);
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
};
var json2 = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
注:
here.第here章