Я хочу, чтобы JSON "пропустил" десериализацию объекта JSON в C # List <object>, если отсутствует член JSON - PullRequest
0 голосов
/ 15 января 2019

Я сериализую список Интерфейсов в файл JSON, который я десериализирую позже. В одном приложении я десериализирую все типы объектов JSON, но в другом приложении я хочу десериализовать только одну конкретную реализацию интерфейса (Machine1) . Я хочу сохранить способ сериализации данных как есть, но измените код десериализации на SKIP для любого из неправильных объектов (объектов JSON другого типа, чем я заинтересован) - я планировал сделать это, проверив, Переменная-член отсутствует (если powerWatts отсутствует, то данные должны представлять Machine2, поэтому мы должны пропустить десериализацию этого конкретного объекта (Machine2) и вместо этого десериализовать остальные в массиве). Как бы я реализовать это с помощью JSON.Net? MissingMemberHandling.Error генерирует исключение, поэтому я не думаю, что сработает для десериализации остальной части списка после просмотра отсутствующего свойства. MissingMemberHandling.Ignore оставляет мне свойство, равное 0, что неверно.

Интерфейс и классы:

public interface IMachineInfo
{
    DateTime windowsTime { get; set; }
    DateTime irigTime { get; set; }
    string messageTypeFlag { get; }
    byte? sequenceNum { get; }
}  

public class Machine1 : IMachineInfo
{
    // Interface properties omitted for brevity
    public double powerWatts { get; set; }
    public double radiation { get; set; }
    public double current { get; set; }
}

public class Machine2 : IMachineInfo
{
    public double dbm { get; set; }
    public double magneticField { get; set; }
    public double frequency { get; set; }
}

Основной:

// Serialization: get the interface and specific data into a collection that will be written to a file

IMachineInfo decoded = data[key].MachineDataDecoded;
dataConverted[key] = decoded;

//... more code 

//write JSON data to JSONData.txt

string jsondata = JsonConvert.SerializeObject(dataConverted.Values.ToArray(), Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

File.AppendAllText(Environment.CurrentDirectory + subDirectoryName + jsonFileName, jsondata);

App2 (десериализация данных)

// try to read the json, but I only want Machine1 data, skip anything in the json that is related to machine2

JsonSerializerSettings settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    MissingMemberHandling = MissingMemberHandling.Ignore
};
List<Machine1> ampData = JsonConvert.DeserializeObject<List<Machine1>>(@"C/Path/jsonfile", settings);

Реальные данные JSON:

[
  {
    "windowsTime": "2019-01-14T18:47:55.8390256-06:00",
    "irigTime": "0001-01-01T00:00:00",
    "messageTypeFlag": "Q",
    "sequenceNum": 0,
  },
  {
    "dbm": "66",
    "magneticField ": "8967",
    "frequency": "34500",
    "windowsTime": "2019-01-14T18:47:55.8390256-06:00",
    "irigTime": "0001-01-01T00:00:00",
    "messageTypeFlag": "Q",
    "sequenceNum": 0,
  },
  {
    "powerWatts": "4000",
    "radiation": "67",
    "current": "2478",
    "windowsTime": "2019-01-14T18:47:55.8390256-06:00",
    "irigTime": "0001-01-01T00:00:00",
    "messageTypeFlag": "Q",
    "sequenceNum": 0,
  },
  {
    "powerWatts": "4000",
    "radiation": "67",
    "current": "2478",
    "windowsTime": "2019-01-14T18:47:55.8390258-06:00",
    "irigTime": "0001-01-01T00:00:00",
    "messageTypeFlag": "Q",
    "sequenceNum": 0,
   }
]

в моих данных выше только 3-й и 4-й элемент имеют тип Machine1, поэтому я хочу, чтобы эти объекты были добавлены в список только из десериализации json. Проблема в том, что когда я сейчас десериализую это, powerWatts = 0 для всех 4 элементов (не то поведение, которое я хочу) , хотя это только допустимое свойство 3-го и 4-го элемента. Это проблема, так как я не могу просто проверить, если powerWatts == 0 и удалить его из моего списка, так как 0 может быть допустимым значением в реальной ситуации. Мне нужно только десериализовать объекты Machine1 JSON

1 Ответ

0 голосов
/ 15 января 2019

Вы можете принять и расширить общий подход с этого ответа до Десериализацию полиморфных классов json без информации о типах, используя json.net или этот ответ до Как вызвать JsonConvert.DeserializeObject и отключить JsonConverter, примененный к базовому типу через [JsonConverter]? , создав custom JsonConverter для List<IMachineInfo>, который выполняет следующее:

  1. Загружает каждую запись массива во временную JToken.
  2. Пытается определить тип элемента по имеющимся параметрам.
  3. Пропускает запись массива, если тип не может быть выведен.

Для этого сначала введите общий конвертер базового класса следующим образом:

public abstract class JsonListItemTypeInferringConverterBase<TItem> : JsonConverter
{
    public override bool CanWrite { get { return false; } }

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

    protected abstract bool TryInferItemType(Type objectType, JToken json, out Type type);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Get contract information
        var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonArrayContract;
        if (contract == null || contract.IsMultidimensionalArray || objectType.IsArray)
            throw new JsonSerializationException(string.Format("Invalid array contract for {0}", objectType));

        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;

        if (reader.TokenType != JsonToken.StartArray)
            throw new JsonSerializationException(string.Format("Expected {0}, encountered {1} at path {2}", JsonToken.StartArray, reader.TokenType, reader.Path));

        var collection = existingValue as IList<TItem> ?? (IList<TItem>)contract.DefaultCreator();

        // Process the collection items
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.EndArray:
                    return collection;

                case JsonToken.Comment:
                    break;

                default:
                    {
                        var token = JToken.Load(reader);
                        Type itemType;
                        if (!TryInferItemType(typeof(TItem), token, out itemType))
                            break;
                        collection.Add((TItem)serializer.Deserialize(token.CreateReader(), itemType));
                    }
                    break;
            }
        }
        // Should not come here.
        throw new JsonSerializationException("Unclosed array at path: " + reader.Path);
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(List<TItem>));
    }
}

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

Затем создайте конкретную версию, которая десериализует только записи списка типа Machine1 следующим образом:

public class Machine1ListConverter<TMachineInfo> : JsonListItemTypeInferringConverterBase<TMachineInfo> where TMachineInfo : IMachineInfo
{
    protected override bool TryInferItemType(Type objectType, JToken json, out Type type)
    {
        var obj = json as JObject;
        if (obj != null && obj.GetValue("powerWatts", StringComparison.OrdinalIgnoreCase) != null)
        {
            type = typeof(Machine1);
            return true;
        }
        type = null;
        return false;
    }
}

И наконец десериализовать вашу строку JSON следующим образом:

var settings = new JsonSerializerSettings
{
    Converters = { new Machine1ListConverter<IMachineInfo>() },
};
var list = JsonConvert.DeserializeObject<List<IMachineInfo>>(jsonString, settings);

из, если вы хотите десериализовать в бетон List<Machine1> сделать:

var settings = new JsonSerializerSettings
{
    Converters = { new Machine1ListConverter<Machine1>() },
};
var list = JsonConvert.DeserializeObject<List<Machine1>>(jsonString, settings);

Примечания:

  • Преобразователь необходимо применять ко всей коллекции, а не к элементам коллекции, поскольку JsonConverter.ReadJson не имеет возможности пропускать считываемый в данный момент токен и предотвращать добавление возвращаемого значения в него. содержащий объект.

  • Для десериализации только элементов типа Machine2 вы также можете создать Machine2ListConverter, в котором TryInferItemType() проверяет наличие dbm.

  • Для десериализации вы звоните

    JsonConvert.DeserializeObject<List<Machine1>>(@"C/Path/jsonfile", settings);
    

    Но JsonConvert.DeserializeObject Method (String, JsonSerializerSettings) десериализует строку JSON, а не именованный файл. Для десериализации из файла см. Десериализацию JSON из файла .

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

...