https://stackoverflow.com/questions/74951070/deserializing-json-mapping-single-property-with-no-direct-json-match

CSharp · Console

/Program.cs

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;
			}
		}
	}
;