首页 > 解决方案 > 使用 circe 解码各种类型列表的问题

问题描述

我有一个 json 字符串,它是一个 json 对象列表,每个 json 都有几个嵌入对象;主要问题在于

自定义字段

,该字段是一个列表,它可以有多种类型的对象;我在此页面上找到了一个示例https://immutables.pl/2017/02/25/customizing-circes-auto-generic-derivation/,但是我对 circe 和 shapeless 没有太多经验,当我尝试做它的解码向我发送了这个异常:

Left(DecodingFailure(CNil, List(DownArray, DownField(custom_fields), DownArray, DownField(tasks))))

解码代码库:

import io.circe
import io.circe.generic.extras.{AutoDerivation, Configuration}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder}

object ADT extends App {
val json =
  """
    | {
    | "tasks": [
    | {
    |  "id": "xxxxxxx",
    |  "custom_id": null,
    |  "name": "xxxxxxxxxxxxxxxxx",
    |  "text_content": null,
    |  "description": null,
    |  "status": {
    |    "status": "Closed",
    |    "color": "#6bc950",
    |    "type": "closed",
    |    "orderindex": 3
    |  },
    |  "orderindex": "247.00000000000000000000000000000000",
    |  "date_created": "1581725281039",
    |  "date_updated": "1587696872148",
    |  "date_closed": "1583380816440",
    |  "archived": false,
    |  "creator": {
    |    "id": 4374599,
    |    "username": "name xxxxxxx",
    |    "color": "#006063",
    |    "email": "xxxx@xxx.com",
    |    "profilePicture": "https://xxxxxxxx.jpg"
    |  },
    |  "assignees": [
    |    {
    |      "id": 000000,
    |      "username": "xxxxx",
    |      "color": "#81b1ff",
    |      "initials": "xx",
    |      "email": "xxxxx@xxxxxxx.com",
    |      "profilePicture": "https://xxxxxxx.jpg"
    |    }
    |  ],
    |  "watchers": [
    |
    |  ],
    |  "checklists": [
    |
    |  ],
    |  "tags": [
    |
    |  ],
    |  "parent": null,
    |  "priority": null,
    |  "due_date": null,
    |  "start_date": null,
    |  "points": null,
    |  "time_estimate": null,
    |
    |  "custom_fields": [
    |    {
    |      "id": "d0c016df-e09a-492e-a7a2-cc92e1993627",
    |      "name": "Sprint",
    |      "type": "labels",
    |      "type_config": {
    |        "options": [
    |          {
    |            "id": "64e19188-8440-4b35-8459-d49348c92e55",
    |            "label": "2021w14",
    |            "color": "#e50000"
    |          },
    |          {
    |            "id": "f71958f7-1c87-484e-8857-c2f90ef80f69",
    |            "label": "2021w13",
    |            "color": "#2ecd6f"
    |          },
    |          {
    |            "id": "a7f835c8-76fe-42e9-85a9-50a8af8dd6ea",
    |            "label": "2021w12",
    |            "color": "#fff"
    |          },
    |          {
    |            "id": "aaf2fcc6-ec8d-4eb8-8621-cac2c8063f17",
    |            "label": "2021w11",
    |            "color": "#fff"
    |          }
    |        ]
    |      },
    |      "date_created": "xxxxxxxxxxxx",
    |      "hide_from_guests": false,
    |      "value": [
    |        "64e19188-8440-4b35-8459-d49348c92e55"
    |      ],
    |      "required": false
    |    },
    |    {
    |      "id": "4513e77f-9866-4139-bcc5-458945db0deb",
    |      "name": "Points",
    |      "type": "drop_down",
    |      "type_config": {
    |        "new_drop_down": true,
    |        "options": [
    |          {
    |            "id": "02b60555-6814-4bfa-9a4a-3c5728261f9b",
    |            "name": "1",
    |            "color": null,
    |            "orderindex": 0
    |          },
    |          {
    |            "id": "d7fce482-c1d2-4732-a6f9-4268c447c4b7",
    |            "name": "2",
    |            "color": null,
    |            "orderindex": 1
    |          },
    |          {
    |            "id": "b7b99b87-7639-4286-8e6f-96966718ff1f",
    |            "name": "3",
    |            "color": null,
    |            "orderindex": 2
    |          }
    |        ]
    |      },
    |      "date_created": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
    |      "hide_from_guests": false,
    |      "required": false
    |    },
    |    {
    |      "id": "xxxxxx_xxxxxx_xxxxxxx_xxxxx",
    |      "name": "Progreso",
    |      "type": "xxxxxxxxxxxxxxxxx",
    |      "type_config": {
    |        "tracking": {
    |          "checklists": true,
    |          "assigned_comments": true
    |        },
    |        "complete_on": 3
    |      },
    |      "date_created": "xxxxxxxxxxxxxxxxxxxxx",
    |      "hide_from_guests": false,
    |      "value": {
    |        "percent_complete": 100
    |      },
    |      "required": null
    |    }
    |  ],
    |
    |  "dependencies": [
    |
    |  ],
    |  "linked_tasks": [
    |
    |  ],
    |  "team_id": "xxxxxxxx",
    |  "url": "xxxxxxxxxx",
    |  "permission_level": "create",
    |  "list": {
    |    "id": "xxxxxxxxx",
    |    "name": "xxxxxxx  02-06",
    |    "access": true
    |  },
    |  "project": {
    |    "id": "xxxxxxxxx",
    |    "name": "Sprints",
    |    "hidden": false,
    |    "access": true
    |  },
    |  "folder": {
    |    "id": "xxxxxxxxx",
    |    "name": "Sprints",
    |    "hidden": false,
    |    "access": true
    |  },
    |  "space": {
    |    "id": "4286586"
    |  }
    |}
    | ]
    | }
    |""".stripMargin


  object codecs {
    import auto._

    implicit val movieEncoder: Encoder[TasksItem] = deriveEncoder[TasksItem]
    implicit val movieDecoder: Decoder[TasksItem] = deriveDecoder[TasksItem]
  }

  private object auto extends AutoDerivation  {
    import shapeless._

    // list value
    implicit def encoderValueClass[T <: AnyVal, V](implicit
                                                   g: Lazy[Generic.Aux[T, V :: HNil]],
                                                   e: Encoder[V]
                                                  ): Encoder[T] = Encoder.instance { element ⇒
      e(g.value.to(element).head)
    }

    implicit def decoderValueClass[T <: AnyVal, V](implicit
                                                   g: Lazy[Generic.Aux[T, V :: HNil]],
                                                   d: Decoder[V]
                                                  ): Decoder[T] = Decoder.instance { cursor ⇒
      d(cursor).map { element ⇒
        g.value.from(element :: HNil)
      }
    }

    implicit val configuration: Configuration = Configuration.default.withDiscriminator("type")
  }

  import codecs._


  val decoded:Either[circe.Error, TasksItem] = circe.jawn.decode[TasksItem](json)
  println(decoded)

}

案例类代码:

case class ValueSprint(value:Option[List[String]]=None ) extends AnyVal
case class ValueStoryPoint(value:Option[Int]=None ) extends AnyVal
case class ValueStoryProgreso(value:Option[Int]=None ) extends AnyVal
case class ValueItem( percent_complete:ValueStoryProgreso )


sealed trait ElementTask
case class CustomFieldsItemSprint( id:Option[String]=None, name:Option[String]=None, `type`:Option[String]=None,
                                   date_created:Option[String]=None,
                                   hide_from_guests:Option[Boolean]=None, value:ValueSprint ,
                                   required:Option[Boolean]=None ) extends ElementTask

case class CustomFieldsItemStoryPoint (  id:Option[String]=None, name:Option[String]=None, `type`:Option[String]=None,
                                         date_created:Option[String]=None,
                                         hide_from_guests:Option[Boolean]=None, value: ValueStoryPoint ,
                                         required:Option[Boolean]=None ) extends ElementTask
case class CustomFieldsItemProgreso (  id:Option[String]=None, name:Option[String]=None, `type`:Option[String]=None,
                                       date_created:Option[String]=None,
                                       hide_from_guests:Option[Boolean]=None, value:Option[ValueItem]=None ,
                                       required:Option[Boolean]=None ) extends ElementTask


case class SpaceItem(id:Option[String]=None)
case class FolderItem( id: Option[String]=None, name:Option[String]=None, hidden:Option[Boolean]=None, access:Option[Boolean]=None) 
case class ProjectItem( id: Option[String]=None, name:Option[String]=None, hidden:Option[Boolean]=None, access:Option[Boolean]=None) 
case class ListItem( id:Option[String]=None, name:Option[String]=None, access:Option[Boolean]=None ) 
case class TagsItem (name:Option[String]=None, tag_fg: Option[String]=None, tag_bg:Option[String]=None, creator: Option[Long]=None) 
case class AssigneesItem( id:Option[Long]=None, username:Option[String]=None, color:Option[String]=None, initials:Option[String]=None,
                      email: Option[String]=None, profilePicture: Option[String]=None) 
case class CreatorItem( id:Option[Long]=None, username:Option[String]=None, color:Option[String]=None, email:Option[String]=None,
                    profilePicture:Option[String]=None ) 
case class StatusItem( status:Option[String]=None, color:Option[String]=None, `type`:Option[String]=None, orderindex:Option[Int]=None) 
case class DependenciesItem( task_id:Option[String]=None, depends_on:Option[String]=None, `type`:Option[Int],
                         date_created:Option[String]=None, userid:Option[String]=None) 
case class PriorityItem( id:Option[String]=None, priority:Option[String]=None, color:Option[String]=None, orderindex:Option[String]=None) 
case class OptionsItem(id:Option[String]=None, label:Option[String]=None, name:Option[String]=None, color:Option[String]=None, orderindex:Option[Int]=None ) 
case class TrackingItem(checklists:Option[Boolean], assigned_comments:Option[Boolean]) 
case class TypeConfigItem( new_drop_down:Option[Boolean]=None, options:Option[List[OptionsItem]]=None , complete_on:Option[Int], tracking:Option[TrackingItem] ) 
case class ChecklistsItem( id:Option[String]=None, task_id:Option[String]=None, name:Option[String]=None, date_created:Option[String]=None, orderindex:Option[Int]=None, creator:Option[Long]=None, resolved:Option[Int]=None, unresolved:Option[Int]=None ) 
case class TaskItem ( id:Option[String]=None, custom_id:Option[String]=None , name:Option[String]=None, text_content:Option[String]=None,
                  description:Option[String]=None, status:Option[StatusItem]=None, orderindex:Option[String]=None, date_created:Option[String]=None ,
                  date_updated: Option[String]=None , date_closed:Option[String]=None, archived:Option[Boolean]=None,
                  creator:Option[CreatorItem] , assignees:Option[List[AssigneesItem]]=None , watchers:Option[List[String]]=None ,
                  checklists:Option[List[ChecklistsItem]]=None , tags:Option[List[TagsItem]]=None , parent:Option[String]=None ,
                  priority:Option[PriorityItem]=None , due_date:Option[String]=None , start_date:Option[String]=None ,
                  points:Option[String]=None , time_estimate:Option[Long]=None , time_spent:Option[Long]=None ,
                    
                      custom_fields:Option[List[ElementTask] ]=None ,
                    
                  dependencies:Option[List[DependenciesItem]]=None, linked_tasks:Option[List[String]]=None ,
                  team_id:Option[String]=None , url:Option[String]=None , permission_level:Option[String]=None,
                  list:Option[ListItem]=None , project:Option[ProjectItem]=None , folder:Option[FolderItem]=None ,
                  space:Option[SpaceItem]=None )
case class TasksItem ( tasks:Option[List[TaskItem]]=None )  // main case class

在此字段上失败: custom_fields:Option[List[ElementTask] ]=None ,

我需要修改什么或缺少什么才能使其正常工作?

标签: scalashapelesscirce

解决方案


推荐阅读