728x90
google.protobuf.Timestamp는 Protobuf에서 날짜 및 시간을 다룰 때 사용하는 표준 타입입니다. Protobuf 메시지를 JSON으로 직렬화하거나 JSON에서 역직렬화(Deserialize)할 때, 타임스탬프(TimeStamp) 처리가 까다로울 수 있습니다. 이 글에서는 ProtoBuf에서의 Timestamp 사용법과 C#에서 JSON 직렬화/역직렬화 처리 방법을 설명합니다.
1. Proto 파일에서 Timestamp 사용 설정
1.1 Timestamp를 사용하기 위한 Proto 설정
Protobuf에서 google.protobuf.Timestamp를 사용하려면 먼저 다음과 같이 import를 선언해야 합니다.
syntax = "proto3";
import "google/protobuf/timestamp.proto";
message Event {
string name = 1;
google.protobuf.Timestamp event_time = 2;
}
2. C#에서 Timestamp 사용하기
Protobuf 메시지를 C#에서 사용할 때, Google.Protobuf.WellKnownTypes.Timestamp 타입을 사용합니다. C#에서 Timestamp 값을 올바르게 설정하려면 UTC 시간을 기준으로 처리해야 합니다.
using Google.Protobuf.WellKnownTypes;
using System;
class Program
{
static void Main()
{
// 현재 시간을 UTC로 설정
Timestamp timestamp = Timestamp.FromDateTime(DateTime.UtcNow);
Console.WriteLine($"Timestamp (Seconds): {timestamp.Seconds}");
Console.WriteLine($"Timestamp (Nanoseconds): {timestamp.Nanos}");
Console.WriteLine($"Timestamp (DateTime): {timestamp.ToDateTime()}");
}
}
3. JSON 역직렬화 문제 발생 원인
C#에서 JSON을 역직렬화할 때 오류가 발생할 수 있는 이유는 다음과 같습니다:
- 시간 형식 불일치
- JSON에서 제공하는 시간이 ISO 8601 형식을 따르지 않거나, 밀리초/나노초의 자리수가 다를 경우 역직렬화에 실패할 수 있습니다.
- 예: 2025-01-20T23:22:33.fffZ → 올바른 형식이 아님.
- Protobuf 역직렬화 제한
- Protobuf의 Timestamp는 엄격한 형식을 따르므로, JSON 입력값이 정확한 포맷이 아닐 경우 예외가 발생합니다.
4. C#에서 JSON 직렬화/역직렬화 해결 방법
해결 방법 JsonConverter를 사용한 직렬화 및 역직렬화
C#에서 JSON을 Protobuf Timestamp로 변환하기 위해 커스텀 컨버터를 적용할 수 있습니다.
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using System.Reflection;
public class TimeStampContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(Google.Protobuf.WellKnownTypes.Timestamp))
{
property.Converter = new TimeStampConverter();
}
return property;
}
public class TimeStampConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime date = DateTime.Parse(reader.Value.ToString());
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
}
}
}
사용방법
var settings = new JsonSerializerSettings
{
ContractResolver = new TimeStampContractResolver()
};
var myObj = JsonConvert.DeserializeObject<MyObject>(jsonString, settings);
4. 응용
Loader.cs
public interface ILoader { }
public class ChatLogLoader : ILoader
{
public List<ChatObject> ChatLogLoaders = new List<ChatObject>();
}
DataManager.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text;
using System.Text.RegularExpressions;
public class DataManager
{
public static void LogData()
{
//최초 로드하고 싶은게 있다면
}
// Loader 인터페이스 사용하는데 Json파일에 Loader이름 있을 경우
public static Loader LoadJson<Loader>(string path) where Loader : ILoader, new()
{
string jsonString = ReadJsonFile(path);
if (string.IsNullOrEmpty(jsonString))
return new Loader();
if (IsValidJson(jsonString) == false)
{
string propertyName = typeof(Loader).GetFields().FirstOrDefault()?.Name ??
throw new Exception("No properties found in Loader class.");
jsonString = ConvertToJson(jsonString, $"{propertyName}");
}
//protobuf TimeStamp 파싱을 위한 커스텀
var settings = new JsonSerializerSettings
{
ContractResolver = new TimeStampContractResolver()
};
return JsonConvert.DeserializeObject<Loader>(jsonString, settings);
}
public static bool IsValidJson(string jsonString)
{
// JSON 형식이 유무
try
{
JToken.Parse(jsonString);
return true;
}
catch (JsonReaderException)
{
return false;
}
}
static string ReadJsonFile(string path)
{
// JSON 파일 유무
string filePath = $"{ConfigManager.Config.logPath}/{path}.json";
if (!File.Exists(filePath))
return string.Empty;
string jsonString;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader reader = new StreamReader(fs))
{
jsonString = reader.ReadToEnd();
}
return jsonString;
}
private static string ConvertToJson(string jsonString, string objectName)
{
//json 형식이 아니라면 Loader의 첫 필드와 파싱시킴
string pattern = @"\{[^}]+\}";
MatchCollection matches = Regex.Matches(jsonString, pattern);
StringBuilder sb = new StringBuilder();
sb.Append($"{{\"{objectName}\":[");
for (int i = 0; i < matches.Count; i++)
{
sb.Append(matches[i].Value);
if (i < matches.Count - 1)
{
sb.Append(",");
}
}
return sb.Append("]}").ToString();
}
}
TimeStampContractResolver.CS
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using System.Reflection;
public class TimeStampContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType == typeof(Google.Protobuf.WellKnownTypes.Timestamp))
{
property.Converter = new TimeStampConverter();
}
return property;
}
public class TimeStampConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DateTime date = DateTime.Parse(reader.Value.ToString());
date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
return Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(date);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((Google.Protobuf.WellKnownTypes.Timestamp)value).ToString());
}
}
}
참조 - https://stackoverflow.com/questions/39348238/google-protobuff-timestamp-proto-in-c-sharp
728x90
'VisualStudio > C#서버' 카테고리의 다른 글
[C#서버] Akka.net과 Cluster Part.5 적용해보자! (0) | 2025.01.21 |
---|---|
[C#서버] Protobuf사용 방법 및 빌드 이벤트와 .bat (0) | 2025.01.09 |
[C#서버] Akka.net과 Cluster Part.4 (3) | 2024.10.08 |
[C#서버] Akka.net과 Actor모델 Part.3 (3) | 2024.09.30 |
[C#서버] Akka.net과 Actor모델 Part.2 (0) | 2024.09.13 |