Jackson Scala JSON反序列化到案例类

时间:2022-10-11 18:04:08

I have a JSON which has following form:

我有一个JSON,其形式如下:

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

The case classes that I want the above json to deserialize look like following:

我希望上面的json反序列化的case类如下所示:

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

I am using jackson-scala module to deserialize the above JSON string as follows:

我使用jackson-scala模块反序列化上面的JSON字符串,如下所示:

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

The error I get is as follows: "Unexpected token (END_OBJECT), expected FIELD_NAME: missing property '@details' that is to contain type id (for class ProductDetails)\n at [Source: java.io.StringReader@12dfbabd; line: 9, column: 5]"

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

I have been through jackson documentation on Polymorphic deserialization and have tried combinations as mentioned but with no luck. I would like to understand what I am doing wrong here, which needs correction with respect to deserialization using jackson module.

我已经通过关于多态反序列化的杰克逊文档,并尝试了所提到的组合,但没有运气。我想了解我在这里做错了什么,需要使用jackson模块对反序列化进行修正。

1 个解决方案

#1


13  

I think there's a few separate problems to address here, so I've listed three separate approaches.

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

TL;DR

Either use Jackson polymorphism correctly or, in your case, go to a simpler approach and remove the need for the polymorphism. See my code on github.

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

1. Custom Deserializer

Your formatted JSON is:

您格式化的JSON是:

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

The field productType is misplaced, in my opinion, but if this format is a strict requirement then you could write your own deserializer that looks at the productType field and instantiates a different concrete class.

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

I don't think this would be the best solution so I didn't write example code, but I like the Joda date-time package as a reference for custom serialize/deserialize

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

2. Jackson Polymorphism

You've separated Product from ProductDetails with a type field:

您已使用类型字段将Product与ProductDetails分开:

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

abstract class ProductDetails

I think you've confused how Jackson's polymorphic data type handling works and complicated your class design as a result.

我认为你已经混淆了Jackson的多态数据类型处理如何工作并因此使您的类设计复杂化。

Perhaps your business rules require that a product has a "type", in which case I'd name it "kind" or some other non-code label, and put it into what you've called ProductDetails.

也许您的业务规则要求产品具有“类型”,在这种情况下,我将其命名为“kind”或其他非代码标签,并将其置于您所谓的ProductDetails中。

But if "type" was included in an attempt to get type polymorphism working, then it isn't the right way.

但是如果试图使类型多态性工作时包含“类型”,那么它就不是正确的方法。

I've included the below as a working example of Jackson polymorphism in Scala:

我将下面作为Scala中Jackson多态性的工作示例包括在内:

/**
 * 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])

Note that I removed the Product vs ProductDetails distinction, so an Inventory now just as a list of Product. I left the names ProductDetailsSimple and ProductDetailsComplex though I think they should be renamed.

请注意,我删除了Product与ProductDetails的区别,因此Inventory现在就像Product列表一样。我留下名称ProductDetailsS​​imple和ProductDetailsComplex虽然我认为它们应该被重命名。

Example usage:

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)

Output:

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

3. Remove the polymorphism

I suggest that polymorphism in this case isn't needed at all, and that the error is in trying to make "description" either a single string or a key/value map when they are really fields with distinct intentions.

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

Perhaps there is a data legacy issue involved (in which case see the custom deser suggestion), but if the data is in your control, I vote for "go simpler":

也许涉及数据遗留问题(在这种情况下请参见自定义deser建议),但如果数据在您的控制范围内,我投票支持“go simpler”:

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

case class PlainInventory(products: List[Product])

I's more "scala-rific" to use Option to indicate the absence of a value, so:

我更“scala-rific”使用Option来表示缺少值,所以:

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

Example usage:

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)

Output:

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

Working minimal code on github.

在github上运行最少的代码。

#1


13  

I think there's a few separate problems to address here, so I've listed three separate approaches.

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

TL;DR

Either use Jackson polymorphism correctly or, in your case, go to a simpler approach and remove the need for the polymorphism. See my code on github.

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

1. Custom Deserializer

Your formatted JSON is:

您格式化的JSON是:

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

The field productType is misplaced, in my opinion, but if this format is a strict requirement then you could write your own deserializer that looks at the productType field and instantiates a different concrete class.

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

I don't think this would be the best solution so I didn't write example code, but I like the Joda date-time package as a reference for custom serialize/deserialize

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

2. Jackson Polymorphism

You've separated Product from ProductDetails with a type field:

您已使用类型字段将Product与ProductDetails分开:

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

abstract class ProductDetails

I think you've confused how Jackson's polymorphic data type handling works and complicated your class design as a result.

我认为你已经混淆了Jackson的多态数据类型处理如何工作并因此使您的类设计复杂化。

Perhaps your business rules require that a product has a "type", in which case I'd name it "kind" or some other non-code label, and put it into what you've called ProductDetails.

也许您的业务规则要求产品具有“类型”,在这种情况下,我将其命名为“kind”或其他非代码标签,并将其置于您所谓的ProductDetails中。

But if "type" was included in an attempt to get type polymorphism working, then it isn't the right way.

但是如果试图使类型多态性工作时包含“类型”,那么它就不是正确的方法。

I've included the below as a working example of Jackson polymorphism in Scala:

我将下面作为Scala中Jackson多态性的工作示例包括在内:

/**
 * 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])

Note that I removed the Product vs ProductDetails distinction, so an Inventory now just as a list of Product. I left the names ProductDetailsSimple and ProductDetailsComplex though I think they should be renamed.

请注意,我删除了Product与ProductDetails的区别,因此Inventory现在就像Product列表一样。我留下名称ProductDetailsS​​imple和ProductDetailsComplex虽然我认为它们应该被重命名。

Example usage:

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)

Output:

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

3. Remove the polymorphism

I suggest that polymorphism in this case isn't needed at all, and that the error is in trying to make "description" either a single string or a key/value map when they are really fields with distinct intentions.

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

Perhaps there is a data legacy issue involved (in which case see the custom deser suggestion), but if the data is in your control, I vote for "go simpler":

也许涉及数据遗留问题(在这种情况下请参见自定义deser建议),但如果数据在您的控制范围内,我投票支持“go simpler”:

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

case class PlainInventory(products: List[Product])

I's more "scala-rific" to use Option to indicate the absence of a value, so:

我更“scala-rific”使用Option来表示缺少值,所以:

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

Example usage:

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)

Output:

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

Working minimal code on github.

在github上运行最少的代码。