Де/сериализация сложного вложенного json в c#
Есть json подобного вида:
{ [object, “AND”, [object, “OR”, object]], “OR”, object }
“AND”, “OR” etc. - это обычные string’и, которые воспринимаются сервером аналогично операторам в sql запросе, например.
Вопрос в том как элегантнее сериализовать и десериализовать такой объект? И возможно ли это? Может можно написать кастомный сериализатор и если да, то в каком виде передавать ему этот объект для парсинга? Есть ли варианты, кроме обычного string’a? Хотелось бы видеть что-то типа (Object1.And(Object2.Or(Object3))).Or(Object4). Есть ли какие-то возможности?
Конкретный пример json:
"jsonObject":[
[ { "name":"Name1", "value":"1" }, "AND", { "name":"Name2", "value":"2" } ],
"OR",
[ { "name":"Name3", "value":"3" }, "AND", { "name":"Name4", "value":"4" } ]
}
Ответы (1 шт):
System.Text.Json позволяет регистрировать свои собственные конвертеры для определенного класса. Посмотреть примеры можно здесь: How to write custom converters for JSON serialization (marshalling) in .NET.
Я не претендую на то что правильно понял вашу задачу, но пример решения возможно не помешает.
Объекты:
[JsonConverter(typeof(MyJsonConverter))]
public abstract class MyObjectBase {
public static MyObject Create(string name, string value) => new MyObject() { Name = name, Value = value };
public abstract MyObjectGroup And(string name, string value);
public abstract MyObjectGroup Or(string name, string value);
}
public class MyObject : MyObjectBase {
public string Name { get; set; }
public string Value { get; set; }
public override MyObjectGroup And(string name, string value) {
MyObjectGroup grp = new MyObjectGroup();
grp.GroupType = MyObjectGroupType.And;
grp.Items.Add(this);
grp.Items.Add(Create(name, value));
return grp;
}
public override MyObjectGroup Or(string name, string value) {
MyObjectGroup grp = new MyObjectGroup();
grp.GroupType = MyObjectGroupType.And;
grp.Items.Add(this);
grp.Items.Add(Create(name, value));
return grp;
}
}
public class MyObjectGroup : MyObjectBase {
public MyObjectGroupType GroupType { get; set; }
public IList<MyObjectBase> Items { get; } = new List<MyObjectBase>();
public override MyObjectGroup And(string name, string value) {
if(GroupType == MyObjectGroupType.And) {
Items.Add(Create(name, value));
return this;
}
MyObjectGroup grp = new MyObjectGroup();
grp.GroupType = MyObjectGroupType.And;
grp.Items.Add(this);
grp.Items.Add(Create(name, value));
return grp;
}
public override MyObjectGroup Or(string name, string value) {
if(GroupType == MyObjectGroupType.Or) {
Items.Add(Create(name, value));
return this;
}
MyObjectGroup grp = new MyObjectGroup();
grp.GroupType = MyObjectGroupType.Or;
grp.Items.Add(this);
grp.Items.Add(Create(name, value));
return grp;
}
}
public enum MyObjectGroupType { And, Or }
Конвертер:
public class MyJsonConverter : JsonConverter<MyObjectBase> {
public override MyObjectBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
switch(reader.TokenType) {
case JsonTokenType.StartArray:
return ReadGroup(ref reader);
case JsonTokenType.StartObject:
return ReadObject(ref reader);
default:
throw new JsonException($"Unexpected token type '{reader.TokenType}'. 'StartArra' or 'StartObject' is expected.");
}
}
static MyObject ReadObject(ref Utf8JsonReader reader) {
if(reader.TokenType != JsonTokenType.StartObject)
throw new JsonException($"Unexpected token: '{reader.TokenType}'. 'StartObject' is expected.");
string name = string.Empty;
string value = string.Empty;
while(reader.Read()) {
if(reader.TokenType == JsonTokenType.EndObject)
return MyObjectBase.Create(name, value);
if(reader.TokenType == JsonTokenType.PropertyName) {
string propertyName = reader.GetString();
reader.Read();
switch(propertyName) {
case nameof(MyObject.Name):
name = reader.GetString();
break;
case nameof(MyObject.Value):
value = reader.GetString();
break;
default:
throw new JsonException($"Unknown property: '{propertyName}'.");
}
}
}
throw new JsonException();
}
static MyObjectGroup ReadGroup(ref Utf8JsonReader reader) {
if(reader.TokenType != JsonTokenType.StartArray)
throw new JsonException($"Unexpected token '{reader.TokenType}'. 'StartArray' is expected.");
MyObjectGroup result = new MyObjectGroup();
while(reader.Read()) {
if(reader.TokenType == JsonTokenType.EndArray)
return result;
if(reader.TokenType == JsonTokenType.StartArray)
result.Items.Add(ReadGroup(ref reader));
else if(reader.TokenType == JsonTokenType.StartObject)
result.Items.Add(ReadObject(ref reader));
else if(reader.TokenType == JsonTokenType.String) {
string value = reader.GetString();
MyObjectGroupType groupType;
if(Enum.TryParse<MyObjectGroupType>(value, out groupType))
result.GroupType = groupType;
else
throw new JsonException($"Unexpected keyword '{value}'. 'AND' or 'OR' is expected.");
}
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, MyObjectBase value, JsonSerializerOptions options) {
if(value is MyObject obj)
WriteObject(writer, obj);
else if(value is MyObjectGroup grp)
WriteGroup(writer, grp, options);
else
throw new JsonException();
}
static void WriteObject(Utf8JsonWriter writer, MyObject value) {
writer.WriteStartObject();
writer.WritePropertyName(nameof(MyObject.Name));
writer.WriteStringValue(value.Name);
writer.WritePropertyName(nameof(MyObject.Value));
writer.WriteStringValue(value.Value);
writer.WriteEndObject();
}
static void WriteGroup(Utf8JsonWriter writer, MyObjectGroup value, JsonSerializerOptions options) {
writer.WriteStartArray();
for(int i = 0; i < value.Items.Count; i ++) {
MyObjectBase item = value.Items[i];
JsonSerializer.Serialize(writer, item, typeof(MyObjectBase), options);
if(i < value.Items.Count - 1)
writer.WriteStringValue(value.GroupType.ToString());
}
writer.WriteEndArray();
}
}
Пример использования:
var obj = MyObjectBase.Create("Name1", "1")
.And("Name2", "2")
.Or("Name3", "3")
.Or("Name4", "4");
string json = JsonSerializer.Serialize<MyObjectBase>(obj);
var obj2 = JsonSerializer.Deserialize<MyObjectBase>(json);