I am trying to deserialize an existing JSON structure to into an object composed of a set of models. The naming in these models are not consistent and I was specifically asked to not change them (renaming, adding attributes, etc).
So, given this Json text (just a small sample):
{
"parameter": {
"alarms": [
{
"id": 1,
"name": "alarm1",
"type": 5,
"min": 0,
"max": 2
}],
"setting-active": true,
"setting-oneRun": true
}
}
would need to be mapped into these models:
public class Alarm
{
public int AlarmId { get; set; }
public string AlarmName { get; set; }
public AlarmType RbcType { get; set; }
public int MinimumTolerated { get; set; }
public int MaximumTolerated { get; set; }
}
public class Setting
{
public bool Active { get; set; }
public bool OneRun { get; set; }
}
public class Parameter
{
public List<Alarm> Alarms { get; set; }
public Setting ParameterSetting { get; set; }
}
So far, im writing a class that extends DefaultContractResolver and overrides maps property names.
MyCustomResolver so far:
public class MyCustomResolver : DefaultContractResolver
{
private Dictionary<string, string>? _propertyMappings;
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
//ModelMappings is a static class that will return a dictionary with mappings per ObjType being deserialized
_propertyMappings = ModelMappings.GetMapping(type);
return base.CreateProperties(type, memberSerialization);
}
protected override string ResolvePropertyName(string propertyName)
{
if (_propertyMappings != null)
{
_propertyMappings.TryGetValue(propertyName, out string? resolvedName);
return resolvedName ?? base.ResolvePropertyName(propertyName);
}
return base.ResolvePropertyName(propertyName);
}
}
Code that Im using to deserialize:
var settings = new JsonSerializerSettings();
settings.DateFormatString = "YYYY-MM-DD";
settings.ContractResolver = new MyCustomResolver();
Parameter p = JsonConvert.DeserializeObject<Parameter>(jsonString, settings);
So I reached a point I need to somehow map the properties in Parameter to values located in the prev json node ("setting-active", "setting-oneRun"). I need to tell the deserializer where these values are. Can this be done using an extension of DefaultContractResolver ?
I appreciate any tips pointing in the right direction
CodePudding user response:
I think that the best way to "KEEP IT SIMPLE", you need to define an object that has exactly the properties of the json. Then you can use a library like "Automapper" to define rules of mapping between the "json object" and the "business object".
CodePudding user response:
You can apply ModelMappings.GetMapping(objectType)
in DefaultContractResolver.CreateObjectContract()
:
public class MyCustomResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
var overrides = ModelMappings.GetMapping(objectType);
if (overrides != null)
{
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.UnderlyingName != null && overrides.TryGetValue(property.UnderlyingName, out var name))
property.PropertyName = name;
}
}
return contract;
}
}
Notes:
By applying the mappings in
CreateObjectContract()
you can remap both property names and creator parameter names.Since the contract resolver is designed to resolve contracts for all types, storing a single
private Dictionary<string, string>? _propertyMappings;
doesn't really make sense.Unlike your previous question, your current question shows properties from a nested c# object
ParameterSetting
getting percolated up to the parent objectParameter
. Since a custom contract resolver is designed to generate the contract for a single type, it isn't suited to restructuring data between types. Instead, consider using a DTO or converter DTO in such situations:public class ParameterConverter : JsonConverter<Parameter> { record ParameterDTO(List<Alarm> alarms, [property: JsonProperty("setting-active")] bool? Active, [property: JsonProperty("setting-oneRun")] bool? OneRun); public override void WriteJson(JsonWriter writer, Parameter? value, JsonSerializer serializer) { var dto = new ParameterDTO(value!.Alarms, value.ParameterSetting?.Active, value.ParameterSetting?.OneRun); serializer.Serialize(writer, dto); } public override Parameter? ReadJson(JsonReader reader, Type objectType, Parameter? existingValue, bool hasExistingValue, JsonSerializer serializer) { var dto = serializer.Deserialize<ParameterDTO>(reader); if (dto == null) return null; existingValue ??= new (); existingValue.Alarms = dto.alarms; if (dto.Active != null || dto.OneRun != null) existingValue.ParameterSetting = new () { Active = dto.Active.GetValueOrDefault(), OneRun = dto.OneRun.GetValueOrDefault() }; return existingValue; } }
If your "real" model is too complex to define a DTO, you could create a
JsonConverter<Paramater>
that (de)serializes the JSON into an intermediateJToken
hierarchy, then restructures that. See e.g. this answer to Can I serialize nested properties to my class in one operation with Json.net?.In some cases, the custom naming of your properties is just camel casing. To camel case property names without the need for explicit overrides, set
MyCustomResolver.NamingStrategy
toCamelCaseNamingStrategy
e.g. as follows:var settings = new JsonSerializerSettings { DateFormatString = "YYYY-MM-DD", // Use CamelCaseNamingStrategy since many properties in the JSON are just camel-cased. ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() }, Converters = { new ParameterConverter() }, };
Demo fiddle here.