Как сохранить объектную модель в JSON и сериализовать словарь в виде массива значений? - PullRequest
1 голос
/ 19 апреля 2019
public class Portfolio
{
    public string Name {get; set;}
    public Dictionary<string,Position> Positions {get; set;}
}

public class Position
{
    public string Ticker {get; set;}
    public decimal Size {get; set;}
}

JsonConvert.SerializeObject(portfolio);

будет производить, например:

{
  "Name": "SP500",
  "Positions": {
        "AOS": {
          "Ticker": "AOS",
          "Size": 100,
        },
        "ABT": {
          "Ticker": "ABT",
          "Size": 100,
        }
    }
}

Но я хотел бы уменьшить пространство и сохранить позиции в виде массива:

{
  "Name": "SP500",
  "Positions": 
  [
      {
        "Ticker": "AOS",
        "Size": 100,
      },
      {
        "Ticker": "ABT",
        "Size": 100,
      }
  ]
}

Как сохранить и загрузить этот JSON в мою объектную модель?

Ответы [ 5 ]

1 голос
/ 19 апреля 2019

Вы можете добавить custom JsonConverter для Dictionary<string,Position>, который сериализует значения словаря в виде списка.Преобразователь предполагает, что ключ словаря может быть каким-то образом извлечен из значения словаря во время десериализации.

Сначала определите следующий преобразователь:

public class PositionDictionaryConverter : DictionaryAsValueListConverter<Position>
{
    protected override string GetKey(Position position) { return position.Ticker; }
}

public abstract class DictionaryAsValueListConverter<TValue> : JsonConverter
{
    protected abstract string GetKey(TValue value);

    public override bool CanConvert(Type objectType)
    {
        return typeof(Dictionary<string, TValue>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = serializer.Deserialize<List<TValue>>(reader);
        var dictionary = (IDictionary<string, TValue>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        foreach (var item in list)
            dictionary.Add(GetKey(item), item);
        return dictionary;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, ((IDictionary<string, TValue>)value).Values);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        if (reader.TokenType == JsonToken.None)
            reader.Read();
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }
}

Затем примените его к вашей модели следующим образом:

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

    [JsonConverter(typeof(PositionDictionaryConverter))]
    public Dictionary<string,Position> Positions {get; set;}
}

Или используйте его в настройках следующим образом:

var settings = new JsonSerializerSettings
{
    Converters = { new PositionDictionaryConverter() },
};

var json = JsonConvert.SerializeObject(root, settings);

Конечно, конвертер предполагает, что ключ словаря и имя тикера идентичны.

Демонстрационная скрипка здесь .

1 голос
/ 19 апреля 2019

используйте это:

public class Portfolio
{
    public string Name {get; set;}
    public List<Position> Positions {get; set;}
}
0 голосов
/ 19 апреля 2019

Самый простой вариант - написать отдельную модель, которая используется для целей сериализации:

Классы:

public class Portfolio
{
    public string Name { get; set; }
    public Dictionary<string, Position> Positions { get; set; }
}

//this is a class used just for serialization
public class PortfolioForSerialization
{
    public string Name { get; set; }
    public Position[] Positions { get; set; }
}

public class Position
{
    public string Ticker { get; set; }
    public decimal Size { get; set; }
}

Использование:

//The portfolio class you want to work with (dictionary property)
var portfolio = new Portfolio()
{
    Name = "MyPortfolio",
    Positions = new Dictionary<string, UserQuery.Position>()
    {
        { "AOS", new Position() { Size = 10, Ticker = "AOS"}},
        { "ABT", new Position() { Size = 100, Ticker = "ABT"}}
    }
};
//Only used for serialization
var forSerialization = new PortfolioForSerialization()
{
    Name = portfolio.Name,
    Positions = portfolio.Positions.Select(p => p.Value).ToArray()
};

string serialized = JsonConvert.SerializeObject(forSerialization);

//On deserialization, map the array back to a dictionary
var deserializedPortfolio = JsonConvert.DeserializeObject<PortfolioForSerialization>(serialized);

//Map the serialization model back to your working model
var workingPortfolio = new Portfolio()
{
    Name = deserializedPortfolio.Name,
    Positions = deserializedPortfolio.Positions.ToDictionary(k => k.Ticker, v => v)
};


В противном случаевам нужно будет использовать пользовательский обработчик контрактов .

0 голосов
/ 19 апреля 2019

Что плохого в том, чтобы просто сменить объектную модель для портфолио на приведенную ниже?Я думаю, что это будет сериализовать, как вы хотитеВозможно, вам придется использовать LINQ, чтобы найти нужный объект в списке.Это также предполагает, что всегда будет уникальный тикер

public class Portfolio
{
    public string Name {get; set;}
    public List<Position> Positions {get; set;}
}
0 голосов
/ 19 апреля 2019

Вы можете изменить свою модель на:

public class Portfolio
{
    public string Name {get; set;}
    public List<Position> Positions {get; set;}
}
...