Как сериализовать объекты верхнего уровня с Json.NET в качестве объектов и вложенные объекты в качестве ссылок - PullRequest
0 голосов
/ 29 марта 2019

Я хочу иметь возможность полностью сериализовать объект, если он находится на верхнем уровне контекста сериализации, но сериализовать объекты ниже в контексте по ссылке.

Я искал и пробовал тестировать с пользовательским контрактомРезольверы, пользовательские конвертеры Json и пользовательский IReferenceResolver, но я не могу найти способ сделать это.

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

Для этого типа и теста

public class IdType
{
   public IdType(string id)
   {
      Id = id;
   }

   public string Id {get;}

   public string Name {get;set;}

   public int Number {get; set;} 

   public IdType OtherType { get; set; }

   public IEnumerable<IdType> Types { get; set;}

   public IDictionary<IdType, string> { get; set; }

   public IDictionary<string, IdType> {get; set; }
}

[TestMethod]
public void SerializeTest()
{
    var t1 = new IdType(1) { Name = 'Alice', Number = 42 };
    var t2 = new IdType(2) { Name = 'Bob', Number = 21, OtherType = t1 };
    var t3 = new IdType(2) { Name = 'Charlie', Number = 84, OtherType = t2, Types = new[] {t1, t2} };

    var testTypes = new[] 
    {
        t1,
        t3
    };

    var serializer = new JsonSerializer
    {
        Formatting = Formatting.Indented,
    };

    StringWriter writer;
    using (writer = new StringWriter())
    {
        serializer.Serialize(writer, myObject);
    }

    Console.WriteLine(writer.ToString());    
}

Я хочу вывод, подобный этому


[
    {
      "Id": "1",
      "Name": "Alice"
      "Number": 42,
    },
    {
      "Id": "3",
      "Name": "Charlie"
      "Number": 84,
      "OtherType": 2
      "Types": [
         "Id" :  1, 2
      ]
    }
]

JsonConverter не имеет контекста, поэтому он всегда будет преобразовывать так или иначе.

Пользовательский преобразователь (производный от DefaultContractResolver) будет работать для свойства типа IdType, но я не могу работатьо том, как заставить его работать со списками и словарями.

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

Любые предложения по достижению этого будут с благодарностью приняты

1 Ответ

0 голосов
/ 31 марта 2019

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

Я еще не реализовал словари, но это работает для основных свойств и списков:

public class CustomResolver : DefaultContractResolver
{
    readonly CamelCasePropertyNamesContractResolver innerResolver = new CamelCasePropertyNamesContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var jsonProperty = base.CreateProperty(member, memberSerialization);

        if (!jsonProperty.PropertyType.IsPrimitive && jsonProperty.PropertyType != typeof(string) && jsonProperty.Readable)
        {
            var innerContract = innerResolver.ResolveContract(jsonProperty.PropertyType);
            if (typeof(IdType).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonObjectContract objectContract)
            {
                jsonProperty.Converter = new IdConverter();
            }
            else if (typeof(IEnumerable<IdType>).IsAssignableFrom(innerContract.UnderlyingType) && innerContract is JsonArrayContract arrayContract)
            {
                jsonProperty.Converter = new IdConverter();
            }
        }

        return jsonProperty;
    }
}

internal class IdConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (typeof(IdType).IsAssignableFrom(objectType) ||
                typeof(IEnumerable<IdType>).IsAssignableFrom(objectType));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value is IdType item)
        {
            JToken token = JToken.FromObject(item.Id);
            token.WriteTo(writer);
        }
        else if (value is IEnumerable<IdType> itemCollection)
        {
            JArray array = new JArray();
            foreach (var i in itemCollection)
            {
                JToken token = JToken.FromObject(i.Id);
                array.Add(token);
            }
            array.WriteTo(writer);
        }
    }

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

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

Чтобы сериализовать и использовать пользовательский преобразователь, вы должны:


    var serializer = new JsonSerializer
    {
        Formatting = Formatting.Indented,
        ContractResolver = new CustomResolver(),
    };
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...