TransferJsonConverter

in English

Библиотека предназначена для сериализации объектов в JSON и десериализации из JSON. Она включает в себя реализацию стандартного класса System.Text.Json.Serialization.JsonConverterFactory и несколько вспомогательных классов. В отличие от стандартной сериализации/десериализации данная библиотека обладает следующими особенностями.

При сериализации

Описанные возможности могут быть использованы следующим образом. На уровне хранения создаются объекты, содержащие кроме прочего некоторые присущие этому уровню свойства, которые не должны быть доступны на уровне модели и на клиенте, например, первичные ключи. Ни модель, ни клиент не должны знать, как устроена база данных. Однако, при этом они должны уметь однозначно ссылаться на объект.

Пример:

Имеется следующий набор типов:

    public interface ICatForListing
    {
        string Name { get; }
    }

    public enum Latitude { Center, Left, Right }

    public enum Longitude { Center, Front, Rear }

    public interface IPaw
    {
        Latitude latitude { get; }
        Longitude longitude { get; }
    }

    public interface ICat : ICatForListing
    {
        IList Paws { get; }
    }

    public class StringIntId
    {
        public string StringId { get; set; }
        public int IntId { get; set; }
    }

    public class Paw : IPaw
    {
        [Key]
        public StringIntId Id { get; set; }
        [Key]
        public StringIntId CatId { get; set; }
        public Latitude latitude { get; set; }
        public Longitude longitude { get; set; }
    }

    public class Cat : ICat
    {
        [Key]
        public StringIntId Id { get; set; }
        public IList Paws { get; set; }

        public string Name { get; set; }

    }

И список:

            List cats = new()
            {
                new Cat
                {
                    Id = new StringIntId { StringId = "Havanas", IntId = 1 },
                    Name = "Murzik",
                    Paws = new List()
                    {
                        new Paw
                        {
                            Id = new StringIntId { StringId = "LeftPaws", IntId = 1 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Left,
                            longitude = Longitude.Front
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "LeftPaws", IntId = 2 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Left,
                            longitude = Longitude.Rear
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "RightPaws", IntId = 1 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Right,
                            longitude = Longitude.Front
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "RightPaws", IntId = 2 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Right,
                            longitude = Longitude.Rear
                        },
                    }
                },
                new Cat
                {
                    Id = new StringIntId { StringId = "Havanas", IntId = 2 },
                    Name = "Barsik",
                    Paws = new List()
                    {
                        new Paw
                        {
                            Id = new StringIntId { StringId = "LeftPaws", IntId = 3 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Left,
                            longitude = Longitude.Front
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "LeftPaws", IntId = 4 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Left,
                            longitude = Longitude.Rear
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "RightPaws", IntId = 3 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Right,
                            longitude = Longitude.Front
                        },
                        new Paw
                        {
                            Id = new StringIntId { StringId = "RightPaws", IntId = 4 },
                            CatId = new StringIntId { StringId = "Havanas", IntId = 1 },
                            latitude = Latitude.Right,
                            longitude = Longitude.Rear
                        },
                    }
                },
            };

        

Отправим список на клиента, передавая только свойства из интерфейса ICatForListing.

        TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null)
            .AddTransient<ICatForListing, Cat>()
            ;
        JsonSerializerOptions options = new();
        options.Converters.Add(serializer);
        context.WriteAsJsonAsync<List<ICat>>(cats, options);

Получим JSON: [{"Id":{"StringId":"Havanas","IntId":1},"Name":"Murzik"},{"Id":{"StringId":"Havanas","IntId":2},"Name":"Barsik"}].

Отправим отдельного кота на клиента, передавая свойства из интерфейса ICat.

        TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null)
            .AddTransient<ICat, Cat>()
            .AddTransient<IPaw, Paw>()
            ;
        JsonSerializerOptions options = new();
        options.Converters.Add(serializer);
        context.WriteAsJsonAsync<ICat>(cats[0], options);

Получим JSON:

{"Id":{"StringId":"Havanas","IntId":1},"Name":"Murzik",
"Paws":[{"Id":{"StringId":"LeftPaws","IntId":1},"CatId":{StringId":"Havanas","IntId":1},"Latitude":"Left","Longitude":"Front"},
{"Id":{"StringId":"LeftPaws","IntId":2},"CatId":{"StringId":"Havanas","IntId":1},"Latitude":"Left","Longitude":"Rear"},
{"Id":{"StringId":"RightPaws","IntId":3},"CatId":{"StringId":"Havanas","IntId":1},"Latitude":"Right","Longitude":"Front"},
{"Id":{"StringId":"RightPaws","IntId":4},"CatId":{"StringId":"Havanas","IntId":1},"Latitude":"Right","Longitude":"Rear"}]}
.

При десериализации

Пример:

Получим на клиенте JSON списка из предыдущего примера. Загрузим его в существующую ObservableCollection<ICatForListing> cats;

            TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null)
                .AddTransient<ICatForListing, Cat>()
                ;
            JsonSerializerOptions options = new();
            options.Converters.Add(serializer);
            serializer.Target = cats;
            JsonSerializer.Deserialize<RewritableListStub<ICatForListing>>(jsonString, options);
        

Здесь мы перезаписали содержимое коллекции, применив заглушку RewritableListStub<ICat>. Мы могли бы также добавить новые объекты в коллекцию применив заглушку AppendableListStub<ICat>. Это может потребоваться, если большое количество данных загружаются несколькими порциями:

            ...
            JsonSerializer.Deserialize<AppendableListStub<ICat>>(jsonString, options);
        

После этого мы будем иметь в коллекции ObservableCollection<ICatForListing> cats; элементы типа ICatForListing, и клиент ничего не узнает о том, как устроена база данных.

Получим на клиенте JSON одного кота из предыдущего примера. Пусть у нас есть свойство:

        public ICat SelectedCat 
        {
            get
            {
                return _selectedCat;
            }
            set
            {
                _selectedCat = value;
                OnPropertyChanged();
            }
        }
        

Мы можем создать новый объект и присвоить его этому свойству:

            TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null)
                .AddTransient<ICat, Cat>()
                .AddTransient<IPaw, Paw>()
                ;
            JsonSerializerOptions options = new();
            options.Converters.Add(serializer);
            SelectedCat = JsonSerializer.Deserialize<ICat>(jsonString, options);
        

А можем использовать старый объект и загрузить новые значения свойств в него. В этом случае нужно вручную известить UI об изменении свойства:

            TransferJsonConverterFactory serializer = new TransferJsonConverterFactory(null)
                .AddTransient<ICat, Cat>()
                .AddTransient<IPaw, Paw>()
                ;
            JsonSerializerOptions options = new();
            options.Converters.Add(serializer);
            serializer.Target = SelectedCat;
            JsonSerializer.Deserialize<ICat>(jsonString, options);
            OnPropertyChanged(nameof(SelectedCat));
        

И в том и в другом случае мы имеем ссылку на объект типа ICat, который ничего не знает про устройство базы данных.

В случае запроса к серверу сериализация происходит так же, как описано в разделе При сериализации. Если требуется получить данные об одном коте или удалить его, сериализуем в краткую форму ICatForListing, если требуется применить изменения - сериализуем в полную форму ICat. При этом JSON будет передан с ключами.