Измените имя свойства на имя производного класса, с которым оно было создано при сериализации с Json. net - PullRequest
2 голосов
/ 29 апреля 2020

Я пытаюсь сериализовать (и десериализовать) мои объекты в. NET (стандарт), используя последнюю версию NewtonSoft. JSON.

Вот мои классы:

public class BaseRequest
{
    public string UserName{ get; set; }
}

Запрос, производный от BaseRequest (у меня есть много других таких классов, производных от BaseRequest):

public class GetDeviceRequest : BaseRequest
{
    public string SerialNumber { get; set; }
}

Запрос, который я сериализую в Json, создается с помощью класса :

public class CommunicationRequest
{
    public CommunicationRequest() { }

    public CommunicationRequest(BaseRequest baseRequest, string version ="1.1")
    {
        this.Version = version;
        this.Request = baseRequest;
    }

    public string Version { get; set; } = "1.1";
    public BaseRequest Request { get; set; }
}

Код, который я использую для сериализации. Я еще не тестировал десериализацию, но она должна работать так же

GetDeviceRequest getDeviceRequest = new GetDeviceRequest()
{
    SerialNumber = "123456789",
    UserName = "john.doe"
};

CommunicationRequest request = new CommunicationRequest(getDeviceRequest);

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver()
};

var json = JsonConvert.SerializeObject(request, settings);

Что я получаю как JSON:

{
   "version":"1.1",
   "request":{      
         "serialNumber":"123456789",
         "username":"john.doe"
   }
}

Что я хочу:

{
   "version":"1.1",
   "getDeviceRequest":{      
         "serialNumber":"123456789",
         "username":"john.doe"
   }
}

Мне нужно динамически изменить JsonProperty имя Request на GetDeviceRequest в зависимости от имени производного класса.

Что я мог бы сделать:

[JsonProperty("DerievedClassName")]
public BaseRequest Request { get; set; }

Это явно не работает для меня, потому что объект не был создан, и я не могу заставить тип использовать что-то вроде Request.GetType().

Казалось бы, способ состоит в том, чтобы реализовать мой ICustomContractResolver, но я не могу получить рабочий CustomContractResolver для моего варианта использования.

Есть ли способ, которым я могу сериализовать, как я хочу?

1 Ответ

1 голос
/ 29 апреля 2020

Вам понадобится пользовательский JsonConverter для вашего CommunicationRequest класса, чтобы обрабатывать имя свойства dynamici c, а также чтобы иметь возможность преобразовать его обратно в правильный тип при десериализации (при условии, что вы хотите go round поездка с этим). Что-то вроде следующего должно соответствовать вашим потребностям, но может потребоваться некоторая настройка.

public class CommunicationRequestConverter : JsonConverter
{
    NamingStrategy NamingStrategy { get; set; }

    public CommunicationRequestConverter(NamingStrategy namingStrategy)
    {
        NamingStrategy = namingStrategy;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(CommunicationRequest);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        CommunicationRequest req = (CommunicationRequest)value;
        JObject jo = new JObject(
            new JProperty(GetPropertyName("Version"), req.Version),
            new JProperty(GetPropertyName(req.Request.GetType().Name), 
                          JObject.FromObject(req.Request, serializer))
        );
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);

        string versionPropertyName = GetPropertyName("Version");
        JProperty requestProperty = jo.Properties().FirstOrDefault(p => p.Name != versionPropertyName);

        Type baseRequestType = Assembly.GetAssembly(typeof(BaseRequest)).GetTypes()
            .Where(t => t.IsClass && GetPropertyName(t.Name) == requestProperty.Name)
            .First();

        CommunicationRequest req = new CommunicationRequest
        {
            Version = (string)jo[versionPropertyName],
            Request = (BaseRequest)requestProperty.Value.ToObject(baseRequestType, serializer)
        };

        return req;
    }

    private string GetPropertyName(string name)
    {
        return NamingStrategy.GetPropertyName(name, false);
    }
}

Настройте параметры сериализатора следующим образом:

var ns = new CamelCaseNamingStrategy();
var settings = new JsonSerializerSettings { 
    ContractResolver = new DefaultContractResolver { NamingStrategy = ns },
    Converters = new List<JsonConverter> { new CommunicationRequestConverter(ns) },
    Formatting = Formatting.Indented
};

Вот демонстрация в оба конца: https://dotnetfiddle.net/kufBae

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...