json.net:如何将“相似但不同”的外部 JSON 结构反序列化为单个类

本文关键字:外部 相似但不同 JSON 结构 单个类 反序列化 net 相似 json | 更新日期: 2023-09-27 18:36:26

我正在寻找一种方法,最好使用 JSON.NET(使用最新版本),将多个外部 JSON 格式/结构反序列化为单个类。

简化示例(差异大于此,但不同的 JSON 包含类似的信息):

外部 1

{
    "id": 1234,
    "person": {
        "name": "john",
        "surname": "doe"           
    }
}

外部 2

{
    "ref": "1234",
    "firstName": "JOHN",
    "lastName": "DOE"
}

内部(这不是真的,只是为了展示)

{
    "tag": "1234",
    "name1": "John",
    "name2": "Doe"
}

是否有某种方法/库可能允许您使用 mapping.json 文件配置映射。最好是还允许格式化值等的。这些只是 2 个例子,但我们还有更多。

编辑:我们可以告诉/提示 JSON.NET 给定的 JSON 来自哪个来源。因此,我们不必有一个可以处理所有不同场景的模式/合约/解决方案。我们实际上更愿意为每个不同的外部 json 结构提供一个 .json 映射/转换配置文件(以使其面向未来,而无需重建所有内容)。

编辑 2:我现在所做的是基于Pavel Baravik的答案是遍历"模式/转换"JSON的所有属性。此 JSON 与我们要将原始外部 JSON 转换为的对象的最终 JSON 具有相同的结构。如果令牌的类型为"字符串",我们将解析该字符串(支持 {{ }} 和纯 =),并将其用作从原始外部 JSON 中提取值的路径。与此同时,正在构建最终的 JSON,之后将反序列化为我们的内部对象。
我认为我们可以通过使用表达式树"排序"编译来提高此代码的性能。

static void Main(string[] args)
{
    var schema = @"
    {
        ""person"": {                    
            ""name"": ""=test.firstName"",
            ""fullName"": ""{{test.firstName}} {{lastName}}"",
            ""surName"":  ""=lastName""
        }
    }";
    var json1 = @"
    {
        ""test"": {
            ""firstName"": ""John""
        },                
        ""lastName"": ""Doe"",
    }";
    Console.WriteLine(Transform(json1, schema).ToString());
    Console.ReadLine();
}
public static JObject Transform(string json, string schema)
{
    var j = JObject.Parse(json);
    var s = JObject.Parse(schema);
    var t = Transform(s, j);
    return t;
}
public static JObject Transform(JObject schema, JObject source)
{
    var target = new JObject();
    foreach (var child in schema.Children())
    {
        var property = child as JProperty;
        if (property != null)
        {
            var schemaToken = property.Value;
            var allowClone = true;
            JToken t = null;
            if (schemaToken.Type == JTokenType.Object)
            {
                t = Transform((JObject) schemaToken, source);
            }
            else if (schemaToken.Type == JTokenType.String)
            {
                allowClone = false;
                t = TransformProperty(source, (JValue)schemaToken);
            }
            if (t != null || allowClone)
            {
                target.Add(property.Name, (t ?? property).DeepClone());
            }
        }
    }
    return target;
}
private static readonly Regex MoustacheRegex = new Regex(@"'{'{[^'}]+'}'}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);
private static JToken TransformProperty(JObject source, JValue jstring)
{
    var str = (string)jstring.Value;
    JToken t = null;
    // if string starts with =
    if (str.StartsWith("="))
    {
        t = GetTokenByPath(source, str.Substring(1));
    }
    else
    {
        var allFound = true;
        str = MoustacheRegex.Replace(str, m =>
        {
            var mv = m.Value;
            var mt = GetTokenByPath(source, mv.Substring(2, mv.Length - 4));
            if (mt == null) allFound = false;
            return mt?.ToString() ?? string.Empty;
        });
        if (allFound)
            t = new JValue(str.Trim());
    }
    return t;
}
private static JToken GetTokenByPath(JObject source, string path)
{
    JToken t = null;
    var pathItems = path.Split('.');
    var s = source;
    for (var i = 0; i < pathItems.Length && s != null; ++i, s = t as JObject)
    {
        t = s[pathItems[i]];
    }
    return t;
}

编辑:(不错的JTransform类)

public class JTransform
{
    private InternalJTransform _internal;
    public void Load(string filePath)
    {
        using (var stream = File.OpenRead(filePath))
        using (var reader = new StreamReader(stream))
        {
            Load(new JsonTextReader(reader));
        }
    }
    public void Load(string filePath, Encoding encoding)
    {
        using (var stream = File.OpenRead(filePath))
        using (var reader = new StreamReader(stream, encoding))
        {
            Load(new JsonTextReader(reader));
        }
    }
    public void Load(JsonReader reader)
    {
        _internal = new InternalJTransform(reader);
    }
    public JObject Transform(JsonReader sourceReader)
    {
        return _internal.Transform(sourceReader);
    }
    public JObject Transform(JObject source)
    {
        return _internal.Transform(source);
    }
    public T TransformObject<T>(object obj)
    {
        return _internal.TransformObject<T>(obj);
    }
    public T TransformObject<T>(JObject source, JsonSerializer serializer = null)
    {
        return _internal.TransformObject<T>(source, serializer);
    }
    #region InternalJTransform
    private sealed class InternalJTransform
    {
        private static readonly Regex MoustacheRegex = new Regex(@"'{'{[^'}]+'}'}", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.Singleline);
        private JsonSerializer _serializer;
        private JObject _template;
        private bool _ignoreUndefined;
        public InternalJTransform(JsonReader reader)
        {
            var json = JObject.Load(reader);
            _template = json["template"] as JObject;
            _serializer = new JsonSerializer();
            var settings = json["settings"];
            if (settings["camelCase"]?.Value<bool>() ?? false)
                _serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
            if (settings["ignoreNull"]?.Value<bool>() ?? false)
                _serializer.NullValueHandling = NullValueHandling.Ignore;
            _ignoreUndefined = (settings["ignoreUndefined"]?.Value<bool>() ?? settings["ignoreNull"]?.Value<bool>() ?? false);
        }
        private void Load(JsonReader reader)
        {
            var json = JObject.Load(reader);
            var template = json["template"] as JObject;
            var serializer = new JsonSerializer();
            var settings = json["settings"];
            if (settings["camelCase"]?.Value<bool>() ?? false)
                serializer.ContractResolver = new CamelCasePropertyNamesContractResolver();
            if (settings["ignoreNull"]?.Value<bool>() ?? false)
                serializer.NullValueHandling = NullValueHandling.Ignore;
            _ignoreUndefined = (settings["ignoreNull"]?.Value<bool>() ?? false);
            _serializer = serializer;
            _template = template;
        }
        public JObject Transform(JsonReader sourceReader)
        {
            var obj = JObject.Load(sourceReader);
            return TransformInternal(_template, obj, _serializer);
        }
        public JObject Transform(JObject source)
        {
            return TransformInternal(_template, source, _serializer);
        }
        public T TransformObject<T>(object obj)
        {
            var source = JObject.FromObject(obj);
            var im = TransformInternal(_template, source, _serializer);
            return im.ToObject<T>(_serializer);
        }
        public T TransformObject<T>(JObject source, JsonSerializer serializer = null)
        {
            var obj = TransformInternal(_template, source, _serializer);
            return obj.ToObject<T>(serializer ?? _serializer);
        }
        private JObject TransformInternal(JObject template, JObject source, JsonSerializer serializer)
        {
            var ignoreNull = serializer.NullValueHandling == NullValueHandling.Ignore;
            var target = new JObject();
            foreach (var property in template.Properties())
            {
                var token = property.Value;
                if (token.Type == JTokenType.Object)
                {
                    token = TransformInternal((JObject)token, source, serializer);
                }
                else if (token.Type == JTokenType.String)
                {
                    token = TransformStringToken(source, (JValue)token);
                    // handle undefined, not found, values
                    if (token == null && _ignoreUndefined) continue;
                }
                // handle real null values (this does not include null values set in the template)
                if (token != null && token.Type == JTokenType.Null && ignoreNull) continue;
                target.Add(property.Name, token?.DeepClone());
            }
            return target;
        }
        private JToken TransformStringToken(JObject source, JValue jstring)
        {
            var str = (string)jstring.Value;
            JToken t = null;
            // if string starts with =
            if (str.StartsWith("="))
            {
                t = GetTokenByPath(source, str.Substring(1));
            }
            else
            {
                var allFound = true;
                str = MoustacheRegex.Replace(str, m =>
                {
                    var mv = m.Value;
                    var mt = GetTokenByPath(source, mv.Substring(2, mv.Length - 4));
                    if (mt == null) allFound = false;
                    return mt?.ToString() ?? string.Empty;
                });
                if (allFound)
                    t = new JValue(str.Trim());
            }
            return t;
        }
        private static JToken GetTokenByPath(JObject source, string path)
        {
            JToken t = null;
            var pathItems = path.Split('.');
            var s = source;
            for (var i = 0; i < pathItems.Length && s != null; ++i, s = t as JObject)
            {
                t = s[pathItems[i]];
            }
            return t;
        }
    }
    #endregion
}

json.net:如何将“相似但不同”的外部 JSON 结构反序列化为单个类

您可以首先使用 JsonReader "展平"输入结构,然后映射到单个类(从反序列化特定属性 JSON.NET 采用)。

void Main()
{
    var json0 = @"{
    ""id"": 1234,
    ""person"": {
        ""name"": ""john"",
        ""surname"": ""doe""           
    }";
    var json1 = @"  {
    ""ref"": ""1234"",
    ""firstName"": ""JOHN"",
    ""lastName"": ""DOE""
    }";
    foreach (var j in new []{json0, json1})
    {
        var name = GetFirstInstance<string>(new [] {"person.name", "firstName", "name1"}, j);
        var surname = GetFirstInstance<string> (new [] {"person.surname", "lastName", "name2"}, j);
        new {name, surname}.Dump();
    }
}
public T GetFirstInstance<T>(string[] path, string json)
{
    using (var stringReader = new StringReader(json))
    using (var jsonReader = new JsonTextReader(stringReader))
    {
        while (jsonReader.Read())
        {
            if (jsonReader.TokenType == JsonToken.PropertyName  && path.Contains((string)jsonReader.Path))
            {
                jsonReader.Read();
                var serializer = new JsonSerializer();
                return serializer.Deserialize<T>(jsonReader);
            }
        }
        return default(T);
    }
}

看看 CustomCreationConverter 在 JSON.NET http://www.newtonsoft.com/json/help/html/CustomCreationConverter.htm 您可以创建不同的转换器,并根据您拥有的 JSON 决定使用哪一个。 他们都可以输出相同的类