Управление генерацией ссылочного идентификатора JSON. NET - PullRequest
0 голосов
/ 26 марта 2020

Я хотел бы иметь возможность контролировать, как JSON. NET генерирует свои мета-ссылочные идентификаторы, такие как "$id": "1". Возьмите следующий код:

public class Person
{
    public string Name { get; set; }

    public Person Mother { get; set; }
}

.

var settings = new Newtonsoft.Json.JsonSerializerSettings
{
    PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
    Formatting = Newtonsoft.Json.Formatting.Indented
};

Newtonsoft.Json.JsonConvert.DefaultSettings = () => settings;

var person = new Person
{
    Name = "bob",
    Mother = new Person { Name = "jane" }
};
var personJson = JsonConvert.SerializeObject(person);
var motherJson = JsonConvert.SerializeObject(person.Mother);

JSON для person выглядит так:

{
  "$id": "1",
  "Name": "bob",
  "Mother": {
    "$id": "2",
    "Name": "jane",
    "Mother": null
  }
}

Однако, если я сериализую person.Mother напрямую, JSON выглядит следующим образом:

{
  "$id": "1",
  "Name": "jane",
  "Mother": null
}

В первом JSON Джейн - "$id": "2", но сериализация Джейн напрямую - "$id": "1". Это поведение, которое я ожидал бы при нормальных условиях, так как сериализатор назначает идентификаторы в том порядке, в котором он пересекает объекты, но мне бы очень хотелось переопределить генерацию идентификаторов, чтобы я мог сделать его равным sh объекта ссылка сама. Таким образом, Джейн будет генерировать один и тот же идентификатор для каждого запущенного экземпляра программы каждый раз, независимо от того, сериализован ли он как член родительского или индивидуализирован.

ОБНОВЛЕНИЕ

В Пример кода в выбранном ответе и рекомендация в комментарии, я использовал IReferenceResolver. Оказывается, я не могу его использовать, но я все равно добавлю код ниже. Причина, по которой это не сработает, заключается в том, что я пытаюсь убить JSON. NET как инструмент быстрого и грязного клонирования, поэтому я не могу винить его за то, что он не соответствует моим потребностям. С тех пор я вернулся к своей собственной утилите клонирования, поэтому она мне больше не нужна.

public class ObjectReferenceResolver : Newtonsoft.Json.Serialization.IReferenceResolver
{
    readonly Dictionary<object, int> objectDic = new Dictionary<object, int>();
    int maxId = 0;

    //Called during serialization
    public string GetReference(object context, object value)
    {
        //This method will return the meta $id that you choose. In this example, I am storing
        //object references in a dictionary with an incremented ID. If the reference exists, I
        //return its ID. Otherwise, I increment the ID and add the reference to the dictionary.

        var id = 0;

        if (objectDic.ContainsKey(value))
        {
            id = objectDic[value];
        }
        else
        {
            objectDic[value] = maxId++;
        }

        return id.ToString();
    }

    //Called during serialization
    public bool IsReferenced(object context, object value)
    {
        //Return whether or not value exists in a reference bank.
        //If false, the JSON will return as a full JSON object with "$id": "x"
        //If true, the JSON will return "$ref": "x"
        return objectDic.ContainsKey(value);
    }

    //Called during deserialization
    public void AddReference(object context, string reference, object value)
    {
        //This method is called after the deserializer has created a new instance of the
        //object. At this time, it's only the initial instance and no properties have been set.
        //This method presents a problem because it does not allow you to create the instance or
        //retrieve it from a repo and then return it for later use by the reference resolver.
        //Therefore, I have to find the existing object by $id, remove it, and then add the new 
        //object created by the deseralizer. This creates the possibility for two instances of
        //the same data object to exist within the running application, so, unfortunately, this
        //will not work.

        var e = objectDic.First(x => x.Value.ToString() == reference).Key;

        objectDic.Remove(e);

        objectDic[value] = reference.ParseInt().Value;
    }

    //Called during deserialization
    public object ResolveReference(object context, string reference)
    {
        //This method retrieves an existing reference by $id and returns it.

        var value = objectDic.FirstOrDefault(x => x.Value.ToString() == reference).Key;

        return value;
    }
}

Ответы [ 2 ]

1 голос
/ 29 марта 2020

Как и другие рекомендовали, вам нужен пользовательский IReferenceResolver:

class PersonNameAsIdResolver : IReferenceResolver
{
    public void AddReference(object context, string reference, object value)
    {
        // This method is called during deserialize for $id
    }

    public string GetReference(object context, object value)
    {
        // Returns person name as value of $id
        return ((Person)value).Name;
    }

    public bool IsReferenced(object context, object value)
    {
        // Returns false, so that $id is used, not $ref.
        return false;
    }

    public object ResolveReference(object context, string reference)
    {
        // This method is called during deserialize for $ref
        return null;
    }
}

Чтобы использовать это:

var settings = new Newtonsoft.Json.JsonSerializerSettings
{
    PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
    Formatting = Newtonsoft.Json.Formatting.Indented
};

settings.ReferenceResolverProvider = ()=> new PersonNameAsIdResolver();

ОБНОВЛЕНИЕ

Ответ на обновление ОП

AddReference вызывается во время заполнения объекта, поэтому было слишком поздно для заменить объект. Чтобы иметь возможность найти и заполнить требуемый объект, вам нужен JsonConverter, который вызывается перед преобразователем ссылок:

class PersonJsonConverter : JsonConverter
{
    private readonly PersonNameAsIdResolver _idResolver;

    public PersonJsonConverter(PersonNameAsIdResolver idResolver)
    {
        _idResolver = idResolver;
    }

    public override bool CanConvert(Type objectType)
        => objectType == typeof(Person);

    // Can't write. There's nothing to changing for writing scenario.
    public override bool CanWrite => false;

    public override object ReadJson(JsonReader reader, Type objectType, 
        object existingValue, JsonSerializer serializer)
    {
        var token = JToken.ReadFrom(reader);
        if (token.Type == JTokenType.Null)
        {
            return null;
        }

        var obj = (JObject)token;

        // The code below calls the resolver to find the existing instance.
        // This can stop JSON.NET creating a new instance.
        Person instance = null;
        var @id = obj["$id"].Value<string>();
        if (@id != null)
        {
            instance = (Person)_idResolver.ResolveReference(this, @id);
        }
        else
        {
            var @ref = obj["$ref"]?.Value<string>();
            if (@ref != null)
            {
                instance = (Person)_idResolver.ResolveReference(this, @ref);
            }
        }

        // Assuming can't resolve, create a new instance.
        if (instance == null)
        {
            instance = new Person();
        }

        // This will populate existing Person object if found
        serializer.Populate(obj.CreateReader(), instance);

        return instance;
    }

    public override void WriteJson(JsonWriter writer, object value, 
        JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }
}

И параметры сериализации по умолчанию должны выглядеть следующим образом:

var settings = new Newtonsoft.Json.JsonSerializerSettings
{
    PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
    Formatting = Newtonsoft.Json.Formatting.Indented
};

var idResolver = new PersonNameAsIdResolver();
settings.Converters.Add(new PersonJsonConverter(idResolver));
settings.ReferenceResolverProvider = () => idResolver;
0 голосов
/ 28 марта 2020

Возможное решение будет следующим:

  1. Заменить PreserveReferncesHandling с объектов на None:
    PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None
Добавление свойства Id в классе Person:
    public class Person
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public Person Mother { get; set; }
    }

Полное решение выглядит следующим образом:

using System;
using Newtonsoft.Json;

namespace ControlJsonId
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("INICIO");
            var settings = new Newtonsoft.Json.JsonSerializerSettings
            {
                PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None,
                Formatting = Newtonsoft.Json.Formatting.Indented,
            };

            Newtonsoft.Json.JsonConvert.DefaultSettings = () => settings;

            var person = new Person
            {
                Id = Guid.NewGuid().ToString(),
                Name = "bob",
                Mother = new Person { Id = string.Empty, Name = "jane" }
            };

            var personJson = JsonConvert.SerializeObject(person);
            Console.WriteLine(personJson);

            var motherJson = JsonConvert.SerializeObject(person.Mother);
            Console.WriteLine(motherJson);

            Console.WriteLine("FIN");
            Console.ReadKey();
        }
    }

    public class Person
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public Person Mother { get; set; }
    }
}

Результат:

INICIO
{
  "Id": "76d6b5f0-2be8-4d1d-aafe-fe1b4b7d6ae1",
  "Name": "bob",
  "Mother": {
    "Id": "",
    "Name": "jane",
    "Mother": null
  }
}
{
  "Id": "",
  "Name": "jane",
  "Mother": null
}
FIN
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...