Xử lý tương tác JSON giữa các API Java và C # REST
Tôi hiện đang xử lý 2 hệ thống hiển thị tương tác thông qua API RESTful JSON của riêng chúng. Một trong C # với JSON.NET và một là Java Spring Boot Starter (Jackson JSON). Tôi có toàn quyền kiểm soát cả hai hệ thống.
Cả hai hệ thống cần chuyển dữ liệu JSON với xử lý tham chiếu. Trong khi cả hai khuôn khổ tuần tự hóa JSON đều hỗ trợ nó, C # JSON.NET sử dụng "$id"và "$ref"cú pháp để biểu thị các tham chiếu trong khi Jackson của Java chỉ sử dụng một cái gì đó rõ ràng "id".
Tôi ít quen thuộc với Java hơn tôi là C # vì vậy tôi sẽ dễ dàng chấp nhận và hiểu bất kỳ giải pháp nào về việc xử lý các tham chiếu JSON hoạt động theo cả hai cách ở phía C #. Làm cách nào để khiến hai hệ thống này tương tác với các tham chiếu JSON?
Tài liệu xử lý tham chiếu C # JSON.NET.
JSON mẫu đến từ Java Jackson
Lưu ý rằng có thể đánh dấu thuộc tính lớp nào mà Jackson sử dụng làm tham chiếu. Trong trường hợp này, tôi đang sử dụng Idbiến vì nó sẽ luôn duy nhất cục bộ cho kiểu.
{
"Resources": [
{
"Id": 0,
"Name": "Resource 0"
},
{
"Id": 1,
"Name": "Resource 1"
}
],
"Tasks": [
{
"Id": 0,
"Name": "Task 0",
"Resource": 0
},
{
"Id": 1,
"Name": "Task 1",
"Resource": 1
},
{
"Id": 2,
"Name": "Task 2",
"Resource": 0
},
{
"Id": 3,
"Name": "Task 3",
"Resource": 1
},
{
"Id": 4,
"Name": "Task 4",
"Resource": 0
}
]
}
Trả lời
Trước khi đọc , hãy xem cách tiếp cận giải pháp khác của tôi ở đây , nó có thể đơn giản hơn. Giữ bài đăng này cũng như tôi tin rằng nó có nhiều thông tin và có thể được một số người coi là một cách tiếp cận tốt hơn.
Tại sao điều này lại phức tạp?
Vấn đề không phải là tên thuộc tính tham chiếu mà bạn có thể sử dụng IReferenceResolverđể ghi đè. Thay vào đó, vấn đề có hai mặt:
Tham chiếu là từ một thuộc tính của một đối tượng trong
Tasksdanh sách đến một đối tượng trongResourcesdanh sách. Đây không phải là mục đích củaPreserveObjectReferencetính năng. Nó nhằm mục đích không lặp lại các đối tượng trong cùng một danh sách cũng như giúp ngăn chặn các tham chiếu theo chu kỳ.Giá trị trong thuộc
Resourcetính của aTasklà một số thay vì mộtResourceđối tượng (cách nào đó sẽ không hoạt động, do mục 1 ở trên), ví dụ:
{
"Id": 0,
"Name": "Task 0",
"Resource": {
"$ref": 0
}
}
- ID và tham chiếu phải là chuỗi, không phải số
Giải pháp
Xây dựng đối tượng theo cách thủ công và khớp các tham chiếu theo cách thủ công:
- DTO của chúng tôi:
public class Dto
{
public Resource[] Resources { get; set; }
public Task[] Tasks { get; set; }
}
public class Resource
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Task
{
public long Id { get; set; }
public string Name { get; set; }
public Resource Resource { get; set; }
}
- Giải quyết hợp đồng:
/// <summary>
/// This is to resolve the Resource resolver for the Task
/// </summary>
internal class TaskResourceContractResolver : DefaultContractResolver
{
private readonly IDictionary<long, Resource> _resources;
public TaskResourceContractResolver(IDictionary<long, Resource> resources) => this._resources = resources;
#region Overrides of DefaultContractResolver
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType != typeof(Task) || property.PropertyName != nameof(Task.Resource))
return property;
property.Converter = new TaskResourceConverter(this._resources);
property.IsReference = true;
property.ValueProvider = new CurrentValueGetterValueProvider();
return property;
}
#endregion Overrides of DefaultContractResolver
/// <summary>
/// This is to resolve the Resource for the Task
/// </summary>
private class TaskResourceConverter : JsonConverter<Task>
{
private readonly IDictionary<long, Resource> _resources;
public TaskResourceConverter(IDictionary<long, Resource> resources) => this._resources = resources;
#region Overrides of JsonConverter
public override void WriteJson(JsonWriter writer, Task value, JsonSerializer serializer) => throw new NotImplementedException();
public override Task ReadJson(JsonReader reader, Type objectType, Task existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.Value is Resource resource) existingValue.Resource = resource;
else if (reader.Value is long resourceRef)
{
if (!this._resources.TryGetValue(resourceRef, out resource)) throw new Exception($"Invalid resource reference '{resourceRef}'");
existingValue.Resource = resource;
}
else throw new Exception($"Invalid resource reference '{reader.Value}'");
return existingValue;
}
#endregion Overrides of JsonConverter
}
/// <summary>
/// This is so we get the value of Task object to be set
/// </summary>
private class CurrentValueGetterValueProvider : IValueProvider
{
#region Implementation of IValueProvider
public void SetValue(object target, object value) => throw new NotImplementedException();
public object GetValue(object target) => target;
#endregion Implementation of IValueProvider
}
}
- Thực hiện:
var input = Encoding.UTF8.GetString(Properties.Resources.input); // the posted Java-outputted JSON
var parsed = JObject.Parse(input);
var resources = parsed[nameof(Dto.Resources)]?.Children()
.Select(token => token.ToObject<Resource>())
.ToDictionary(r => r!.Id);
var serializer = new JsonSerializer() { ContractResolver = new TaskResourceContractResolver(resources) };
var dto = new Dto
{
Resources = resources?.Values.ToArray(),
Tasks = parsed[nameof(Dto.Tasks)]?.Children()
.Select(token => token.ToObject<Task>(serializer))
.ToArray()
};
Console.WriteLine($@"Distinct resources: {dto.Resources?.Distinct().Count()}");
Console.WriteLine($@"Distinct tasks: {dto.Tasks?.Distinct().Count()}"); Console.WriteLine($@"Distinct task resources: {dto.Tasks?.Select(t => t.Resource).Distinct().Count()}");
- Đầu ra:
Distinct resources: 2
Distinct tasks: 5
Distinct task resources: 2
.NET Fiddle
Có thể là một cách tiếp cận đơn giản hơn đối với những gì tôi đã xuất bản ở đây
Lý do thì giống nhau, nhưng giải pháp thì khác:
Sử dụng lớp chỉ tuần tự hóa tạm thời
(cái này có cả giải pháp tuần tự hóa và giải mã trên không)
- DTO giống nhau:
public class Dto
{
public Resource[] Resources { get; set; }
public Task[] Tasks { get; set; }
}
public class Resource
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Task
{
public long Id { get; set; }
public string Name { get; set; }
public Resource Resource { get; set; }
}
- Lớp chỉ tuần tự hóa tạm thời:
/// <summary>
/// Helper class for Dto serialization
/// </summary>
internal class DtoSerializationHelper
{
public Resource[] Resources { get; set; }
/// <summary>
/// To be used by application code (not for
/// </summary>
[JsonIgnore]
public Task[] Tasks { get; set; }
/// <summary>
/// Used by serializer
/// </summary>
[JsonProperty(nameof(Tasks))]
private TaskSerializationHelper[] SerializationTasks { get; set; }
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
var resourceLookup = this.Resources.ToDictionary(r => r.Id);
this.Tasks = this.SerializationTasks.Select(t => t.ToTask(resourceLookup)).ToArray();
}
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
this.SerializationTasks = this.Tasks?.Select(t => new TaskSerializationHelper(t)).ToArray();
}
/// <summary>
/// Converts from the helper to the Dto when casting
/// </summary>
/// <param name="helper"></param>
public static implicit operator Dto(DtoSerializationHelper helper) => new Dto
{
Resources = helper.Resources,
Tasks = helper.Tasks
};
/// <summary>
/// Converts from the Dto to the helper when casting
/// </summary>
/// <param name="dto"></param>
public static explicit operator DtoSerializationHelper(Dto dto) => new DtoSerializationHelper
{
Resources = dto.Resources,
Tasks = dto.Tasks
};
/// <summary>
/// A Task serialization helper class
/// </summary>
private class TaskSerializationHelper
{
public TaskSerializationHelper() { }
public TaskSerializationHelper(Task task) : this()
{
this.Id = task.Id;
this.Name = task.Name;
this.Resource = task.Resource.Id;
}
public long Id { get; set; }
public string Name { get; set; }
public long Resource { get; set; }
public Task ToTask(IDictionary<long, Resource> resourceLookup) =>
new Task
{
Id = this.Id,
Name = this.Name,
Resource = resourceLookup is null || !resourceLookup.TryGetValue(this.Resource, out var resource)
? throw new Exception($"Invalid resource {this.Resource}")
: resource
};
}
}
- Thực hiện:
var input = Encoding.UTF8.GetString(Properties.Resources.input); // the posted Java-outputted JSON
var dtoSerializationHelper = JsonConvert.DeserializeObject<DtoSerializationHelper>(input);
var dto = (Dto)dtoSerializationHelper;
var deserializationResults = new
{
distinctResources = dto.Resources?.Distinct().Count(),
distinctTasks = dto.Tasks?.Distinct().Count(),
distinctTaskResources = dto.Tasks?.Select(t => t.Resource).Distinct().Count()
};
Console.WriteLine($@"Distinct resources: {deserializationResults.distinctResources}");
Console.WriteLine($@"Distinct tasks: {deserializationResults.distinctTasks}"); Console.WriteLine($@"Distinct task resources: {deserializationResults.distinctTaskResources}");
if (deserializationResults.distinctResources != 2 ||
deserializationResults.distinctTasks != 5 ||
deserializationResults.distinctTaskResources != 2) throw new Exception("Deserialization failed");
Console.WriteLine();
var output = JsonConvert.SerializeObject((DtoSerializationHelper)dto);
var serializationResult = output == input;
Console.WriteLine($@"Input and output are same: {serializationResult}"); if (serializationResult) return; Console.WriteLine($@"Output: {output}");
throw new Exception("Serialization failed");
- Đầu ra:
Distinct resources: 2
Distinct tasks: 5
Distinct task resources: 2
Input and output are same: True
.NET Fiddle