CSharp · Console
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters;
using System.IO;
using System.Data;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using NUnit.Framework; // Throws NUnit.Framework.AssertionException
//https://stackoverflow.com/questions/74951070/deserializing-json-mapping-single-property-with-no-direct-json-match
//
#nullable enable
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;
}
}
public static class ModelMappings
{
private static Dictionary<Type, Dictionary<string, string>> memberNameToJsonNameMap = new ()
{
[typeof(Alarm)] = new() {
{nameof(Alarm.AlarmId), "id"},
{nameof(Alarm.AlarmName), "name"},
{nameof(Alarm.RbcType ), "type"},
{nameof(Alarm.MinimumTolerated ), "min"},
{nameof(Alarm.MaximumTolerated ), "max"},
},
[typeof(Setting)] = new() {
{nameof(Setting.Active), "setting-active"},
{nameof(Setting.OneRun), "setting-oneRun"},
},
[typeof(Parameter)] = new() {
{nameof(Parameter.Alarms), "alarms"},
{nameof(Parameter.ParameterSetting), "setting"},
},
};
public static Dictionary<string, string>? GetMapping(Type type) => memberNameToJsonNameMap.TryGetValue(type, out var d) ? d : null;
}
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; }
}
public enum AlarmType
{
None,
Loud,
Five = 5,
}
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;
}
}
public class RootModel
{
public Parameter Parameter { get; set; }
}
class TestClass
{
public static void Test()
{
TestRoundTrip();
TestSerialize();
}
static void TestRoundTrip()
{
var json =
"""
{
"parameter": {
"alarms": [
{
"id": 1,
"name": "alarm1",
"type": 5,
"min": 0,
"max": 2
}],
"setting-active": true,
"setting-oneRun": true
}
}
""";
var settings = new JsonSerializerSettings
{
DateFormatString = "YYYY-MM-DD",
// Use CamelCaseNamingStrategy since most properties in the JSON are just camel-cased.
ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() },
Converters = { new ParameterConverter() },
};
var root = JsonConvert.DeserializeObject<RootModel>(json, settings);
var json2 = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
Console.WriteLine("Result of round-tripping {0}:", root);
Console.WriteLine(json2);
}
static void TestSerialize()
{
var parameter = new Parameter
{
Alarms = new ()
{
new() { AlarmId = 1, AlarmName = "alarm1", RbcType = AlarmType.Five, MinimumTolerated = 0, MaximumTolerated = 2 },
},
ParameterSetting = new() { Active = true, OneRun = true },
};
var settings = new JsonSerializerSettings
{
DateFormatString = "YYYY-MM-DD",
// Use CamelCaseNamingStrategy since most properties in the JSON are just camel-cased.
ContractResolver = new MyCustomResolver { NamingStrategy = new CamelCaseNamingStrategy() },
Converters = { new ParameterConverter() },
};
var json = JsonConvert.SerializeObject(parameter, Formatting.Indented, settings);
Console.WriteLine("Result of serializing a {0}:", parameter);
Console.WriteLine(json);
}
}
public class Program
{
public static void Main()
{
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Namespace, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine();
try
{
TestClass.Test();
}
catch (Exception ex)
{
Console.WriteLine("Failed with unhandled exception: ");
Console.WriteLine(ex);
throw;
}
}
}