Your situation is very similar to the one from Serialize list of objects with string properties to json array of objects: your JSON contains arrays of single-property objects, and you would like to map those array to POCOs with multiple properties corresponding to the singleton properties in the corresponding array of objects. Your situation differs only in that every type of object in your JSON is represented as an array of objects, rather than just one type.
Thus you can generalize ObjectAsObjectArrayConverter<TObject>
from this answer to handle all c# types that will be serialized as JSON objects, then use it to deserialize your JSON.
First, define the following custom JsonConverter
:
public class ObjectAsObjectArrayConverter : JsonConverter
{
// Generalized from this answer https://stackoverflow.com/a/72354204/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/72352865/serialize-list-of-objects-with-string-properties-to-json-array-of-objects
static IContractResolver DefaultResolver { get; } = JsonSerializer.Create().ContractResolver;
readonly IContractResolver resolver;
public ObjectAsObjectArrayConverter() : this(DefaultResolver) { }
public ObjectAsObjectArrayConverter(IContractResolver resolver) => this.resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
public override bool CanConvert(Type objectType)
{
if (objectType.IsPrimitive || objectType == typeof(string))
return false;
if (!(resolver.ResolveContract(objectType) is JsonObjectContract contract))
return false;
if (contract.DefaultCreator == null)
return false;
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract) ?? throw new ArgumentException("Wrong contract type");
writer.WriteStartArray();
foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
{
var propertyValue = property.ValueProvider.GetValue(value);
if (propertyValue == null && (serializer.NullValueHandling == NullValueHandling.Ignore || property.NullValueHandling == NullValueHandling.Ignore))
continue;
writer.WriteStartObject();
writer.WritePropertyName(property.PropertyName);
if (propertyValue == null)
writer.WriteNull();
else if (property.Converter != null && property.Converter.CanWrite)
property.Converter.WriteJson(writer, propertyValue, serializer);
else
serializer.Serialize(writer, propertyValue);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
protected virtual bool ShouldSerialize(JsonProperty property, object value) =>
property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (existingValue == null)
existingValue = serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
switch (reader.MoveToContentAndAssert().TokenType)
{
case JsonToken.Null:
return (object)null;
case JsonToken.StartArray:
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
{
switch (reader.TokenType)
{
case JsonToken.StartObject:
serializer.Populate(reader, existingValue);
break;
default:
throw new JsonSerializationException("Unexpected token type " + reader.TokenType.ToString());
}
}
break;
case JsonToken.StartObject:
serializer.Populate(reader, existingValue);
break;
default:
throw new JsonSerializationException("Unexpected token type " + reader.TokenType.ToString());
}
return existingValue;
}
}
public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
reader.ReadAndAssert().MoveToContentAndAssert();
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
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)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
Next, modify your CrimeResultModel
as follows, leaving criminal_record_list
, query
and identity
unchanged:
public class CrimeResultModel
{
[JsonProperty("criminal_record_list")]
public List<criminal_record_list> criminal_record_list{ get; set; }
[JsonProperty("query")]
public query query{ get; set; }
[JsonProperty("identity")]
public identity identity { get; set; }
public long CriminalRecordType { get; set; }
}
And now you will be able to do:
var inputSettings = new JsonSerializerSettings
{
Converters = { new ObjectAsObjectArrayConverter() },
};
var result = JsonConvert.DeserializeObject<CrimeResultModel>(jsonString, inputSettings);
Demo fiddle here.