Сериализация модели Linq2sql выборочно в JSON

#linq-to-sql #c#-4.0 #extension-methods #json.net

#linq-to-sql #c #-4.0 #методы расширения #json.net

Вопрос:

У меня довольно распространенная бизнес-модель linq2sql из базы данных mssql. Между таблицами есть некоторые ассоциации, что хорошо. Вся модель находится в отдельной сборке. Я использую JSON.NET библиотека для сериализации.

Теперь мне нужно сериализовать эти модели в JSON и указать, какие свойства использовать и какие сейчас. Использование атрибутов if невозможно, но мне также не нравится идея класса метаданных.

Итак, я думал об использовании метода расширения таким образом:

 public static class User {
  public static object GetSerializable(this DataModel.User user) {
    return new {
      user.Id, user.LoginName, user.FirstName, user.LastName
    }
  }
}
  

Это было бы неплохо, однако я не уверен, как использовать его в подобных случаях:

 [JsonObject]
public class AuthModel {
  [JsonProperty]
  public DataModel.User { get; set; }
}
  

Есть ли у вас какие-либо идеи, как эффективно использовать эти методы расширений там? Или какие-то другие совершенно другие идеи?

Ответ №1:

Вы можете использовать Fluent Json для преобразования их в json. Эта настройка может быть выполнена в коде без использования атрибутов.

Вариант 2: вы можете использовать пользовательский сериализатор

Вариант 3: вы можете использовать KeyValuePairConverter . Преобразуйте свой постоянный класс в словарь и используйте это.

Комментарии:

1. Спасибо! Fluent Json выглядит интересно, я подумаю о том, чтобы использовать его вместо json.net . Пользовательский конвертер может быть вариантом, но я оставляю его в качестве последнего средства. Однако KeyValuePair бесполезен, или я не знаю, как именно вы хотите его использовать.

2. Хорошо … Беглый Json хорош, но он слишком явный. Мне нравится это для JSON.NET , что он способен сериализовать простой объект без какой-либо настройки по умолчанию. Пользовательский конвертер довольно ограничен для этой цели, нет ссылки на само свойство, поэтому я не могу прочитать дополнительный атрибут с информацией о сериализации.

3. Да, Json. NET работает плавно и не требует настройки. Но в вашем случае вам нужна конфигурация, и вы не можете использовать атрибуты. Если вы определенно хотите использовать Json. NET создайте POCO со всеми атрибутами Json, такими как JsonIgnore и т. Д., И сопоставьте свой постоянный класс с этим POCO. Таким образом, ваш mapper и POCO дадут вам точный контроль над тем, что становится json.

4. Да, мне нужна конфигурация, но только для нескольких случаев. Большую часть времени речь идет только об отфильтровывании нежелательных свойств из этих фиксированных классов. Мне не нужен много мелкозернистый подход. Я не знаю точно, что вы подразумеваете под POCO, но я не хочу заканчивать созданием некоторых «теневых» классов с одинаковыми свойствами, которые говорят мне, что действительно сериализовать. В случае некоторых изменений компилятор будет молчать.

5. POCO = обычный старый объект CLR. Хорошо, что вы выбрали пользовательский конвертер — это был вариант 2 — и лучше, чем создавать теневые классы, как вы сказали.

Ответ №2:

Хорошо, я выбрал подход, основанный на пользовательском JsonConverter, который в результате очень похож на описанный выше подход. Это выглядит так:

 public abstract class SerializeSelectorConverter<TModel> : JsonConverter where TModel: class
{
    protected abstract object GetSerializableObject( TModel model );

    public override bool CanWrite { get { return true; } }

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, GetSerializableObject( value as TModel ));
    }

    public override bool CanRead { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
  

Затем я просто создаю класс, подобный:

 [JsonObject]
public class AuthModel {
    [JsonProperty]
    [JsonConverter(typeof(UserConverter))]
    public DataModel.User { get; set; }
}

private class UserConverter : SerializeSelectorConverter<DataModel.User>
{
    protected override object GetSerializableObject(DataModel.User model)
    {
        return new
        {
            model.Id,
            model.LoginName,
            model.FirstName,
            model.LastName
        };
    }
}
  

Достаточно просто, без какой-либо сложной конфигурации или классов метаданных. Все правильно проверяется компилятором, поэтому никаких опечаток и проблем при внесении изменений.

Ответ №3:

Я написал пользовательский JsonConverter для обработки такого рода случаев в общем случае, учитывая, что исходный класс имеет интерфейс, перечисляющий то, что необходимо сериализовать.

Ваш класс плюс интерфейс сериализации:

 public interface IUser {
    Guid Id { get; set; }
    string LoginName { get; set; }
    ...
}

public class User : IUser {
    ...implementation...
}
  

Конвертер:

 public class InterfaceExtractorJsonConverter<T> : JsonConverter {
    private class InterfaceDictionary<T> : Dictionary<string, object> { }

    private PropertyInfo[] InterfaceProperties {
        get { return typeof(T).GetProperties(); }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        var dictionary = new InterfaceDictionary<T>();
        foreach (var property in InterfaceProperties) {
            dictionary[property.Name] = value.GetType().GetProperty(property.Name).GetValue(value, null);
        }
        serializer.Serialize(writer, dictionary);
    }

    private object ReadNestedObject(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        while (reader.TokenType == JsonToken.Comment) {
            if (!reader.Read())
                throw new Exception("Unexpected end.");
        }

        switch (reader.TokenType) {
            case JsonToken.StartObject:
            case JsonToken.StartArray:
                return serializer.Deserialize(reader, objectType);
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Null:
            case JsonToken.Undefined:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return reader.Value;
            default:
                throw new Exception(string.Format("Unexpected token when converting object: {0}", reader.TokenType));
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        var obj = Activator.CreateInstance(objectType);

        while (reader.Read()) {
            switch (reader.TokenType) {
                case JsonToken.PropertyName:
                    string propertyName = reader.Value.ToString();

                    if (!reader.Read())
                        throw new Exception("Unexpected end.");


                    if (!InterfaceProperties.Any(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase))) {
                        reader.Skip();
                        continue;
                    }
                    var property = objectType.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

                    var innerObj = ReadNestedObject(reader, property.PropertyType, existingValue, serializer);

                    property.SetValue(obj, innerObj, null);
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return obj;
            }
        }
        throw new Exception("Unexpected end.");
    }

    public override bool CanConvert(Type objectType) {
        return objectType.GetInterfaces().Contains(typeof(T));
    }
}
  

В конвертер можно внести множество оптимизаций…