Есть некий проект, в котором пользователь выбирает модификации, по началу вроде все просто было, а именно создал класс, который имел Id
, Name
и другую нужную информацию.
public class Modification
{
public Modification(string name)
{
Id = Guid.NewGuid();
Name = name;
}
public Guid Id { get; set; }
public string Name { get; set; }
}
Проект развивался и стало необходимо следить, какая модификация необходима для другой (зависимости) и какая модификация конфликтует с другой (конфликты). Переделал я класс, добавил в него свойство Dependency
с типом List<Modification>
, в котором хотел хранить ссылки на все зависимости. Сериализую в JSON и получаю следующее:
[
{
"Id": "b8892913-a3be-4ec5-ac09-ee24fd6307b2",
"Name": "Mod1",
"Dependency": []
},
{
"Id": "05be1efd-1f66-49fa-8b7c-82704121ce42",
"Name": "Mod2",
"Dependency": [
{
"Id": "b8892913-a3be-4ec5-ac09-ee24fd6307b2",
"Name": "Mod1",
"Dependency": []
}
]
}
]
Вроде 2 объекта, у второго есть зависимость от первого, все бы хорошо. Десериализую обратно, меняю имя у первой модификации, но оно не меняется у той модификации, что указана в зависимостях. Тут я понимаю, что это совершенно два разных объекта, да и вид зависимостей мне явно не нравиться.
Сокращаю сам JSON, оставляю в зависимостях только ID:
[
{
"Id": "b8892913-a3be-4ec5-ac09-ee24fd6307b2",
"Name": "Mod1",
"Dependency": []
},
{
"Id": "05be1efd-1f66-49fa-8b7c-82704121ce42",
"Name": "Mod2",
"Dependency": [ "b8892913-a3be-4ec5-ac09-ee24fd6307b2" ]
}
]
В коде я пытаюсь сделать конвертер, но застреваю в самом начале, ибо я без понятия как мне найти через конвертер нужный объект, ибо на сколько мне известно, там все грузится поочередно и нужный объект попросту может быть еще не загружен, а значит мне его не от куда взять.
Единственный вариант, который я здесь вижу, это сделать public List<object> Dependency { get; set; }
, затем загрузить данные, где в JSON будут только строковые id
у зависимостей, а потом циклом искать нужные модификации, что то вроде этого:
var jsonData = JsonConvert.DeserializeObject<List<Modification>>(File.ReadAllText("Data.json"));
foreach (var item in jsonData)
{
for (int i = 0; i < item.Dependency.Count; i++)
{
var id = Guid.Parse($"{item.Dependency[i]}");
item.Dependency[i] = jsonData.FirstOrDefault(x => x.Id == id);
}
}
var first = jsonData.FirstOrDefault();
var last = jsonData.LastOrDefault();
Console.WriteLine(first.Name); //Mod1
Console.WriteLine(((Modification)last.Dependency[0]).Name); //Mod1
first.Name = "someName";
Console.WriteLine(first.Name); //someName
Console.WriteLine(((Modification)last.Dependency[0]).Name); //someName
Тогда да, получаем два объекта и у нужных ссылки на зависимости, но как по мне это костыли, да и все время тип гонять, ну такое....
В общем, прошу вашей помощи, как сделать задуманное, или я вовсе копаю не в ту сторону и такое делается по другому?
Перечитал ваш вопрос. Не очень мне ясна ваша проблема.
Допустим, у нас есть json
string json = @"[
{
""Id"": ""b8892913-a3be-4ec5-ac09-ee24fd6307b2"",
""Name"": ""Mod1"",
""Dependency"": []
},
{
""Id"": ""05be1efd-1f66-49fa-8b7c-82704121ce42"",
""Name"": ""Mod2"",
""Dependency"": [ ""b8892913-a3be-4ec5-ac09-ee24fd6307b2"" ]
}
]";
И есть класс
public class Modification
{
public string Id {get;set;}
public string Name{get;set;}
public HashSet<string> Dependency{get;set;} = new HashSet<string>();
}
Мы можем это прочитать и сборать из этого, например, словарь
var data = JsonConvert.DeserializeObject<Modification[]>(json);
var registry = data.ToDictionary(x=>x.Id);
Имея словарь, можно сделать многое. Например, проверить его на циклические зависимости
bool HasCycleDependency(Dictionary<string, Modification> registry){
foreach(var id in registry.Keys)
if (HasCycleDependency(registry, id, new HashSet<string>())) return true;
return false;
}
bool HasCycleDependency(Dictionary<string, Modification> registry, string current, HashSet<string> onTrack)
{
if (onTrack.Contains(current)) return true;
onTrack.Add(current);
foreach(var dep in registry[current].Dependency)
if (HasCycleDependency(registry, dep, onTrack)) return true;
onTrack.Remove(current);
return false;
}
Проверка
Console.WriteLine($"Has cycles: {HasCycleDependency(registry)}");
Вывод
Has cycles: False
Ну, или напечатать полные зависимости наших модификаций
void PrintInfo(Dictionary<string, Modification> registry){
foreach(var mod in registry.Values.OrderBy(m=>m.Name))
{
Console.WriteLine($"--------------------");
PrintInfo(registry, mod);
}
}
void PrintInfo(Dictionary<string, Modification> registry, Modification mod, string shift = "")
{
Console.WriteLine($"{shift}Mod id: {mod.Id}");
Console.WriteLine($"{shift}Mod name: {mod.Name}");
if (mod.Dependency.Count > 0) Console.WriteLine($"{shift}Dependencies:");
foreach(var d in mod.Dependency){
PrintInfo(registry, registry[d], shift + "\t");
}
}
Проверка
PrintInfo(registry);
Вывод
--------------------
Mod id: b8892913-a3be-4ec5-ac09-ee24fd6307b2
Mod name: Mod1
--------------------
Mod id: 05be1efd-1f66-49fa-8b7c-82704121ce42
Mod name: Mod2
Dependencies:
Mod id: b8892913-a3be-4ec5-ac09-ee24fd6307b2
Mod name: Mod1
Для соединения "ссылками" данные в JSON нам нужно будет сделать следующее:
Сделать интерфейс, по которому мы будем сверять Id объекта и находить необходимые объекты:
interface IJsonLinked
{
string Id { get; }
}
Некий класс, который мы зададим как StreamingContext
сериализатору/десериализатору.
В данном классе определим метод, который вернет объект по его Id, а также данный класс будет содержать в себе словарь всех ссылок:
class JsonLinkedContext
{
private readonly IDictionary<Type, IDictionary<string, object>> links = new Dictionary<Type, IDictionary<string, object>>();
public static object GetLinkedValue(JsonSerializer serializer, Type type, string reference)
{
var context = (JsonLinkedContext)serializer.Context.Context;
if (!context.links.TryGetValue(type, out IDictionary<string, object> links))
context.links[type] = links = new Dictionary<string, object>();
if (!links.TryGetValue(reference, out object value))
links[reference] = value = FormatterServices.GetUninitializedObject(type);
return value;
}
}
Теперь нам нужен конвертер для свойств, которые нужно "сопоставить" с их ссылками по Id:
Тут все довольно просто, если объект реализует IJsonLinked
, то при сериализации мы берем его Id, а при десериализации (при помощи ранее написанного метода) получаем основную ссылку на объект.
class JsonRefConverter : JsonConverter
{
public override bool CanConvert(Type type) => type.IsAssignableFrom(typeof(IJsonLinked));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteValue(((IJsonLinked)value).Id);
public override object ReadJson(JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String) throw new Exception("Ref value must be a string.");
return JsonLinkedContext.GetLinkedValue(serializer, type, reader.Value.ToString());
}
}
Ну и последнее, нам нужен конвертер, который сопоставит ссылку с нужным объектом:
class JsonLinkConverter : JsonConverter
{
public override bool CanConvert(Type type) => type.IsAssignableFrom(typeof(IJsonLinked));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => serializer.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
var value = JsonLinkedContext.GetLinkedValue(serializer, type, (string)jo.PropertyValues().First());
serializer.Populate(jo.CreateReader(), value);
return value;
}
}
Осталось теперь переделать немного класс, который будет работать с JSON, задать некие данные для теста и сериализировать/десериализировать данные:
Классы для сериализации/десериализации:
class Root
{
[JsonProperty(ItemConverterType = typeof(JsonLinkConverter))]
public List<Modification> Modifications { get; set; }
}
public class Modification : IJsonLinked
{
public Modification(string name)
{
Id = Guid.NewGuid().ToString("N");
Name = name;
}
public string Id { get; set; }
public string Name { get; set; }
[JsonProperty(ItemConverterType = typeof(JsonRefConverter))]
public List<Modification> Dependences { get; set; }
string IJsonLinked.Id => Id;
}
Тестовые данные:
var mod1 = new Modification("mod1");
var mod2 = new Modification("mod2")
{
Dependences = new List<Modification> { mod1 }
};
var mod3 = new Modification("mod3")
{
Dependences = new List<Modification> { mod1, mod2 }
};
var root = new Root() { Modifications = new List<Modification> { mod1, mod2, mod3 } };
Сериализация:
Тут ставим игнорирование NULL
значений.
var json = JsonConvert.SerializeObject(root, Formatting.Indented, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
Десериализация:
Задаем StreamingContext
.
var rootModifications = JsonConvert.DeserializeObject<Root>(json, new JsonSerializerSettings
{
Context = new StreamingContext(StreamingContextStates.All, new JsonLinkedContext()),
});
Результат:
{
"Modifications": [
{
"Id": "66fe811290df4105839a850153116250",
"Name": "mod1"
},
{
"Id": "1ba03f9480c94a86ba8f2d40e66e95cb",
"Name": "mod2",
"Dependences": [
"66fe811290df4105839a850153116250"
]
},
{
"Id": "b936eec3eadb4e38b7f19bd200c0e673",
"Name": "mod3",
"Dependences": [
"66fe811290df4105839a850153116250",
"1ba03f9480c94a86ba8f2d40e66e95cb"
]
}
]
}
Для того, чтобы убедиться, что это действительно ссылки сделал простенький вывод имени мода из зависимостей и оригинала, после изменил имя и опять вывел имена:
Console.WriteLine(rootModifications.Modifications[0].Name);
Console.WriteLine(rootModifications.Modifications[1].Dependences[0].Name);
rootModifications.Modifications[0].Name = "test";
Console.WriteLine(rootModifications.Modifications[0].Name);
Console.WriteLine(rootModifications.Modifications[1].Dependences[0].Name);
Вывод:
mod1
mod1
test
test
За основу взял ответ @Athari.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
В интернете много статей на тему интерфейсов, что это такое и как их реализовыватьНо я не нашел внятного ответа кто и зачем их придумал? Я только...
Вот разметка ComboBox:
Сравниваю 1 и 2 текстовый файл, если в 1 нету тех строк, которые во 2 текстовом файле, то он создает третий текстовый файл и записывает в негоКак...
У меня на сайте есть галерея картинокКартинки в довольно хорошем качестве, где-то 2000х1500