다음을 통해 공유


ASP.NET Web API JSON 및 XML Serialization

이 문서에서는 ASP.NET Web API JSON 및 XML 포맷터에 대해 설명합니다.

ASP.NET Web API 미디어 형식 포맷터는 다음을 수행할 수 있는 개체입니다.

  • HTTP 메시지 본문에서 CLR 개체 읽기
  • HTTP 메시지 본문에 CLR 개체 쓰기

Web API는 JSON 및 XML 모두에 대한 미디어 형식 포맷터를 제공합니다. 프레임워크는 기본적으로 이러한 포맷터를 파이프라인에 삽입합니다. 클라이언트는 HTTP 요청의 Accept 헤더에서 JSON 또는 XML을 요청할 수 있습니다.

콘텐츠

JSON Media-Type 포맷터

JSON 형식 지정은 JsonMediaTypeFormatter 클래스에서 제공됩니다. 기본적으로 JsonMediaTypeFormatterJson.NET 라이브러리를 사용하여 serialization을 수행합니다. Json.NET 타사 오픈 소스 프로젝트입니다.

원하는 경우 Json.NET 대신 DataContractJsonSerializer를 사용하도록 JsonMediaTypeFormatter 클래스를 구성할 수 있습니다. 이렇게 하려면 UseDataContractJsonSerializer 속성을 true로 설정합니다.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;

JSON serialization

이 섹션에서는 기본 Json.NET 직렬 변환기를 사용하여 JSON 포맷터의 몇 가지 특정 동작에 대해 설명합니다. 이는 Json.NET 라이브러리에 대한 포괄적인 설명서가 아닙니다. 자세한 내용은 Json.NET 설명서를 참조하세요.

Serialize되는 항목은 무엇인가요?

기본적으로 모든 공용 속성 및 필드는 직렬화된 JSON에 포함됩니다. 속성 또는 필드를 생략하려면 JsonIgnore 특성으로 데코레이트합니다.

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    [JsonIgnore]
    public int ProductCode { get; set; } // omitted
}

"옵트인" 방법을 선호하는 경우 DataContract 특성으로 클래스를 데코레이트합니다. 이 특성이 있는 경우 멤버는 DataMember가 없는 한 무시됩니다. DataMember를 사용하여 프라이빗 멤버를 직렬화할 수도 있습니다.

[DataContract]
public class Product
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public decimal Price { get; set; }
    public int ProductCode { get; set; }  // omitted by default
}

Read-Only 속성

읽기 전용 속성은 기본적으로 직렬화됩니다.

날짜

기본적으로 Json.NET ISO 8601 형식으로 날짜를 씁니다. UTC(협정 세계시)의 날짜는 "Z" 접미사로 작성됩니다. 현지 시간의 날짜에는 표준 시간대 오프셋이 포함됩니다. 예:

2012-07-27T18:51:45.53403Z         // UTC
2012-07-27T11:51:45.53403-07:00    // Local

기본적으로 Json.NET 표준 시간대를 유지합니다. DateTimeZoneHandling 속성을 설정하여 이를 재정의할 수 있습니다.

// Convert all dates to UTC
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;

ISO 8601 대신 Microsoft JSON 날짜 형식 ("\/Date(ticks)\/")을 사용하려면 serializer 설정에서 DateFormatHandling 속성을 설정합니다.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.DateFormatHandling 
= Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

들여쓰기

들여쓰기된 JSON을 작성하려면 서식 설정을 Formatting.Indented로 설정합니다.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

낙타 대/소문자

데이터 모델을 변경하지 않고 카멜 대/소문자를 사용하여 JSON 속성 이름을 작성하려면 serializer에서 CamelCasePropertyNamesContractResolver 를 설정합니다.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

익명 및 Weakly-Typed 개체

작업 메서드는 익명 개체를 반환하고 JSON으로 serialize할 수 있습니다. 예:

public object Get()
{
    return new { 
        Name = "Alice", 
        Age = 23, 
        Pets = new List<string> { "Fido", "Polly", "Spot" } 
    };
}

응답 메시지 본문에는 다음 JSON이 포함됩니다.

{"Name":"Alice","Age":23,"Pets":["Fido","Polly","Spot"]}

웹 API가 클라이언트에서 느슨하게 구조화된 JSON 개체를 수신하는 경우 요청 본문을 Newtonsoft.Json.Linq.JObject 형식으로 역직렬화할 수 있습니다.

public void Post(JObject person)
{
    string name = person["Name"].ToString();
    int age = person["Age"].ToObject<int>();
}

그러나 일반적으로 강력한 형식의 데이터 개체를 사용하는 것이 좋습니다. 그런 다음 직접 데이터를 구문 분석할 필요가 없으며 모델 유효성 검사의 이점을 얻을 수 있습니다.

XML serializer는 익명 형식 또는 JObject 인스턴스를 지원하지 않습니다. JSON 데이터에 이러한 기능을 사용하는 경우 이 문서의 뒷부분에 설명된 대로 파이프라인에서 XML 포맷터를 제거해야 합니다.

XML Media-Type 포맷터

XML 서식 지정은 XmlMediaTypeFormatter 클래스에서 제공됩니다. 기본적으로 XmlMediaTypeFormatterDataContractSerializer 클래스를 사용하여 serialization을 수행합니다.

원하는 경우 DataContractSerializer 대신 XmlSerializer를 사용하도록 XmlMediaTypeFormatter를 구성할 수 있습니다. 이렇게 하려면 UseXmlSerializer 속성을 true로 설정합니다.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;

XmlSerializer 클래스는 DataContractSerializer보다 더 좁은 형식 집합을 지원하지만 결과 XML을 더 많이 제어합니다. 기존 XML 스키마와 일치해야 하는 경우 XmlSerializer 를 사용하는 것이 좋습니다.

XML Serialization

이 섹션에서는 기본 DataContractSerializer를 사용하는 XML 포맷터의 몇 가지 특정 동작에 대해 설명합니다.

기본적으로 DataContractSerializer는 다음과 같이 작동합니다.

  • 모든 공용 읽기/쓰기 속성 및 필드가 serialize됩니다. 속성 또는 필드를 생략하려면 IgnoreDataMember 특성으로 데코레이트합니다.
  • 프라이빗 및 보호된 멤버는 직렬화되지 않습니다.
  • 읽기 전용 속성은 serialize되지 않습니다. (그러나 읽기 전용 컬렉션 속성의 내용은 serialize됩니다.)
  • 클래스 및 멤버 이름은 클래스 선언에 표시되는 것과 똑같이 XML로 작성됩니다.
  • 기본 XML 네임스페이스가 사용됩니다.

serialization에 대한 더 많은 제어가 필요한 경우 DataContract 특성으로 클래스를 데코레이트할 수 있습니다. 이 특성이 있으면 클래스는 다음과 같이 직렬화됩니다.

  • "옵트인" 접근 방식: 속성 및 필드는 기본적으로 직렬화되지 않습니다. 속성 또는 필드를 serialize하려면 DataMember 특성으로 데코레이트합니다.
  • 프라이빗 또는 보호된 멤버를 직렬화하려면 DataMember 특성으로 데코레이트합니다.
  • 읽기 전용 속성은 serialize되지 않습니다.
  • 클래스 이름이 XML에 표시되는 방식을 변경하려면 DataContract 특성에서 Name 매개 변수를 설정합니다.
  • XML에 멤버 이름이 표시되는 방식을 변경하려면 DataMember 특성에서 Name 매개 변수를 설정합니다.
  • XML 네임스페이스를 변경하려면 DataContract 클래스에서 네임스페이스 매개 변수를 설정합니다.

Read-Only 속성

읽기 전용 속성은 serialize되지 않습니다. 읽기 전용 속성에 지원 프라이빗 필드가 있는 경우 DataMember 특성으로 프라이빗 필드를 표시할 수 있습니다. 이 방법을 사용하려면 클래스에 DataContract 특성이 필요합니다.

[DataContract]
public class Product
{
    [DataMember]
    private int pcode;  // serialized

    // Not serialized (read-only)
    public int ProductCode { get { return pcode; } }
}

날짜

날짜는 ISO 8601 형식으로 작성됩니다. 예: "2012-05-23T20:21:37.9116538Z".

들여쓰기

들여쓰기된 XML을 작성하려면 Indent 속성을 true로 설정합니다.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.Indent = true;

Per-Type XML 직렬 변환기 설정

다양한 CLR 형식에 대해 다른 XML 직렬 변환기를 설정할 수 있습니다. 예를 들어 이전 버전과의 호환성을 위해 XmlSerializer 가 필요한 특정 데이터 개체가 있을 수 있습니다. 이 개체 에 XmlSerializer 를 사용하고 다른 형식에 DataContractSerializer 를 계속 사용할 수 있습니다.

특정 형식에 대한 XML serializer를 설정하려면 SetSerializer를 호출합니다.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
// Use XmlSerializer for instances of type "Product":
xml.SetSerializer<Product>(new XmlSerializer(typeof(Product)));

XmlSerializer 또는 XmlObjectSerializer에서 파생된 개체를 지정할 수 있습니다.

JSON 또는 XML 포맷터 제거

사용하지 않으려면 포맷터 목록에서 JSON 포맷터 또는 XML 포맷터를 제거할 수 있습니다. 이 작업을 수행하는 기본 이유는 다음과 같습니다.

  • 웹 API 응답을 특정 미디어 형식으로 제한합니다. 예를 들어 JSON 응답만 지원하고 XML 포맷터를 제거하도록 결정할 수 있습니다.
  • 기본 포맷터를 사용자 지정 포맷터로 바꾸려면 예를 들어 JSON 포맷터를 JSON 포맷터의 사용자 지정 구현으로 바꿀 수 있습니다.

다음 코드에서는 기본 포맷터를 제거하는 방법을 보여 줍니다. Global.asax에 정의된 Application_Start 메서드에서 호출합니다.

void ConfigureApi(HttpConfiguration config)
{
    // Remove the JSON formatter
    config.Formatters.Remove(config.Formatters.JsonFormatter);

    // or

    // Remove the XML formatter
    config.Formatters.Remove(config.Formatters.XmlFormatter);
}

순환 개체 참조 처리

기본적으로 JSON 및 XML 포맷터는 모든 개체를 값으로 씁니다. 두 속성이 동일한 개체를 참조하거나 동일한 개체가 컬렉션에 두 번 표시되는 경우 포맷터는 개체를 두 번 직렬화합니다. 직렬 변환기가 그래프에서 루프를 검색할 때 예외를 throw하기 때문에 개체 그래프에 주기가 포함된 경우 이는 특히 문제가 됩니다.

다음 개체 모델 및 컨트롤러를 고려합니다.

public class Employee
{
    public string Name { get; set; }
    public Department Department { get; set; }
}

public class Department
{
    public string Name { get; set; }
    public Employee Manager { get; set; }
}

public class DepartmentsController : ApiController
{
    public Department Get(int id)
    {
        Department sales = new Department() { Name = "Sales" };
        Employee alice = new Employee() { Name = "Alice", Department = sales };
        sales.Manager = alice;
        return sales;
    }
}

이 작업을 호출하면 포맷터가 예외를 throw합니다. 이 예외는 클라이언트에 대한 상태 코드 500(내부 서버 오류) 응답으로 변환됩니다.

JSON에서 개체 참조를 유지하려면 Global.asax 파일의 Application_Start 메서드에 다음 코드를 추가합니다.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.All;

이제 컨트롤러 작업은 다음과 같은 JSON을 반환합니다.

{"$id":"1","Name":"Sales","Manager":{"$id":"2","Name":"Alice","Department":{"$ref":"1"}}}

serializer는 두 개체에 "$id" 속성을 추가합니다. 또한 Employee.Department 속성이 루프를 만드는 것을 감지하여 값을 개체 참조 {"$ref":"1"}로 바꿉니다.

참고

개체 참조는 JSON에서 표준이 아닙니다. 이 기능을 사용하기 전에 클라이언트가 결과를 구문 분석할 수 있는지 여부를 고려합니다. 단순히 그래프에서 주기를 제거하는 것이 더 좋을 수 있습니다. 예를 들어 이 예제에서는 Employee에서 부서로의 링크가 실제로 필요하지 않습니다.

XML에서 개체 참조를 유지하려면 두 가지 옵션이 있습니다. 더 간단한 옵션은 모델 클래스에 추가하는 [DataContract(IsReference=true)] 것입니다. IsReference 매개 변수는 개체 참조를 사용하도록 설정합니다. DataContract는 serialization 옵트인을 수행하므로 속성에 DataMember 특성도 추가해야 합니다.

[DataContract(IsReference=true)]
public class Department
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public Employee Manager { get; set; }
}

이제 포맷터는 다음과 유사한 XML을 생성합니다.

<Department xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="i1" 
            xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" 
            xmlns="http://schemas.datacontract.org/2004/07/Models">
  <Manager>
    <Department z:Ref="i1" />
    <Name>Alice</Name>
  </Manager>
  <Name>Sales</Name>
</Department>

모델 클래스의 특성을 방지하려면 다른 옵션이 있습니다. 새 형식별 DataContractSerializer instance 만들고 생성자에서 preserveObjectReferencestrue로 설정합니다. 그런 다음 이 instance XML 미디어 형식 포맷터에서 형식별 직렬 변환기로 설정합니다. 다음 코드는 이 작업을 수행하는 방법을 보여줍니다.

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Department), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ true, null);
xml.SetSerializer<Department>(dcs);

개체 직렬화 테스트

웹 API를 디자인할 때 데이터 개체를 직렬화하는 방법을 테스트하는 것이 유용합니다. 컨트롤러를 만들거나 컨트롤러 작업을 호출하지 않고 이 작업을 수행할 수 있습니다.

string Serialize<T>(MediaTypeFormatter formatter, T value)
{
    // Create a dummy HTTP Content.
    Stream stream = new MemoryStream();
    var content = new StreamContent(stream);
    /// Serialize the object.
    formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
    // Read the serialized string.
    stream.Position = 0;
    return content.ReadAsStringAsync().Result;
}

T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
    // Write the serialized string to a memory stream.
    Stream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    // Deserialize to an object of type T
    return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}

// Example of use
void TestSerialization()
{
    var value = new Person() { Name = "Alice", Age = 23 };

    var xml = new XmlMediaTypeFormatter();
    string str = Serialize(xml, value);

    var json = new JsonMediaTypeFormatter();
    str = Serialize(json, value);

    // Round trip
    Person person2 = Deserialize<Person>(json, str);
}