Вам понадобится какой-то механизм, который разрешает эти ссылки.
Вот два подхода:
1. Использование встроенной обработки ссылок
Одним из таких механизмов является свойство PreserveReferencesHandling
сериализатора Newtonsoft, которое выполняет именно то, что вы описываете, за исключением того, что вместо $id
и $ref
вместо id
и ref
.
Чтобы использовать это, вы можете преобразовать дерево JSON до его преобразования в типизированные объекты, сначала прочитав его в виде дерева JSON (используя JToken.Parse
), затем, пройдя по этому дереву, заменив свойства id
и ref
на $id
и $ref
(поскольку промежуточное дерево JSON модифицируемо и динамически c по своей природе, вы можете сделать это легко).
Затем вы можете преобразовать это преобразованное дерево в ваши типизированные объекты, используя встроенный механизм разрешения ссылок, используя JObject.CreateReader
, чтобы получить JsonReader
для преобразованного дерева, которое вы может дать JsonSerializer.Deserialize<T>
, чтобы дать ему команду десериализовать его в нужный тип.
T DeserializeJsonWithReferences<T>(string input)
{
var jsonTree = JToken.Parse(jsonString);
TransformJsonTree(jsonTree); // renames `id` and `ref` properties in-place
var jsonReader = jsonTree.CreateReader();
var jsonSerializer = new JsonSerializer() {
PreserveReferencesHandling = PreserveReferenceHandling.All
};
var deserialized = jsonSerializer.Deserialize<T>(jsonReader);
return deserialized;
}
void TransformJsonTree(JToken token)
{
var container = token as JContainer;
if (container == null)
return;
foreach (propName in SpecialPropertyNames) // {"id", "ref"}
{
objects = container
.Descendants()
.OfType<JObject>()
.Where(x => x.ContainsKey(propName));
foreach (obj in objects)
{
obj["$" + propName] = obj[propName];
obj.Remove(propName);
}
}
}
2. Прокрутка собственного слоя с эталонным разрешением
Более сложный подход, если вы хотите сделать это самостоятельно: вам нужно добавить свой собственный слой с эталонным разрешением, который преобразует дерево JSON до его преобразования в типизированный объекты.
Здесь вы можете начать с чтения потока JSON в виде дерева JSON. Затем вам нужно дважды пройти по этому дереву:
При первом обходе вы будете искать объекты со свойством id
и записывать их в словарь (из *) 1045 * к объекту, содержащему его).
При втором обходе вы будете искать объекты со свойством ref
и заменять эти ref-объекты соответствующим значением путем ищем указанный объект по его id
в словаре, который вы создали ранее, затем перемещаетесь по его свойствам в соответствии с цепочкой свойств, описанной в значении ref
. Например, если ref равен 3.address.city
, вы будете искать объект с идентификатором 3, затем найдете значение его свойства address
, а затем значение свойства city
этого значения, и это будет быть окончательным значением ссылки.
После преобразования дерева JSON и замены всех ссылочных объектов их соответствующими ссылочными значениями вы можете преобразовать дерево JSON в типизированный объект.
В отношении кода, который будет точно таким же, как и в предыдущем примере, за исключением transformJsonTree
, вместо простого переименования свойств id
и ref
, вам придется реализовать фактический поиск и logi эталонного разрешения c.
Это может выглядеть примерно так:
IDictionary<string, JToken> BuildIdMap(JContainer container)
{
return container
.Descendants()
.OfType<JObject>()
.Where(obj => obj.ContainsKey(IdPropertyName)
.ToDictionary(obj => obj[IdPropertyName], obj => obj);
}
JToken LookupReferenceValue(string referenceString, IDictionary<string, JObject> idToObjectMap)
{
var elements = referenceString.Split('.');
var obj = idToObjectMap(elements[0]);
for (int i = 1; i < elements.Length; i++)
{
var elem = elements[i];
switch (obj)
{
case JArray jarr:
obj = arr[elem]; // elem is a property name
break;
case JObject jobj:
obj = jobj[int.Parse(elem)]; // elem is an array index
break;
default:
throw Exception("You should throw a meaningful exception here");
}
}
}
void ResolveReferences(JContainer container, IDictionary<string, JObject> idToObjectMap)
{
refObjects = container
.Descendants()
.OfType<JObject>()
.Where(obj.Count == 1 && obj => obj.ContainsKey(RefPropertyName))
foreach (var refObject in refObjects)
{
referenceString = refObject[RefPropertyName];
referencedValue = LookupReferenceValue(refObject, idToObjectMap)
refObject.Replace(referencedValue);
}
}
РЕДАКТИРОВАТЬ: Также взгляните на JToken.SelectToken
, который позволяет перемещаться цепочка свойств из строки или JsonPath
, что избавляет от многих проблем из вышеперечисленного (при условии, что ссылочный синтаксис в вашем документе соответствует поддерживаемому Newtonsoft, например, в отношении индексов массива).
JToken LookupReferenceValue(string referenceString, IDictionary<string, JObject> idToObjectMap)
{
var parts = referenceString.Split('.', 1); // only split on first '.'
var id = parts[0];
var tokenPath = parts[1];
var referencedObject = idToObjectMap[id];
var referencedValue = referencedObject.SelectToken(tokenPath);
return referencedValue;
}
Прошло много лет с тех пор, как я написал C#, поэтому прошу прощения за любые синтаксические ошибки или не-идиоматическое использование c. Но это общая идея.