Вложенные JO-объекты сериализуются как пустые массивы - PullRequest
1 голос
/ 02 апреля 2020

У меня действительно странная ситуация, когда я пытаюсь сериализовать объект, возвращенный сторонним API, в JSON. У меня нет никакого контроля над сторонним API или объектом, который он возвращает. C# POCO, который я пытаюсь сериализовать, выглядит примерно так:

public class JobSummary {
    public Job Job { get; set; }    
}

public class Job {
    public Status Status { get; set; }
}

public class Status {
    public object JobOutput { get; set; }
    public int Progress { get; set; }
}

Исходя из того, что возвращает сторонняя библиотека, я ожидаю, что она сериализуется в следующем порядке. Во время выполнения я могу сказать, что тип JobOutput - это JObject, который содержит один ключ (Count) и значение (0).

{
   job: {
       status: {
           jobOutput: {
               Count: 0
           },
           progress: 100
       }
   }
}

В этом случае работа и статус, очевидно, являются объектами. прогресс - int, а jobOutput - JObject.

Если я запускаю любой из следующих вариантов:

  1. JToken.FromObject(jobSummary)
  2. JObject.FromObject(jobSummary)
  3. JObject.Parse(jobSummary)

И ToString() или JsonConvert.SerializeObject() В результате я получаю следующий вывод:

{
   job: {
       status: {
           jobOutput: {
               Count: []
           },
           progress: 100
       }
   }
}

Обратите внимание, что Count стал [].

Но если я сделаю jobSummary.Status.JobOutput.ToString(), я правильно верну 0, поэтому я знаю, что POCO, возвращаемое сторонней библиотекой, не искажено и содержит необходимую информацию.

Кто-нибудь знает, что может происходить? Или как правильно сериализовать вложенный JObject?

Edit: Я должен уточнить, что я нахожусь на v6.0.8 из Newtonsoft по причинам, не зависящим от меня, и что сборка третьей стороны, которая содержит в POCO есть неизвестная версия Newtonsoft ILMerged. Я не знаю, имеет ли это отношение.

1 Ответ

1 голос
/ 03 апреля 2020

Вы написали, что

Я должен уточнить, что я нахожусь на 6.0.8 из Newtonsoft по независящим от меня причинам, и что у сторонней сборки, содержащей POCO, есть неизвестная версия Newtonsoft ILMerged в этом.

Это объясняет вашу проблему. JobOutput содержит объект с полным именем Newtonsoft.Json.Linq.JObject из совершенно другой библиотеки Json. NET, чем та, которую вы используете . Когда ваша версия Json. NET проверяет, является ли сериализуемый объект JToken, она проверяет objectType.IsSubclassOf(typeof(JToken)), что приведет к ошибке, поскольку тип ILMerged на самом деле не является подклассом вашей версии. тип, несмотря на то же имя.

В качестве обходного пути вам потребуется создать собственный JsonConverter logi c, который использует методы ToString() внешних объектов JToken для генерации вывода JSON, затем пишет, что JSON в поток JSON, который вы генерируете. Следующие должны сделать эту работу:

public class ForeignJsonNetContainerConverter : ForeignJsonNetBaseConverter
{
    static readonly string [] Names = new []
    {
        "Newtonsoft.Json.Linq.JObject",
        "Newtonsoft.Json.Linq.JArray",
        "Newtonsoft.Json.Linq.JConstructor",
        "Newtonsoft.Json.Linq.JRaw",
    };

    protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var json = value.ToString();
        // Fix indentation
        using (var stringReader = new StringReader(json))
        using (var jsonReader = new JsonTextReader(stringReader))
        {
            writer.WriteToken(jsonReader);
        }
    }
}

public class ForeignJsonNetValueConverter : ForeignJsonNetBaseConverter
{
    static readonly string [] Names = new []
    {
        "Newtonsoft.Json.Linq.JValue",
    };

    protected override IReadOnlyCollection<string> TypeNames { get { return Names; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var underlyingValue = ((dynamic)value).Value;
        if (underlyingValue == null)
        {
            writer.WriteNull();
        }
        else
        {
            // JValue.ToString() will be wrong for DateTime objects, we need to serialize them instead.
            serializer.Serialize(writer, underlyingValue);
        }
    }
}

public abstract class ForeignJsonNetBaseConverter : JsonConverter
{
    protected abstract IReadOnlyCollection<string> TypeNames { get; }

    public override bool CanConvert(Type objectType)
    {
        if (objectType.IsPrimitive)
            return false;
        // Do not use the converter for Native JToken types, only non-native types with the same name(s).
        if (objectType == typeof(JToken) || objectType.IsSubclassOf(typeof(JToken)))
            return false;
        var fullname = objectType.FullName;
        if (TypeNames.Contains(fullname))
            return true;
        return false;
    }

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

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

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

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

var json = JsonConvert.SerializeObject(summary, Formatting.Indented, settings);

Примечания:

  • Преобразователи работать, предполагая, что типы, чьи FullName соответствуют именам типов Json. NET, на самом деле являются Json. NET типами из другой версии.

  • JValue.ToString() возвращает локализованные значения для DateTime объектов (подробности см. здесь ), поэтому я создал отдельный преобразователь для JValue.

  • Я также исправил отступы.

Макетная скрипка здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...