将JSON字符串初始化为具有抽象类型的对象

mtb9vblg  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(151)

我试图将一个复杂的嵌套json转换为我的对象。唯一的问题是,在我的对象中,我使用抽象类型,所以它必须有一些逻辑来使用正确的派生类。
类型保存在枚举中。
为了解释它,我将保持它相当简单,我们只嵌套一次,但为了我的目的,它更多地嵌套对象和类型。

JSON

{
   "screen":{
      "type":"Component",
      "footer":{
         "type":"Bar"
      },
      "header":{
         "type":"Top"
      }
   }
}

字符串

public abstract class Screen
{
    public abstract ScreenType Type { get; }
}

public enum ScreenType
{
    Component,
    b,
    c,
    d,
    e
}

public sealed class ComponentScreen : Screen
{
    public override ScreenType Type => ScreenType.Component;
    public Header? Header { get; init; }
    public Footer? Footer { get; init; }
    public bool? ShowStuff {get; init; }

}

public abstract class Header : ITyped<HeaderType>
{
    public abstract HeaderType Type { get; }
}

public enum HeaderType
{
    Top,
    b,
    c,
    d
}

public sealed class TopScreenHeader : Header
{
    public override HeaderType Type => HeaderType.Top;
    public string MyStuff { get; }
}


不可能只改变所有的抽象类型,或者编写转换器,因为有多个抽象类型的X次派生对象。JSON也不一致。

我当前使用newtonsoft的代码

var screen = JsonConvert.DeserializeObject<Screen>(jsonString, new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.Objects,
    ContractResolver = new CamelCasePropertyNamesContractResolver()

}


这不工作,并给出错误:

Could not create an instance of type Screens.Screen. Type is an interace or abstract class and cannot be instantiated. Path 'screen', line 1, position 10.

6tqwzwtp

6tqwzwtp1#

这不是一个直接的答案,但如果你正在考虑迁移到.NET 7+和System.Text.Json,你可以使用System.Text.Json的能力来处理类型层次结构:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(ComponentScreen),"Component")]
// all other Screen types ...
public abstract class Screen
{
    public abstract ScreenType Type { get; }
}

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(TopScreenHeader), "Top")]
// all other Header types ...
public abstract class Header : ITyped<HeaderType>
{
    public abstract HeaderType Type { get; }
}

字符串
请注意,你的json并不代表屏幕,而是一些屏幕保持器类型,所以:

class ScreenHolder
{
    public Screen Screen { get; set; }
}


和用法(对于footer,我使用了简单类):

var js = """
{
   "screen":{
      "type":"Component",
      "footer":{
         "type":"Bar"
      },
      "header":{
         "type":"Top"
      }
   }
}
""";

var deserialize = JsonSerializer.Deserialize<ScreenHolder>(js, new JsonSerializerOptions{PropertyNameCaseInsensitive = true});


对于Newtonsoft Json.NET,有以下选项:

还请注意,使用TypeNameHandling与其他选项以外的None和没有正确的SerializationBindercan lead to application vulnerabilities

fiei3ece

fiei3ece2#

你在json转换代码中有一个bug,你需要添加一个根类。你还需要在你的json字符串中包含一个类名来指向要使用的类。为了更容易地为所有抽象类创建一个基类

public class Root
{
    public ComponentScreen Screen { get; set; }
}
public class DataBase
{
    public string ClassName { get { return this.GetType().Name; } }
}
public abstract class Footer : DataBase
{
    public abstract HeaderType Type { get; }
}

public abstract class Header : DataBase
{
    public abstract HeaderType Type { get; }
}

public sealed class ComponentScreen : Screen
{
    public override ScreenType Type => ScreenType.Component;
    [JsonConverter(typeof( AbstractToConcretConverter))]
    public Header? Header { get; init; }
    [JsonConverter(typeof( AbstractToConcretConverter))]
    public Footer? Footer { get; init; }
    public bool? ShowStuff { get; init; }

}

字符串
它将创建一个这样的JSON字符串

{
   "screen":{
      "type":"Component",
      "footer":{
         "type":"Bar",
     "ClassName":"ScreenFooter"
      },
      "header":{
         "type":"Top",
      "ClassName":"TopScreenHeader"
      }
   }
}


你可以使用这个转换器从基类转换到具体类

public class AbstractToConcretConverter : JsonConverter<DataBase>
{
    public override DataBase ReadJson(JsonReader reader, Type objectType, DataBase existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var jObj = JObject.Load(reader);
        return (DataBase)jObj.ToObject(Type.GetType((string)jObj["ClassName"]));
    }

    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, DataBase value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

irlmq6kh

irlmq6kh3#

解决方案

如果$type属性被设置为Map到它应该被Map到的确切对象,则可能使用TypeNameHandling进行抽象化。如果没有$type属性,则无法使用typenameHandling将其Map到嵌套的抽象对象。
在使用newtonsoft和TypeNameHandling作为设置序列化JSON时,$type被设置。因此,接收到的JSON应该被正确地序列化。
如果这是不可能的,那么有两个“解决方案”:

  • 编写代码将正确的$types添加到JSON中
  • 写入转换器

对我来说,写代码代替转换器更容易更快。我正在用它做原型,但我不推荐它用于生产。
这两种方法都容易在软件包中发生错误,因此不是一个可靠的解决方案,但它是针对特定约束和目标的解决方案。

这是$types的样子:

{
   "$type": "MyPackage.Screens.Screen, MyPackage"
   "screen":{
      "$type": "MyPackage.Screens.ComponentScreen, MyPackage",
      "type":"Component",
      "footer":{
         "$type": "MyPackage.Footer.BarFooter, MyPackage",
         "type":"Bar"
      },
      "header":{
         "$type": "MyPackage.Header.TopHeader, MyPackage",
         "type":"Top"
      }
   }
}

字符串
在我的例子中,“type”属性对于非结构化来说不是必需的,可以被删除。

在代码中进行初始化

要在代码中实现,你需要在定义屏幕的地方使用一个 Package 器。因为直接实现到屏幕(抽象)是不可能的。

注意

$type应该在type属性的上面,否则它不会被正确处理。我不知道为什么会这样。

相关问题