4

我有一个 JSON 格式如下:

{
"inventory": [
           {
        "productType": "someProduct1",
        "details": {
            "productId": "Some_id",
            "description": "some description"
        }
        },
 {
        "productType": "someProduct2",
        "details": {
            "productId": "Some_id",
            "description":{"someKey":"somevalue"}
        }
    }
]
}

我希望上述 json 反序列化的案例类如下所示:

case class Inventory(products:List[Product])
case class Product(productType:String,details:ProductDetails)
abstract class ProductDetails
case class ProductDetailsSimple(productId:String,description:String) extends ProductDetails
case class ProductDetailsComplex(productId:String,description:Map[String,String]) extends ProductDetails

我正在使用jackson-scala 模块来反序列化上述 JSON 字符串,如下所示:

 val mapper = new ObjectMapper() with ScalaObjectMapper
 mapper.registerModule(DefaultScalaModule)
 mapper.readValue(jsonBody, classOf[Inventory])

我得到的错误如下:“意外的令牌(END_OBJECT),预期的FIELD_NAME:缺少包含类型ID的属性'@details'(对于ProductDetails类)\n在[Source:java.io.StringReader@12dfbabd;行: 9, 栏目: 5]"

我已经阅读了关于多态反序列化的杰克逊文档,并尝试了上述组合,但没有运气。我想了解我在这里做错了什么,需要更正使用杰克逊模块的反序列化。

4

1 回答 1

14

我认为这里有几个单独的问题需要解决,所以我列出了三种不同的方法。

TL;博士

要么正确使用杰克逊多态性,要么在您的情况下,采用更简单的方法并消除对多态性的需求。在 github 上查看我的代码。

1.自定义反序列化器

您格式化的 JSON 是:

{ inventory:
   [ { productType: 'someProduct1',
       details:
        { productId: 'Some_id',
          description: 'some description' } },
     { productType: 'someProduct2',
       details:
        { productId: 'Some_id',
          description: { someKey: 'somevalue' } 
        }
     } 
   ]
}

在我看来,该字段productType放错了位置,但如果这种格式是严格要求,那么您可以编写自己的反序列化器来查看该productType字段并实例化不同的具体类。

我认为这不是最好的解决方案,所以我没有编写示例代码,但我喜欢Joda 日期时间包作为自定义序列化/反序列化的参考

2.杰克逊多态性

您已经与类型字段Product分开:ProductDetails

case class Product(productType:String,details:ProductDetails)

abstract class ProductDetails

我认为您混淆了 Jackson 的多态数据类型处理的工作原理,从而使您的类设计变得复杂。

也许您的业务规则要求产品具有“类型”,在这种情况下,我将其命名为“种类”或其他一些非代码标签,并将其放入您所称ProductDetails的 .

但是,如果在尝试使类型多态起作用时包含“类型”,那么这不是正确的方法。

我将以下内容作为 Scala 中杰克逊多态性的一个工作示例:

/**
 * The types here are close to the original question types but use 
 * Jackson annotations to mark the polymorphic JSON treatment.
 */

import scala.Array
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.PROPERTY,
  property = "type")
@JsonSubTypes(Array(
  new Type(value = classOf[ProductDetailsSimple], name = "simple"),
  new Type(value = classOf[ProductDetailsComplex], name = "complex")
))
abstract class Product

case class ProductDetailsSimple(productId: String, description: String) extends Product

case class ProductDetailsComplex(productId: String, description: Map[String, String]) extends Product

case class PolymorphicInventory(products: List[Product])

请注意,我删除了ProductvsProductDetails区别,因此Inventory现在就像Product. 我留下了名字ProductDetailsSimpleProductDetailsComplex尽管我认为它们应该重命名。

示例用法:

val inv = PolymorphicInventory(
  List(
    ProductDetailsSimple(productId="Some_id", description="some description"),
    ProductDetailsComplex(productId="Some_id", description=Map("someKey" -> "somevalue"))
  )
)

val s = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(inv)
println("Polymorphic Inventory as JSON: "+s)

输出:

Polymorphic Inventory as JSON: {
  "products" : [ {
    "type" : "simple",
    "productId" : "Some_id",
    "description" : "some description"
  }, {
    "type" : "complex",
    "productId" : "Some_id",
    "description" : {
      "someKey" : "somevalue"
    }
  } ]
}

3.去除多态性

我建议在这种情况下根本不需要多态性,并且错误在于尝试将“描述”为单个字符串或键/值映射,而它们实际上是具有不同意图的字段。

可能涉及到数据遗留问题(在这种情况下,请参阅自定义 deser 建议),但如果数据在您的控制范围内,我投票赞成“更简单”:

case class Product(productId: String,
                   description: String="",
                   attributes: Map[String, String]=Map.empty)

case class PlainInventory(products: List[Product])

我更“scala-rific”Option来表示没有值,所以:

case class Product(productId: String,
                   description: Option[String]=None,
                   attributes: Option[Map[String, String]]=None)

示例用法:

val inv = PlainInventory(
  List(
    Product(productId="Some_id", description=Some("some description")),
    Product(productId="Some_id", attributes=Some(Map("someKey" -> "somevalue")))
  )
)

val s = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(inv)
println("Plain Inventory as JSON: "+s)

输出:

Plain Inventory as JSON: {
  "products" : [ {
    "productId" : "Some_id",
    "description" : "some description"
  }, {
    "productId" : "Some_id",
    "attributes" : {
      "someKey" : "somevalue"
    }
  } ]
}

在github上工作最少的代码。

于 2014-02-19T08:12:30.210 回答