JSON.net: làm thế nào để khử lưu huỳnh mà không sử dụng hàm tạo mặc định?


136

Tôi có một lớp có một hàm tạo mặc định và cũng là một hàm tạo quá tải có trong một tập các tham số. Các tham số này khớp với các trường trên đối tượng và được chỉ định khi xây dựng. Tại thời điểm này tôi cần hàm tạo mặc định cho các mục đích khác vì vậy tôi muốn giữ nó nếu tôi có thể.

Vấn đề của tôi: Nếu tôi loại bỏ hàm tạo mặc định và truyền vào chuỗi JSON, đối tượng sẽ giải tuần tự hóa một cách chính xác và chuyển vào các tham số của hàm tạo mà không có bất kỳ vấn đề nào. Tôi cuối cùng đã lấy lại được đối tượng cư trú theo cách tôi mong đợi. Tuy nhiên, ngay khi tôi thêm hàm tạo mặc định vào đối tượng, khi tôi gọi JsonConvert.DeserializeObject<Result>(jsontext)các thuộc tính không còn được điền.

Tại thời điểm này tôi đã cố gắng thêm new JsonSerializerSettings(){CheckAdditionalContent = true}vào cuộc gọi khử lưu huỳnh. điều đó không làm gì cả

Một lưu ý khác. các tham số của bộ điều khiển khớp chính xác với tên của các trường ngoại trừ các tham số được bắt đầu bằng một chữ cái viết thường. Tôi sẽ không nghĩ rằng điều này sẽ quan trọng vì, như tôi đã đề cập, quá trình khử lưu huỳnh hoạt động tốt mà không có hàm tạo mặc định.

Đây là một mẫu của các nhà xây dựng của tôi:

public Result() { }

public Result(int? code, string format, Dictionary<string, string> details = null)
{
    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;
}

Có lẽ điều này có thể giúp stackoverflow.com/questions/8254503/ Cách
csharpwinphonexaml

Câu trả lời:


208

Json.Net thích sử dụng hàm tạo mặc định (không tham số) trên một đối tượng nếu có. Nếu có nhiều hàm tạo và bạn muốn Json.Net sử dụng một hàm không mặc định, thì bạn có thể thêm [JsonConstructor]thuộc tính vào hàm tạo mà bạn muốn Json.Net gọi.

[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
    ...
}

Điều quan trọng là các tên tham số của hàm tạo khớp với các tên thuộc tính tương ứng của đối tượng JSON (trường hợp bỏ qua) để điều này hoạt động chính xác. Tuy nhiên, bạn không nhất thiết phải có một tham số hàm tạo cho mọi thuộc tính của đối tượng. Đối với các thuộc tính đối tượng JSON không được bao phủ bởi các tham số của hàm tạo, Json.Net sẽ cố gắng sử dụng các bộ truy cập thuộc tính công cộng (hoặc thuộc tính / trường được đánh dấu [JsonProperty]) để điền vào đối tượng sau khi xây dựng nó.

Nếu bạn không muốn thêm thuộc tính vào lớp của mình hoặc không kiểm soát mã nguồn cho lớp mà bạn đang cố gắng giải tuần tự hóa, thì một cách khác là tạo JsonConverter tùy chỉnh để khởi tạo và điền vào đối tượng của bạn. Ví dụ:

class ResultConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Result));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load the JSON for the Result into a JObject
        JObject jo = JObject.Load(reader);

        // Read the properties which will be used as constructor parameters
        int? code = (int?)jo["Code"];
        string format = (string)jo["Format"];

        // Construct the Result object using the non-default constructor
        Result result = new Result(code, format);

        // (If anything else needs to be populated on the result object, do that here)

        // Return the result
        return result;
    }

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

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

Sau đó, thêm trình chuyển đổi vào cài đặt serializer của bạn và sử dụng các cài đặt khi bạn khử lưu trữ:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);

4
Điều này đã làm việc. Thật là tệ khi bây giờ tôi phải lấy phần phụ thuộc JSON.net trong dự án mô hình của mình, nhưng cái này thì sao. Tôi sẽ đánh dấu đây là câu trả lời.
kmacdonald

3
Có các tùy chọn khác-- bạn có thể tạo một tùy chỉnh JsonConvertercho lớp của mình. Điều này sẽ loại bỏ sự phụ thuộc, nhưng sau đó bạn sẽ phải xử lý việc khởi tạo và tự điền đối tượng vào trình chuyển đổi. Cũng có thể viết một tùy chỉnh ContractResolversẽ hướng Json.Net sử dụng hàm tạo khác bằng cách thay đổi nó JsonObjectContract, nhưng điều này có thể chứng minh là khó hơn một chút so với âm thanh.
Brian Rogers

Vâng, tôi nghĩ rằng thuộc tính sẽ hoạt động tốt. Cuộc gọi deserialize thực sự chung chung để nó có thể là bất kỳ loại đối tượng nào. Tôi nghĩ rằng câu trả lời ban đầu của bạn sẽ làm việc tốt. Cảm ơn bạn về thông tin!
kmacdonald

2
Nó thực sự có ích nếu có thể đặt một quy ước khác cho lựa chọn hàm tạo. Ví dụ, tôi nghĩ rằng Unity container hỗ trợ này. Sau đó, bạn có thể làm cho nó để nó luôn chọn hàm tạo với hầu hết các tham số thay vì quay lại mặc định. Bất kỳ khả năng như một điểm mở rộng như vậy tồn tại trong Json.Net?
julealgon

1
Đừng quênusing Newtonsoft.Json;
Bruno Bieri

36

Hơi muộn và không thực sự phù hợp ở đây, nhưng tôi sẽ thêm giải pháp của mình vào đây, vì câu hỏi của tôi đã bị đóng như là một bản sao của câu hỏi này, và vì giải pháp này hoàn toàn khác.

Tôi cần một cách chung để hướng dẫn Json.NETthích cấu trúc cụ thể nhất cho kiểu cấu trúc do người dùng xác định, vì vậy tôi có thể bỏ qua các JsonConstructorthuộc tính sẽ thêm phụ thuộc vào dự án nơi mỗi cấu trúc như vậy được xác định.

Tôi đã đảo ngược một chút và triển khai trình giải quyết hợp đồng tùy chỉnh trong đó tôi đã ghi đè CreateObjectContractphương thức để thêm logic tạo tùy chỉnh của mình.

public class CustomContractResolver : DefaultContractResolver {

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var c = base.CreateObjectContract(objectType);
        if (!IsCustomStruct(objectType)) return c;

        IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
        var mostSpecific = list.LastOrDefault();
        if (mostSpecific != null)
        {
            c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
            c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
        }

        return c;
    }

    protected virtual bool IsCustomStruct(Type objectType)
    {
        return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
    }

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        method.ThrowIfNull("method");
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }
}

Tôi đang sử dụng nó như thế này.

public struct Test {
  public readonly int A;
  public readonly string B;

  public Test(int a, string b) {
    A = a;
    B = b;
  }
}

var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
  ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");

2
Tôi hiện đang sử dụng câu trả lời được chấp nhận ở trên, nhưng muốn cảm ơn bạn vì đã cho thấy giải pháp của bạn là tốt!
DotBert

1
Tôi đã loại bỏ các hạn chế về cấu trúc (kiểm tra objectType.IsValueType) và điều này hoạt động rất tốt, cảm ơn!
Alex Angas

@AlexAngas Có áp dụng chiến lược này nói chung có ý nghĩa cảm ơn bạn đã phản hồi của bạn.
Zoltán Tamási

3

Dựa trên một số câu trả lời ở đây, tôi đã viết một CustomConstructorResolverđể sử dụng trong một dự án hiện tại và tôi nghĩ rằng nó có thể giúp đỡ người khác.

Nó hỗ trợ các cơ chế phân giải sau, tất cả đều có thể cấu hình:

  • Chọn một hàm tạo riêng tư để bạn có thể xác định một hàm tạo riêng mà không phải đánh dấu nó bằng một thuộc tính.
  • Chọn hàm tạo riêng cụ thể nhất để bạn có thể có quá tải, mà không phải sử dụng thuộc tính.
  • Chọn hàm tạo được đánh dấu bằng một thuộc tính của một tên cụ thể - như trình phân giải mặc định, nhưng không phụ thuộc vào gói Json.Net vì bạn cần tham chiếu Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
    public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
    public bool IgnoreAttributeConstructor { get; set; } = false;
    public bool IgnoreSinglePrivateConstructor { get; set; } = false;
    public bool IgnoreMostSpecificConstructor { get; set; } = false;

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        var contract = base.CreateObjectContract(objectType);

        // Use default contract for non-object types.
        if (objectType.IsPrimitive || objectType.IsEnum) return contract;

        // Look for constructor with attribute first, then single private, then most specific.
        var overrideConstructor = 
               (this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType)) 
            ?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType)) 
            ?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));

        // Set override constructor if found, otherwise use default contract.
        if (overrideConstructor != null)
        {
            SetOverrideCreator(contract, overrideConstructor);
        }

        return contract;
    }

    private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
    {
        contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
        contract.CreatorParameters.Clear();
        foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
        {
            contract.CreatorParameters.Add(constructorParameter);
        }
    }

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    {
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    }

    protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();

        if (constructors.Count == 1) return constructors[0];
        if (constructors.Count > 1)
            throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");

        return null;
    }

    protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

        return constructors.Length == 1 ? constructors[0] : null;
    }

    protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
    {
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .OrderBy(e => e.GetParameters().Length);

        var mostSpecific = constructors.LastOrDefault();
        return mostSpecific;
    }
}

Đây là phiên bản hoàn chỉnh với tài liệu XML dưới dạng ý chính: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

Phản hồi đánh giá cao.


Giải pháp tuyệt vời! Cám ơn vì đã chia sẻ.
thomai

1

Hành vi mặc định của Newtonsoft.Json sẽ tìm các nhà publicxây dựng. Nếu hàm tạo mặc định của bạn chỉ được sử dụng trong việc chứa lớp hoặc cùng một cụm, bạn có thể giảm mức truy cập xuống protectedhoặc internalđể Newtonsoft.Json sẽ chọn hàm tạo mong muốn của bạn public.

Phải thừa nhận rằng, giải pháp này khá hạn chế trong các trường hợp cụ thể.

internal Result() { }

public Result(int? code, string format, Dictionary<string, string> details = null)
{
    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;
}

-1

Giải pháp:

public Response Get(string jsonData) {
    var json = JsonConvert.DeserializeObject<modelname>(jsonData);
    var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
    return data;
}

Mô hình:

public class modelname {
    public long parameter{ get; set; }
    public int parameter{ get; set; }
    public int parameter{ get; set; }
    public string parameter{ get; set; }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.