Helpex - Trao đổi & giúp đỡ Đăng nhập
37

Tôi có một từ điển với Loại .net tùy chỉnh làm khóa của nó. Tôi đang cố gắng tuần tự hóa từ điển này thành JSON bằng cách sử dụng JSON.net, Tuy nhiên, nó không thể chuyển đổi các khóa thành giá trị thích hợp trong quá trình tuần tự hóa.

class ListBaseClass
{
    public String testA;
    public String testB;
}
-----
var details = new Dictionary<ListBaseClass, string>();
details.Add(new ListBaseClass { testA = "Hello", testB = "World" }, "Normal");
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

Cái này cho tôi -> "{\" JSonSerialization.ListBaseClass \ ": \" Normal \ "}"

Tuy nhiên, nếu tôi có loại Tùy chỉnh làm giá trị trong Từ điển thì nó hoạt động tốt

  var details = new Dictionary<string, ListBaseClass>();
  details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
  var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
  var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, ListBaseClass>>(results);

Cái này cho tôi -> "{\" Bình thường \ ": {\" testA \ ": \" Xin chào \ ", \" testB \ ": \" Thế giới \ "}}"

Ai đó có thể đề xuất nếu tôi đang gặp phải một số hạn chế của Json.net hoặc tôi đang làm điều gì đó sai?

37 hữu ích 0 bình luận 17k xem chia sẻ
32

Các serialization Hướng dẫn quốc (xem phần: Từ điển và Hashtables; cảm ơn bạn @Shashwat cho liên kết):

Khi tuần tự hóa một từ điển, các khóa của từ điển được chuyển đổi thành chuỗi và được sử dụng làm tên thuộc tính đối tượng JSON. Chuỗi được viết cho một khóa có thể được tùy chỉnh bằng cách ghi đè ToString () cho loại khóa hoặc bằng cách triển khai TypeConverter. TypeConverter cũng sẽ hỗ trợ chuyển đổi lại một chuỗi tùy chỉnh khi giải kích thước từ điển.

Tôi đã tìm thấy một ví dụ hữu ích về cách triển khai bộ chuyển đổi kiểu như vậy trên trang "hướng dẫn" của Microsoft:

Về cơ bản, tôi cần mở rộng System.ComponentModel.TypeConvertervà ghi đè:

bool CanConvertFrom(ITypeDescriptorContext context, Type source);

object ConvertFrom(ITypeDescriptorContext context,
                   System.Globalization.CultureInfo culture, object value);

object ConvertTo(ITypeDescriptorContext context, 
                 System.Globalization.CultureInfo culture, 
                 object value, Type destinationType);

Nó cũng cần thiết để thêm thuộc tính [TypeConverter(typeof(MyClassConverter))] vào MyClasskhai báo lớp.

Với những tại chỗ, tôi đã có thể serialize và từ điển deserialize tự động .

32 hữu ích 3 bình luận chia sẻ
26

Bạn có thể không muốn sử dụng câu trả lời mà Gordon Bean đã trình bày. Giải pháp hoạt động, nhưng nó cung cấp một chuỗi được tuần tự hóa cho đầu ra. Nếu bạn đang sử dụng JSON, điều này sẽ cung cấp cho bạn kết quả ít hơn lý tưởng, vì bạn thực sự muốn biểu diễn JSON của một đối tượng chứ không phải biểu diễn chuỗi.

ví dụ: giả sử rằng bạn có cấu trúc dữ liệu liên kết các điểm lưới duy nhất với các chuỗi:

class Point
{
    public int x { get; set; }
    public int y { get; set; }
}

public Dictionary<Point,string> Locations { get; set; };

Sử dụng ghi đè TypeConverter, bạn sẽ nhận được biểu diễn chuỗi của đối tượng này khi bạn tuần tự hóa nó.

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},

Nhưng những gì chúng tôi thực sự muốn là:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},

Có một số vấn đề với việc ghi đè TypeConverter để tuần tự hóa / giải mã hóa lớp.

Đầu tiên, đây không phải là JSON và bạn có thể phải viết logic tùy chỉnh bổ sung để xử lý tuần tự hóa và giải mã hóa nó ở nơi khác. (chẳng hạn như Javascript trong lớp khách hàng của bạn?)

Thứ hai, Bất kỳ nơi nào khác sử dụng đối tượng này bây giờ sẽ phun ra chuỗi này, nơi trước đó nó được tuần tự hóa đúng cách thành một đối tượng:

"GridCenterPoint": { "x": 0, "y": 0 },

hiện đăng thành:

"GridCenterPoint": "0,0",

Bạn có thể kiểm soát định dạng TypeConverter một chút, nhưng bạn không thể tránh khỏi thực tế là nó được hiển thị dưới dạng một chuỗi chứ không phải một đối tượng.

Vấn đề này không phải là vấn đề với bộ tuần tự, vì Json.NET nhai qua các đối tượng phức tạp mà không bỏ sót một nhịp nào, đó là vấn đề với cách xử lý các khóa từ điển. Nếu bạn thử lấy đối tượng ví dụ và tuần tự hóa một Danh sách hoặc thậm chí một Hashset, bạn nhận thấy rằng không có vấn đề gì khi tạo JSON thích hợp. Điều này cung cấp cho chúng tôi một cách đơn giản hơn nhiều để giải quyết vấn đề này.

Lý tưởng nhất, chúng tôi muốn chỉ cho Json.NET tuần tự hóa khóa dưới dạng bất kỳ loại đối tượng nào, và không buộc nó phải là một chuỗi. Vì đó dường như không phải là một lựa chọn, nên có một cách khác là cung cấp cho Json.NET thứ gì đó mà nó có thể hoạt động: a List<KeyValuePair<T,K>>.

Nếu bạn cung cấp danh sách các KeyValuePairs vào bộ tuần tự của Json.NET, bạn sẽ nhận được chính xác những gì bạn mong đợi. Ví dụ: đây là một trình bao bọc đơn giản hơn nhiều mà bạn có thể triển khai:

    private Dictionary<Point, string> _Locations;
    public List<KeyValuePair<Point, string>> SerializedLocations
    {
        get { return _Locations.ToList(); }
        set { _Locations= value.ToDictionary(x => x.Key, x => x.Value); }
    }

Thủ thuật này hoạt động, vì các khóa trong kvp không bị buộc ở định dạng chuỗi. Tại sao lại là một định dạng chuỗi, bạn hỏi? Nó đánh bại tôi. đối tượng Dictionary triển khai IEnumerable<KeyValuePair<TKey, TValue>>giao diện, vì vậy sẽ không có bất kỳ vấn đề nào trong việc tuần tự hóa nó theo kiểu giống như danh sách các kvps, vì đó về cơ bản là từ điển. Ai đó (James Newton?) Đã đưa ra quyết định khi viết bộ tuần tự từ điển Newtonsoft rằng các phím phức tạp quá lộn xộn để xử lý. Có lẽ có một số trường hợp góc mà tôi đã không xem xét làm cho vấn đề này trở nên khó khăn hơn nhiều.

Đây là một giải pháp tốt hơn nhiều vì nó tạo ra các đối tượng JSON thực tế, đơn giản hơn về mặt kỹ thuật và không tạo ra bất kỳ tác dụng phụ nào do thay thế bộ tuần tự.

26 hữu ích 4 bình luận chia sẻ
3

Một cách khác để thực hiện điều này là sử dụng ContractResolver tùy chỉnh và thiết lập OverrideCreator.

public class DictionaryAsArrayResolver : DefaultContractResolver
{
    public override JsonContract CreateContract(Type objectType)
    {
        if (IsDictionary(objectType))
        {
            JsonArrayContract contract = base.CreateArrayContract(objectType);
            contract.OverrideCreator = (args) => CreateInstance(objectType);
            return contract;
        }

        return base.CreateContract(objectType);
    }

    internal static bool IsDictionary(Type objectType)
    {
        if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
        {
            return true;
        }

        if (objectType.GetInterface(typeof(IDictionary<,>).Name) != null)
        {
            return true;
        }

        return false;
    }

    private object CreateInstance(Type objectType)
    {
        Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(objectType.GetGenericArguments());
        return Activator.CreateInstance(dictionaryType);
    }
}

Sử dụng:

JsonSerializer jsonSerializer = new JsonSerializer();
jsonSerializer.ContractResolver = new DictionaryAsArrayResolver();
3 hữu ích 1 bình luận chia sẻ
0

Lấy cảm hứng từ gson enableComplexMapKeySerialization và cách thức hoạt động \:

public class DictionaryAsArrayJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (IDictionary)value;

        writer.WriteStartArray();

        var en = dictionary.GetEnumerator();
        while (en.MoveNext())
        {
            writer.WriteStartArray();
            serializer.Serialize(writer, en.Key);
            serializer.Serialize(writer, en.Value);
            writer.WriteEndArray();
        }
        
        writer.WriteEndArray();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!CanConvert(objectType))
            throw new Exception(string.Format("This converter is not for {0}.", objectType));

        Type keyType = null;
        Type valueType = null;
        IDictionary result;

        if (objectType.IsGenericType)
        {
            keyType = objectType.GetGenericArguments()[0];
            valueType = objectType.GetGenericArguments()[1];
            var dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
            result = (IDictionary)Activator.CreateInstance(dictionaryType);
        }
        else
        {
            result = (IDictionary)Activator.CreateInstance(objectType);
        }

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

        int depth = reader.Depth;
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartArray)
            {
            }
            else if (reader.TokenType == JsonToken.EndArray)
            {
                if (reader.Depth == depth)
                    return result;
            }
            else
            {
                object key = serializer.Deserialize(reader, keyType);
                reader.Read();
                object value = serializer.Deserialize(reader, valueType);
                result.Add(key, value);
            }
        }

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary).IsAssignableFrom(objectType);
    }

}

Có thể tạo json giống như mã Tal Aloni, nhưng dưới dạng JsonConverter thay vì hợp đồng. Linh hoạt hơn vì nó có thể được sử dụng trên các thuộc tính đã chọn với JsonConverterAttribute hoặc trên mọi thứ với JsonSerializerSettings.Converters.Add (...)

0 hữu ích 0 bình luận chia sẻ
0

Dựa trên phản hồi sâu sắc của @ roger-hill, tôi đã tạo phần sau JsonConvertersẽ chuyển đổi một IDictionaryđối tượng thành một Listtrong KeyValuePaircác đối tượng.

liên kết github

public class ListDictionaryConverter : JsonConverter
{
    private static (Type kvp, Type list, Type enumerable, Type[] args) GetTypes(Type objectType)
    {
        var args = objectType.GenericTypeArguments;
        var kvpType = typeof(KeyValuePair<,>).MakeGenericType(args);
        var listType = typeof(List<>).MakeGenericType(kvpType);
        var enumerableType = typeof(IEnumerable<>).MakeGenericType(kvpType);

        return (kvpType, listType, enumerableType, args);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var (kvpType, listType, _, args) = GetTypes(value.GetType());
        
        var keys = ((IDictionary)value).Keys.GetEnumerator();
        var values = ((IDictionary)value).Values.GetEnumerator();
        var cl = listType.GetConstructor(Array.Empty<Type>());
        var ckvp = kvpType.GetConstructor(args);
        
        var list = (IList)cl!.Invoke(Array.Empty<object>());
        while (keys.MoveNext() && values.MoveNext())
        {
            list.Add(ckvp!.Invoke(new []{keys.Current, values.Current}));
        }
        
        serializer.Serialize(writer, list);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var (_, listType, enumerableType, args) = GetTypes(objectType);
        
        var list = ((IList)(serializer.Deserialize(reader, listType)));

        var ci = objectType.GetConstructor(new[] {enumerableType});
        if (ci == null)
        {
            ci = typeof(Dictionary<,>).MakeGenericType(args).GetConstructor(new[] {enumerableType});
        }
        
        var dict = (IDictionary) ci!.Invoke(new object[]{ list });

        return dict;
    }

    public override bool CanConvert(Type objectType)
    {
        if (!objectType.IsGenericType) return objectType.IsAssignableTo(typeof(IDictionary));
        
        var args = objectType.GenericTypeArguments;
        return args.Length == 2 && objectType.IsAssignableTo(typeof(IDictionary<,>).MakeGenericType(args));
    }
}

Tôi đã thực hiện một số thử nghiệm và mã này hoạt động tốt trong các thử nghiệm đó ... nhưng tôi có thể thiếu một hoặc hai trường hợp cạnh.

0 hữu ích 0 bình luận chia sẻ
-4

Mọi thứ dễ dàng hơn

var details = new Dictionary<string, ListBaseClass>();
details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details.ToList());
var data = 
Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

Mẫu vật

 class Program
{
    static void Main(string[] args)
    {
        var  testDictionary = new Dictionary<TestKey,TestValue>()
        {
            {
                new TestKey()
                {
                    TestKey1 = "1",
                    TestKey2 = "2",
                    TestKey5 = 5
                },
                new TestValue()
                {
                    TestValue1 = "Value",
                    TestValue5 = 96
                }
            }
        };

        var json = JsonConvert.SerializeObject(testDictionary);
        Console.WriteLine("=== Dictionary<TestKey,TestValue> ==");
        Console.WriteLine(json);
        // result: {"ConsoleApp2.TestKey":{"TestValue1":"Value","TestValue5":96}}


        json = JsonConvert.SerializeObject(testDictionary.ToList());
        Console.WriteLine("=== List<KeyValuePair<TestKey, TestValue>> ==");
        Console.WriteLine(json);
        // result: [{"Key":{"TestKey1":"1","TestKey2":"2","TestKey5":5},"Value":{"TestValue1":"Value","TestValue5":96}}]


        Console.ReadLine();

    }
}

class TestKey
{
    public string TestKey1 { get; set; }

    public string TestKey2 { get; set; }

    public int TestKey5 { get; set; }
}

class TestValue 
{
    public string TestValue1 { get; set; }

    public int TestValue5 { get; set; }
}
-4 hữu ích 1 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ c# json serialization dictionary json.net , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading