JSON.net : 기본 생성자를 사용하지 않고 직렬화 해제하는 방법?


136

기본 생성자가있는 클래스와 매개 변수 집합을 사용하는 오버로드 된 생성자가 있습니다. 이 매개 변수는 객체의 필드와 일치하며 구성시 지정됩니다. 이 시점에서 다른 목적을 위해 기본 생성자가 필요하므로 가능하면 유지하고 싶습니다.

내 문제 : 기본 생성자를 제거하고 JSON 문자열을 전달하면 객체가 올바르게 deserialize되고 아무런 문제없이 생성자 매개 변수를 전달합니다. 예상대로 개체를 다시 채우게됩니다. 그러나 기본 생성자를 객체에 추가하자마자 JsonConvert.DeserializeObject<Result>(jsontext)속성을 호출 하면 더 이상 채워지지 않습니다.

이 시점 new JsonSerializerSettings(){CheckAdditionalContent = true}에서 deserialization 호출에 추가하려고했습니다 . 그것은 아무것도하지 않았다.

또 다른 메모. 구성자 매개 변수는 매개 변수가 소문자로 시작된다는 점을 제외하고 필드 이름과 정확히 일치합니다. 앞에서 언급했듯이 deserialization이 기본 생성자없이 제대로 작동하기 때문에 이것이 중요하지 않다고 생각합니다.

다음은 생성자의 샘플입니다.

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


답변:


208

Json.Net은 객체에 기본 (매개 변수없는) 생성자를 사용하는 것을 선호합니다. 여러 생성자가 있고 Json.Net이 기본이 아닌 것을 사용하도록하려면 [JsonConstructor]Json.Net이 호출 할 생성자에 속성을 추가 할 수 있습니다 .

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

이 매개 변수가 올바르게 작동하려면 생성자 매개 변수 이름이 JSON 오브젝트의 해당 특성 이름과 일치해야합니다 (대소 문자 무시). 그러나 객체의 모든 속성에 대해 생성자 매개 변수를 반드시 가질 필요는 없습니다. 생성자 매개 변수로 다루지 않는 JSON 객체 속성의 경우 Json.Net은 공용 속성 접근 자 (또는로 표시된 속성 / 필드 [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. 수업에 대한 사용자 정의 를 만들 수 있습니다 . 그러면 종속성이 제거되지만 변환기에서 직접 객체 인스턴스화 및 채우기를 처리해야합니다. ContractResolverJson.Net이을 변경하여 다른 생성자를 사용하도록 지시 하는 사용자 정의를 작성할 JsonObjectContract수도 있지만 이는 소리보다 약간 까다로울 수 있습니다 .
Brian Rogers

예, 속성이 제대로 작동한다고 생각합니다. deserialize 호출은 실제로 제네릭이므로 모든 유형의 객체가 될 수 있습니다. 나는 당신의 원래 대답이 잘 작동 할 것이라고 생각합니다. 정보 주셔서 감사합니다!
kmacdonald

2
생성자 선택에 다른 규칙을 설정할 수 있다면 정말 도움이 될 것입니다. 예를 들어 Unity 컨테이너가이를 지원한다고 생각합니다. 그런 다음 항상 기본 매개 변수로 돌아 가지 않고 대부분의 매개 변수가있는 생성자를 선택하도록 만들 수 있습니다. 그러한 확장 점이 Json.Net에 존재할 가능성이 있습니까?
julealgon

1
잊지 마세요using Newtonsoft.Json;
Bruno Bieri

36

조금 늦었고 여기에 정확히 맞지 않지만 여기에 내 질문 이 복제되어 닫히고이 솔루션이 완전히 다르기 때문에 여기에 솔루션을 추가 할 것 입니다.

Json.NET사용자 정의 구조체 유형에 대해 가장 구체적인 생성자를 선호 하도록 지시하는 일반적인 방법이 필요 했기 때문에 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
구조체에 대한 제한을 제거하고 (확인 objectType.IsValueType)이 기능이 훌륭합니다. 감사합니다!
Alex Angas

@AlexAngas이 전략을 일반적으로 적용하는 것은 귀하의 의견에 감사드립니다.
Zoltán Tamá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생성자 를 찾습니다 . 기본 생성자가 클래스 또는 동일한 어셈블리를 포함하는 데만 사용되는 경우 Newtonsoft.Json이 원하는 생성자를 선택하도록 액세스 수준을 낮추 protected거나 줄일 수 있습니다 .internalpublic

물론이 솔루션은 특정 사례로 제한되어 있습니다.

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.