有选择地将实例的属性序列化为JSON

时间:2017-05-30 11:41:59

标签: c# json.net

我有一些JSON文件包含一些奇怪的格式。我必须为我的项目阅读,修改和保存这种格式。不幸的是,我对给定的格式没有任何影响。

这种格式的奇怪之处在于,此结构中的所有实体都具有唯一标识符,该标识符(无效地)称为“$ id”。这些实例也可以通过它们的id来引用,而不是它们的完整属性集。在这种情况下,没有“$ id”,而是有一个“$ ref”字段,包含已定义的实体。请看下面的(缩短的)示例:

{
    "$id": "1",
    "StyleName": "Standard",
    "Style": {
        "$id": "2",
        "ShapeStyle": {
            "$id": "3",
            "Background": {
                "$id": "4",
                "Color": {
                    "$id": "5",
                    "A": 255,
                    "R": 68,
                    "G": 84,
                    "B": 106
                }
            }
        },
        "RightEndCapsStyle": {
            "$id": "6",
            "Background": {
                "$id": "7",
                "Color": {
                    "$ref": "5"
                }
            }
        }
    }
}

为了解决这个问题,我为所有JSON数据类创建了一个C#基类 Entity 。此基类维护一个id注册表,以便它可以轻松找到给定 $ ref 的实例。这是代码:

public class Entity
{
    public static Dictionary<string, Entity> Registry = new Dictionary<string, Entity>();

    private string key = string.Empty;

    [JsonProperty("$id", Order = -2)]
    [DefaultValue("")]
    public string Key
    {
        get { return key; }
        set
        {
            key = value;
            if (!string.IsNullOrEmpty(key)) Registry.Add(key, this);
        }
    }

    [JsonProperty("$ref")]
    public string RefKey { get; set; }

    [JsonIgnore]
    public bool IsReference => !string.IsNullOrEmpty(RefKey);

    [JsonIgnore]
    public Entity RefEntity
    {
        get
        {
            Entity entity = null;
            if (IsReference && !Registry.TryGetValue(RefKey, out entity))
            {
                throw new ApplicationException("Referenced entity not found!");
            }
            return entity;
        }
    }
}

JSON类层次结构如下所示:

public class RootObject: Entity
{
    public string StyleName { get; set; }
    public StyleRoot Style { get; set; }
}

public class StyleRoot: Entity
{
    public Style ShapeStyle { get; set; }
    public Style RightEndCapsStyle { get; set; }
}

public class Style: Entity
{
    public Background Background { get; set; }
}

public class Background: Entity
{
    public Color Color { get; set; }
}

public class Color: Entity
{
    public int A { get; set; }
    public int R { get; set; }
    public int G { get; set; }
    public int B { get; set; }
}

到目前为止,这么好。现在问我的问题:

  1. [JsonProperty("$id")][JsonProperty("$ref")]不起作用,因为 $ id $ ref 不是有效的JSON字段名称标识符。目前,我正在使用正则表达式将它们替换为有效的东西,然后将它们重新替换回原来的状态。但这很迟钝。我想知道我是否可以使用NamingStrategy来实现同样的目标。但我没有成功。请记住,我需要两种方式,反序列化和序列化。
  2. 序列化是一个更难的问题。如果我按原样序列化我的结构,我最终会在每个实例中同时使用"$id":"$ref":字段。更重要的是,与引用的项目一起,所有其他字段都使用默认值序列化。最简单的方法是在DefaultValueHandling.IgnoreAndPopulate中设置JsonSerializerSettings。但是这样,所有默认值都会从输出中删除,从而导致意外行为。我尝试使用ContractResolver但失败了,因为这是当前属性的本地属性,并且无法考虑周围的实体。有没有其他方法可以实现自定义序列化策略?
  3. 我知道很难的问题。还有很多东西需要阅读和理解。但如果它很容易,我就不会把精力放在这里。迫切需要你的帮助。

1 个答案:

答案 0 :(得分:1)

启用PreserveReferencesHandling mode时,Json.NET会在内部使用这些$id$ref。您无需关心它们,只需定义RootObjectStyleRoot等类,通常不使用Entity基类,并在{{1}中设置PreserveReferencesHandling模式}}。 Json.NET将处理引擎盖下的JsonSerializerSettings$id并创建一个保留原始引用结构的对象网络:

$ref

序列化也可以在保留引用处理模式下完成:

var obj = JsonConvert.DeserializeObject<RootObject>(json,
    new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All });

演示:https://dotnetfiddle.net/TygMDZ

N.B。在输入数据示例中,var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }); 属性包含数值,而Json.NET写入并期望"$ref"属性具有字符串值。可能是因为你在一些实验过程中不小心发布了JSON文本吗?