JSON.net: วิธีการดีซีเรียลไลซ์โดยไม่ต้องใช้ Constructor เริ่มต้น?


136

ฉันมีคลาสที่มีคอนสตรัคเตอร์เริ่มต้นและคอนสตรัคเตอร์โอเวอร์โหลดที่ใช้ในชุดพารามิเตอร์ พารามิเตอร์เหล่านี้จับคู่กับเขตข้อมูลบนวัตถุและได้รับมอบหมายในการก่อสร้าง ณ จุดนี้ฉันต้องการตัวสร้างเริ่มต้นสำหรับวัตถุประสงค์อื่น ๆ ดังนั้นฉันต้องการเก็บไว้ถ้าฉันสามารถ

ปัญหาของฉัน: ถ้าฉันลบตัวสร้างเริ่มต้นและส่งผ่านในสตริง JSON วัตถุ deserializes อย่างถูกต้องและผ่านในพารามิเตอร์ตัวสร้างโดยไม่มีปัญหาใด ๆ ฉันได้รับวัตถุกลับมาเติมในแบบที่ฉันคาดหวัง อย่างไรก็ตามทันทีที่ฉันเพิ่มนวกรรมิกเริ่มต้นลงในวัตถุเมื่อฉันเรียกJsonConvert.DeserializeObject<Result>(jsontext)คุณสมบัติที่จะไม่บรรจุอีกต่อไป

ณ จุดนี้ฉันได้ลองเพิ่มnew JsonSerializerSettings(){CheckAdditionalContent = true}การโทรดีซีเรียลไลเซชั่น ที่ไม่ได้ทำอะไรเลย

หมายเหตุอื่น พารามิเตอร์ตัวสร้างตรงกับชื่อของเขตข้อมูลอย่างแน่นอนยกเว้นว่าพารามิเตอร์จะเริ่มต้นด้วยตัวอักษรตัวพิมพ์เล็ก ฉันไม่คิดว่าจะเป็นเช่นนี้เพราะอย่างที่ฉันได้กล่าวไว้การดีซีเรียลไลเซชั่นใช้งานได้ดีโดยไม่มีคอนสตรัคเตอร์เริ่มต้น

นี่คือตัวอย่างของการก่อสร้างของฉัน:

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;
}

บางทีนี่อาจช่วยstackoverflow.com/questions/8254503/…
csharpwinphonexaml

คำตอบ:


208

Json.Net ต้องการใช้คอนสตรัคเตอร์เริ่มต้น (แบบไม่มีพารามิเตอร์) บนวัตถุหากมี หากมีหลายคอนสตรัคเตอร์และคุณต้องการให้ Json.Net ใช้อันที่ไม่ใช่ค่าปริยายคุณสามารถเพิ่มแอ[JsonConstructor]ททริบิวให้กับคอนสตรัคเตอร์ที่คุณต้องการให้ Json.Net โทรได้

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

มันเป็นสิ่งสำคัญที่ชื่อพารามิเตอร์คอนสตรัคตรงกับชื่อคุณสมบัติที่สอดคล้องกันของวัตถุ JSON (ละเว้นกรณี) เพื่อให้ทำงานได้อย่างถูกต้อง อย่างไรก็ตามคุณไม่จำเป็นต้องมีพารามิเตอร์คอนสตรัคเตอร์สำหรับคุณสมบัติทุกอย่างของวัตถุ สำหรับคุณสมบัติอ็อบเจ็กต์ JSON เหล่านั้นที่ไม่ได้ครอบคลุมโดยพารามิเตอร์ Constructor Json.Net จะพยายามใช้ accessors คุณสมบัติสาธารณะ (หรือคุณสมบัติ / ฟิลด์ที่มีเครื่องหมาย[JsonProperty]) เพื่อเติมข้อมูลให้กับวัตถุหลังจากสร้างมันขึ้นมา

หากคุณไม่ต้องการเพิ่มคุณสมบัติให้กับคลาสของคุณหรือไม่ควบคุมซอร์สโค้ดสำหรับคลาสที่คุณพยายามยกเลิกการจัดกลุ่มจากนั้นอีกทางเลือกหนึ่งคือการสร้างJsonConverterแบบกำหนดเองเพื่อสร้างอินสแตนซ์และเติมวัตถุของคุณ ตัวอย่างเช่น:

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();
    }
}

จากนั้นเพิ่มตัวแปลงลงในการตั้งค่าซีเรียลไลเซอร์ของคุณและใช้การตั้งค่าเมื่อคุณทำการดีซีเรียลไลซ์:

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

4
สิ่งนี้ใช้ได้ผล มันแย่มากที่ตอนนี้ฉันต้องใช้การพึ่งพา JSON.net ในโปรเจ็กต์โมเดลของฉัน แต่สิ่งที่เฮ้ ฉันจะทำเครื่องหมายสิ่งนี้เป็นคำตอบ
kmacdonald

3
มีตัวเลือกอื่น ๆ - คุณสามารถสร้างที่กำหนดเองJsonConverterสำหรับชั้นเรียนของคุณ สิ่งนี้จะลบการอ้างอิง แต่คุณจะต้องจัดการการสร้างอินสแตนซ์และเติมวัตถุด้วยตนเองในตัวแปลง มันอาจเป็นไปได้ที่จะเขียน custom ContractResolverที่จะสั่ง Json.Net ให้ใช้ constructor ตัวอื่นโดยการเปลี่ยนมันJsonObjectContractแต่สิ่งนี้สามารถพิสูจน์ได้ว่ามันยากกว่าที่คิด
Brian Rogers

ใช่ฉันคิดว่าคุณสมบัติจะทำงานได้ดี การโทรแบบ deserialize เป็นแบบทั่วไปดังนั้นมันอาจเป็นวัตถุชนิดใดก็ได้ ฉันคิดว่าคำตอบดั้งเดิมของคุณจะใช้ได้ดี ขอบคุณสำหรับข้อมูล!
kmacdonald

2
มันจะช่วยได้จริงหรือไม่หากเป็นไปได้ที่จะกำหนดแบบแผนอื่นสำหรับการเลือกตัวสร้าง ตัวอย่างเช่นฉันคิดว่า Unity container รองรับสิ่งนี้ จากนั้นคุณสามารถสร้างมันขึ้นมาเพื่อให้มันเลือกตัวสร้างที่มีพารามิเตอร์ส่วนใหญ่อยู่เสมอแทนที่จะกลับไปเป็นค่าเริ่มต้น มีความเป็นไปได้ที่จะเกิดจุดขยายดังกล่าวใน Json.Net หรือไม่?
julealgon

1
อย่าลืมusing Newtonsoft.Json;
Bruno Bieri

36

สายไปหน่อยและไม่เหมาะกับที่นี่ แต่ฉันจะเพิ่มวิธีแก้ปัญหาของฉันที่นี่เพราะคำถามของฉันถูกปิดซ้ำกับคำถามนี้และเพราะวิธีนี้แตกต่างอย่างสิ้นเชิง

ฉันต้องการวิธีทั่วไปในการแนะนำJson.NETให้เลือกตัวสร้างที่เฉพาะเจาะจงมากที่สุดสำหรับประเภท struct ที่ผู้ใช้กำหนดดังนั้นฉันสามารถละเว้นJsonConstructorคุณลักษณะที่จะเพิ่มการพึ่งพาให้กับโครงการที่กำหนดโครงสร้างเช่นนั้น

ฉันได้ออกแบบวิศวกรรมย้อนกลับมาเล็กน้อยและปรับใช้ตัวแก้ไขสัญญาที่กำหนดเองโดยที่ฉันได้แทนที่CreateObjectContractวิธีการเพื่อเพิ่มตรรกะการสร้างที่กำหนดเองของฉัน

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);
    }
}

ฉันใช้มันแบบนี้

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
ฉันกำลังใช้คำตอบที่ได้รับการยอมรับด้านบน แต่ต้องการขอบคุณสำหรับการแสดงวิธีแก้ปัญหาของคุณเช่นกัน!
DotBert

1
ฉันได้ลบข้อ จำกัด ของ structs (การตรวจสอบobjectType.IsValueType) และใช้งานได้ดีขอบคุณ!
Alex Angas

@AlexAngas ใช่การใช้กลยุทธ์นี้โดยทั่วไปจะสมเหตุสมผลขอบคุณสำหรับความคิดเห็นของคุณ
ZoltánTamási

3

จากคำตอบบางส่วนที่นี่ฉันได้เขียนไว้CustomConstructorResolverสำหรับใช้ในโครงการปัจจุบันและฉันคิดว่ามันอาจช่วยคนอื่นได้

สนับสนุนกลไกการแก้ปัญหาต่อไปนี้ซึ่งสามารถกำหนดค่าได้ทั้งหมด:

  • เลือกตัวสร้างส่วนตัวเดียวเพื่อให้คุณสามารถกำหนดตัวสร้างส่วนตัวหนึ่งโดยไม่ต้องทำเครื่องหมายด้วยแอตทริบิวต์
  • เลือกคอนสตรัคเตอร์ส่วนตัวที่เฉพาะเจาะจงที่สุดเพื่อให้คุณสามารถมีโอเวอร์โหลดหลายตัวได้
  • เลือกสร้างที่มีเครื่องหมายแอตทริบิวต์ของชื่อเฉพาะ - การจำแนกเช่นเริ่มต้น แต่โดยไม่ต้องพึ่งพาแพคเกจ Json.Net 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;
    }
}

นี่คือรุ่นที่สมบูรณ์พร้อมเอกสาร XML เป็นส่วนสำคัญ: https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

ข้อเสนอแนะชื่นชม


สุดยอดทางออก! ขอบคุณสำหรับการแบ่งปัน.
thomai

1

พฤติกรรมเริ่มต้นของ Newtonsoft.Json กำลังจะหาตัวpublicสร้าง หากสร้างการเริ่มต้นของคุณจะใช้เฉพาะในที่มีชั้นเรียนหรือการชุมนุมเดียวกันคุณสามารถลดระดับการเข้าถึงไปprotectedหรือinternalเพื่อให้ Newtonsoft.Json จะเลือกที่คุณต้องการpublicคอนสตรัค

เป็นที่ยอมรับว่าโซลูชันนี้ค่อนข้าง จำกัด เฉพาะบางกรณีเท่านั้น

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

สารละลาย:

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;
}

รุ่น:

public class modelname {
    public long parameter{ get; set; }
    public int parameter{ get; set; }
    public int parameter{ get; set; }
    public string parameter{ get; set; }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.