Scala:在匹配语句后返回基数 class 时访问 case class 字段
Scala: accessing case class fields when returning a base class after a match statement
我目前正在尝试创建一个 class 和子 classes 来封装数据管道的各种配置方面(在 json 中提供);我也在非常学习 Scala。我正在使用 Play 框架库来解析 json 字符串输入 - https://www.playframework.com/documentation/2.8.x/ScalaJson
我有一些代码目前可以工作,但感觉有几个方面不对,而且这不是正确的方法。
应用程序的工作流需要获取一个 json 字符串,根据各种相似但略有不同的结构对其进行解析和验证,然后使下游处理的其他部分可以访问这些值(例如,如果文件type 是一个 csv 文件,因此设置一些配置,如果它是一个 json 文件,则改为执行此操作);但需要注意的是,这是一个动态过程。在我看来,这似乎是 case classes 的完美用例,但我觉得我误解了它们的用法。
所以我有一个密封的(为了确保所有匹配都是已知的)抽象class FileConfig,目前有两个subclasses,DelimitedConfig 和JsonConfig。 DelimitedConfig class 还使用了一个额外的 case class DelimitedFileTypeDetails,这基本上是目前两者之间的主要区别,但随着我的继续,还会添加其他偏差;并且我还有所有三个 classes 的伴随对象,以便利用播放框架格式方法:
sealed abstract class FileConfig
case class DelimitedFileTypeDetails (delimiter: String, hasColumnHeaders: Boolean, rowsToSkip: Int)
object DelimitedFileTypeDetails {
implicit val format = Json.format[DelimitedFileTypeDetails]
}
case class DelimitedConfig (deltaLakeDatabricksMountPoint: String,
restrictedDeltaLakeDatabricksMountPoint: String,
notebookToRun: String,
rowHashExclusionColumns: List[String],
deltaDatabase: String,
mergeKeySQLClause: String,
mergeUpdateFilterSQLClause: String,
fileToMerge: String,
containsPIIData: Boolean,
piiEntityNames: List[String],
transformsClass: String,
transformsMethod: String,
extractStartTime: String,
fileType: String,
fileTypeDetails: DelimitedFileTypeDetails
) extends FileConfig {
require(fileType == "Delimited", "fileType must be 'Delimited' for class DelimitedConfig")
}
object DelimitedConfig {
implicit val format = Json.format[DelimitedConfig]
}
case class JsonConfig (deltaLakeDatabricksMountPoint: String,
restrictedDeltaLakeDatabricksMountPoint: String,
notebookToRun: String,
rowHashExclusionColumns: List[String],
deltaDatabase: String,
mergeKeySQLClause: String,
mergeUpdateFilterSQLClause: String,
fileToMerge: String,
containsPIIData: Boolean,
piiEntityNames: List[String],
transformsClass: String,
transformsMethod: String,
extractStartTime: String,
fileType: String) extends FileConfig {
require(fileType == "Json", "fileType must be 'Json' for class JsonConfig")
}
object JsonConfig {
implicit val format = Json.format[JsonConfig]
}
object FileConfig {
implicit val format = Json.format[FileConfig]
}
在这些之后,我还有一个函数可以使用 play 框架的验证方法对任何提供的 json 字符串进行匹配;这将是第一个问题 - 这感觉真的很笨拙,有没有办法修改对象的嵌套评估?本质上它检查它是否满足第一个 subclass,如果不满足(JsError 是结果)然后检查下一个,依此类推:
object FileConfigParser {
/**
* Parse the provided json to ensure it matches the specified format.
*
* @param jsonString - a string representation of the json
* @return - FileConfig of the parsed json
*/
def parseFileConfig(jsonString: String): FileConfig = {
val json = Json.parse(jsonString)
json.validate[DelimitedConfig] match {
case s: JsSuccess[DelimitedConfig] => s.get.asInstanceOf[DelimitedConfig]
case e: JsError => {
json.validate[JsonConfig] match {
case s: JsSuccess[JsonConfig] => s.get.asInstanceOf[JsonConfig]
case e: JsError => {
throw new Error("Invalid json provided")
}
}
}
}
}
}
一切正常,当我运行函数时,验证成功;然而,由于 parseFileConfig 函数的结果,我无法访问 class 字段,直到我 运行 在我的主要对象中进行进一步匹配:-
val jsonString = """{
"sourceDatabricksMountPoint": "/mnt/rawlayer/myData/",
"deltaLakeDatabricksMountPoint": "/mnt/delta/",
"restrictedDeltaLakeDatabricksMountPoint": "/mnt/sensitive_delta/",
"notebookToRun":"/Ingestion/LoadDeltaLake",
"rowHashExclusionColumns": ["RowLastUpdatedTime"],
"deltaDatabase": "myDb",
"restrictedDeltaDatabase": "NICE_WFM_RESTRICTED",
"deltaTableName": "myTable",
"mergeKeySQLClause": "s.date=t.date AND s.ID=t.ID",
"mergeUpdateFilterSQLClause":"s.RowHash <> t.RowHash",
"fileToMerge": "myFile.txt",
"fileType":"Delimited",
"fileTypeDetails": {"delimiter": "|",
"hasColumnHeaders": true,
"rowsToSkip": 1
},
"containsPIIData": true,
"piiEntityNames": ["name", "surname"],
"transformsMethod": "convertDateTimeColumns",
"transformsClass": "com.example.transforms.CustomTransform",
"extractStartTime":"2020-12-10T00:00:00.000000"
}"""
val fileConfig: FileConfig = parseFileConfig(jsonString)
println(fileConfig.getClass())
// if you uncomment this it doesn't work
// println(fileConfig.fileType)
// but this does work
val fileType = fileConfig match {
case d: DelimitedConfig => d.fileType
case j: JsonConfig => j.fileType
}
println(fileType)
必须 运行 与主代码中的每个字段进行匹配感觉有点乏味,有没有办法在 class 中执行此操作,或者让字段可从class?或者....我只是做错了这一切,还有更好的方法吗?
提前致谢,
马特
如果每个 FileConfig
都有一个给定类型的成员(例如 fileType
),您可以将该成员放入 FileConfig
:
sealed abstract class FileConfig {
def fileType: String
}
然后在 类 扩展 FileConfig
的各种情况下,你会得到 override val fileType: String
.
也就是说,我会重新考虑让 fileType
成为 case class
中的一个字段。如果每个 DelimitedConfig
都将 fileType
设为 "Delimited"
,那么将其设为 def
然后定义一个 Format[FileConfig]
可能是有意义的,它会查看 fileType
字段在反序列化时注入到 JSON 中。我已经有一段时间没有使用 Play JSON,所以我不太记得那里的技术。
我目前正在尝试创建一个 class 和子 classes 来封装数据管道的各种配置方面(在 json 中提供);我也在非常学习 Scala。我正在使用 Play 框架库来解析 json 字符串输入 - https://www.playframework.com/documentation/2.8.x/ScalaJson
我有一些代码目前可以工作,但感觉有几个方面不对,而且这不是正确的方法。
应用程序的工作流需要获取一个 json 字符串,根据各种相似但略有不同的结构对其进行解析和验证,然后使下游处理的其他部分可以访问这些值(例如,如果文件type 是一个 csv 文件,因此设置一些配置,如果它是一个 json 文件,则改为执行此操作);但需要注意的是,这是一个动态过程。在我看来,这似乎是 case classes 的完美用例,但我觉得我误解了它们的用法。
所以我有一个密封的(为了确保所有匹配都是已知的)抽象class FileConfig,目前有两个subclasses,DelimitedConfig 和JsonConfig。 DelimitedConfig class 还使用了一个额外的 case class DelimitedFileTypeDetails,这基本上是目前两者之间的主要区别,但随着我的继续,还会添加其他偏差;并且我还有所有三个 classes 的伴随对象,以便利用播放框架格式方法:
sealed abstract class FileConfig
case class DelimitedFileTypeDetails (delimiter: String, hasColumnHeaders: Boolean, rowsToSkip: Int)
object DelimitedFileTypeDetails {
implicit val format = Json.format[DelimitedFileTypeDetails]
}
case class DelimitedConfig (deltaLakeDatabricksMountPoint: String,
restrictedDeltaLakeDatabricksMountPoint: String,
notebookToRun: String,
rowHashExclusionColumns: List[String],
deltaDatabase: String,
mergeKeySQLClause: String,
mergeUpdateFilterSQLClause: String,
fileToMerge: String,
containsPIIData: Boolean,
piiEntityNames: List[String],
transformsClass: String,
transformsMethod: String,
extractStartTime: String,
fileType: String,
fileTypeDetails: DelimitedFileTypeDetails
) extends FileConfig {
require(fileType == "Delimited", "fileType must be 'Delimited' for class DelimitedConfig")
}
object DelimitedConfig {
implicit val format = Json.format[DelimitedConfig]
}
case class JsonConfig (deltaLakeDatabricksMountPoint: String,
restrictedDeltaLakeDatabricksMountPoint: String,
notebookToRun: String,
rowHashExclusionColumns: List[String],
deltaDatabase: String,
mergeKeySQLClause: String,
mergeUpdateFilterSQLClause: String,
fileToMerge: String,
containsPIIData: Boolean,
piiEntityNames: List[String],
transformsClass: String,
transformsMethod: String,
extractStartTime: String,
fileType: String) extends FileConfig {
require(fileType == "Json", "fileType must be 'Json' for class JsonConfig")
}
object JsonConfig {
implicit val format = Json.format[JsonConfig]
}
object FileConfig {
implicit val format = Json.format[FileConfig]
}
在这些之后,我还有一个函数可以使用 play 框架的验证方法对任何提供的 json 字符串进行匹配;这将是第一个问题 - 这感觉真的很笨拙,有没有办法修改对象的嵌套评估?本质上它检查它是否满足第一个 subclass,如果不满足(JsError 是结果)然后检查下一个,依此类推:
object FileConfigParser {
/**
* Parse the provided json to ensure it matches the specified format.
*
* @param jsonString - a string representation of the json
* @return - FileConfig of the parsed json
*/
def parseFileConfig(jsonString: String): FileConfig = {
val json = Json.parse(jsonString)
json.validate[DelimitedConfig] match {
case s: JsSuccess[DelimitedConfig] => s.get.asInstanceOf[DelimitedConfig]
case e: JsError => {
json.validate[JsonConfig] match {
case s: JsSuccess[JsonConfig] => s.get.asInstanceOf[JsonConfig]
case e: JsError => {
throw new Error("Invalid json provided")
}
}
}
}
}
}
一切正常,当我运行函数时,验证成功;然而,由于 parseFileConfig 函数的结果,我无法访问 class 字段,直到我 运行 在我的主要对象中进行进一步匹配:-
val jsonString = """{
"sourceDatabricksMountPoint": "/mnt/rawlayer/myData/",
"deltaLakeDatabricksMountPoint": "/mnt/delta/",
"restrictedDeltaLakeDatabricksMountPoint": "/mnt/sensitive_delta/",
"notebookToRun":"/Ingestion/LoadDeltaLake",
"rowHashExclusionColumns": ["RowLastUpdatedTime"],
"deltaDatabase": "myDb",
"restrictedDeltaDatabase": "NICE_WFM_RESTRICTED",
"deltaTableName": "myTable",
"mergeKeySQLClause": "s.date=t.date AND s.ID=t.ID",
"mergeUpdateFilterSQLClause":"s.RowHash <> t.RowHash",
"fileToMerge": "myFile.txt",
"fileType":"Delimited",
"fileTypeDetails": {"delimiter": "|",
"hasColumnHeaders": true,
"rowsToSkip": 1
},
"containsPIIData": true,
"piiEntityNames": ["name", "surname"],
"transformsMethod": "convertDateTimeColumns",
"transformsClass": "com.example.transforms.CustomTransform",
"extractStartTime":"2020-12-10T00:00:00.000000"
}"""
val fileConfig: FileConfig = parseFileConfig(jsonString)
println(fileConfig.getClass())
// if you uncomment this it doesn't work
// println(fileConfig.fileType)
// but this does work
val fileType = fileConfig match {
case d: DelimitedConfig => d.fileType
case j: JsonConfig => j.fileType
}
println(fileType)
必须 运行 与主代码中的每个字段进行匹配感觉有点乏味,有没有办法在 class 中执行此操作,或者让字段可从class?或者....我只是做错了这一切,还有更好的方法吗?
提前致谢,
马特
如果每个 FileConfig
都有一个给定类型的成员(例如 fileType
),您可以将该成员放入 FileConfig
:
sealed abstract class FileConfig {
def fileType: String
}
然后在 类 扩展 FileConfig
的各种情况下,你会得到 override val fileType: String
.
也就是说,我会重新考虑让 fileType
成为 case class
中的一个字段。如果每个 DelimitedConfig
都将 fileType
设为 "Delimited"
,那么将其设为 def
然后定义一个 Format[FileConfig]
可能是有意义的,它会查看 fileType
字段在反序列化时注入到 JSON 中。我已经有一段时间没有使用 Play JSON,所以我不太记得那里的技术。