복붙노트

[SCALA] 플레이 2 JSON 형식으로 누락 된 속성에 대한 기본값

SCALA

플레이 2 JSON 형식으로 누락 된 속성에 대한 기본값

나는 플레이 스칼라에서 다음과 같은 모델의 동등한 있습니다

case class Foo(id:Int,value:String)
object Foo{
  import play.api.libs.json.Json
  implicit val fooFormats = Json.format[Foo]
}

다음 푸 예를 들어

Foo(1, "foo")

나는 다음과 같은 JSON 문서를 얻을 것입니다 :

{"id":1, "value": "foo"}

이 JSON을 지속하고 데이터 저장소에서 읽습니다. 이제 내 요구 사항이 변경되었습니다 난 푸에 속성을 추가해야합니다. 속성은 기본값이 :

case class Foo(id:String,value:String, status:String="pending")

JSON에 쓰는 것은 문제가되지 않습니다 :

{"id":1, "value": "foo", "status":"pending"}

그것에서 읽기 그러나 "/ 상태"경로 누락에 대한 JsError을 산출한다.

어떻게 최소한의 소음과 기본을 제공 할 수 있습니까?

(PS : 나는 아래에 게시 할 예정입니다 대답을하지만 난 정말 그것으로 만족하지 오전 upvote에와 더 나은 옵션을 받아들이)

해결법

  1. ==============================

    1.2.6 재생

    2.6 재생

    CanardMoussant의 대답 @ 따라, 재생 2.6로 시작하는 플레이 JSON 매크로 개선 및 역 직렬화 할 때 자리로 기본값을 사용하는 등의 여러 새로운 기능을 제안하고있다 :

    implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
    

    아래 옵션 중 하나를 사용하여 2.6 최선의 선택이 남아 아래 플레이의 경우 :

    플레이 JSON-추가

    나는이 문제의 하나를 포함하여 플레이 JSON로했다 단점의 대부분을 훨씬 더 나은 솔루션에 대해 알게 :

    플레이 JSON-별도의 사용 [플레이 JSON-확장] 내부적으로이 문제의 특정 문제를 해결하기 위해.

    자동으로 발생하기 쉬운 오류 훨씬 덜 refactors을 만드는 시리얼 라이저 / 디시리얼라이저에서 누락 된 기본값을 포함하는 매크로를 포함!

    import play.json.extra.Jsonx
    implicit def jsonFormat = Jsonx.formatCaseClass[Foo]
    

    플레이 JSON-추가 : 검사 할 수있는 라이브러리에 더가있다

    JSON 변압기

    나의 현재 솔루션은 JSON 변압기를 만들고이 매크로에 의해 생성 읽습니다와 결합하는 것입니다. 변압기는 이하의 방법에 의해 생성된다 :

    object JsonExtensions{
      def withDefault[A](key:String, default:A)(implicit writes:Writes[A]) = __.json.update((__ \ key).json.copyFrom((__ \ key).json.pick orElse Reads.pure(Json.toJson(default))))
    }
    

    형식 정의는 다음이된다 :

    implicit val fooformats: Format[Foo] = new Format[Foo]{
      import JsonExtensions._
      val base = Json.format[Foo]
      def reads(json: JsValue): JsResult[Foo] = base.compose(withDefault("status","bidon")).reads(json)
      def writes(o: Foo): JsValue = base.writes(o)
    }
    

    Json.parse("""{"id":"1", "value":"foo"}""").validate[Foo]
    

    기본 값을 적용하여 실제로 푸의 인스턴스를 생성합니다.

    이것은 내 의견이 주요 결함이있다 :

  2. ==============================

    2.내가 찾은 것을 가장 깨끗한 방법은, 예를 들어, 사용 "순수"이다

    내가 찾은 것을 가장 깨끗한 방법은, 예를 들어, 사용 "순수"이다

    ...      
    ((JsPath \ "notes").read[String] or Reads.pure("")) and
    ((JsPath \ "title").read[String] or Reads.pure("")) and
    ...
    

    기본이 일정 할 때이 정상적인 암시 적 방법으로 사용할 수 있습니다. 그것의 동적, 다음 방법을 작성해야하는 경우가 읽어 들여 만든 다음 범위 인, 라를 소개합니다

    implicit val packageReader = makeJsonReads(jobId, url)
    
  3. ==============================

    3.대안적인 해결책은 InvariantFunctor에서 inmap 결합 formatNullable [T]를 사용하는 것이다.

    대안적인 해결책은 InvariantFunctor에서 inmap 결합 formatNullable [T]를 사용하는 것이다.

    import play.api.libs.functional.syntax._
    import play.api.libs.json._
    
    implicit val fooFormats = 
      ((__ \ "id").format[Int] ~
       (__ \ "value").format[String] ~
       (__ \ "status").formatNullable[String].inmap[String](_.getOrElse("pending"), Some(_))
      )(Foo.apply, unlift(Foo.unapply))
    
  4. ==============================

    4.나는 공식적인 답변 지금 플레이 JSON 2.6 함께 오는 WithDefaultValues를 사용하도록해야한다고 생각 :

    나는 공식적인 답변 지금 플레이 JSON 2.6 함께 오는 WithDefaultValues를 사용하도록해야한다고 생각 :

    implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]
    

    편집하다:

    주의하는 것이 중요하다 플레이 JSON-추가 라이브러리에서 행동 다릅니다. 예를 들어 당신이 DateTime.Now에 대한 기본 값을 갖는 날짜 시간 매개 변수가있는 경우에, 당신은 이제 프로세스의 시작 시간을 얻을 것이다 - 아마 당신이 원하지 않는 무엇을 - 당신은 창조의 시간을 가졌다 플레이 JSON-추가와 반면 JSON에서.

  5. ==============================

    5.난 그냥 (사용자 측 즉 옵션) 나는 모든 JSON 필드는 선택 사양하고 싶은 경우에 직면했지만 내부적으로 나는 모든 필드는 사용자가 특정 필드를 지정하지 않는 경우 정확하게 정의 된 기본값이 아닌 선택 사항으로합니다. 이 사용 사례와 유사합니다.

    난 그냥 (사용자 측 즉 옵션) 나는 모든 JSON 필드는 선택 사양하고 싶은 경우에 직면했지만 내부적으로 나는 모든 필드는 사용자가 특정 필드를 지정하지 않는 경우 정확하게 정의 된 기본값이 아닌 선택 사항으로합니다. 이 사용 사례와 유사합니다.

    나는 현재 단순히 완전히 선택적 인수와 푸의 건설을 랩하는 방법을 고려 중이 야 :

    case class Foo(id: Int, value: String, status: String)
    
    object FooBuilder {
      def apply(id: Option[Int], value: Option[String], status: Option[String]) = Foo(
        id     getOrElse 0, 
        value  getOrElse "nothing", 
        status getOrElse "pending"
      )
      val fooReader: Reads[Foo] = (
        (__ \ "id").readNullable[Int] and
        (__ \ "value").readNullable[String] and
        (__ \ "status").readNullable[String]
      )(FooBuilder.apply _)
    }
    
    implicit val fooReader = FooBuilder.fooReader
    val foo = Json.parse("""{"id": 1, "value": "foo"}""")
                  .validate[Foo]
                  .get // returns Foo(1, "foo", "pending")
    

    불행하게도, 당신이 피하기 위해 원하는 것을 아마 인, 명시 적 [푸] 읽기 및 쓰기 [푸] 작성이 필요합니다? 하나 더 단점은 키가 없거나 값이 null의 경우 기본값 만 사용하는 것입니다. 키가 잘못된 유형의 값을 포함하지만 경우, 다시 전체 검증은 ValidationError를 반환합니다.

    이러한 옵션 JSON 구조를 중첩하는 것은 인스턴스에 대한 문제가되지 않습니다 :

    case class Bar(id1: Int, id2: Int)
    
    object BarBuilder {
      def apply(id1: Option[Int], id2: Option[Int]) = Bar(
        id1     getOrElse 0, 
        id2     getOrElse 0 
      )
      val reader: Reads[Bar] = (
        (__ \ "id1").readNullable[Int] and
        (__ \ "id2").readNullable[Int]
      )(BarBuilder.apply _)
      val writer: Writes[Bar] = (
        (__ \ "id1").write[Int] and
        (__ \ "id2").write[Int]
      )(unlift(Bar.unapply))
    }
    
    case class Foo(id: Int, value: String, status: String, bar: Bar)
    
    object FooBuilder {
      implicit val barReader = BarBuilder.reader
      implicit val barWriter = BarBuilder.writer
      def apply(id: Option[Int], value: Option[String], status: Option[String], bar: Option[Bar]) = Foo(
        id     getOrElse 0, 
        value  getOrElse "nothing", 
        status getOrElse "pending",
        bar    getOrElse BarBuilder.apply(None, None)
      )
      val reader: Reads[Foo] = (
        (__ \ "id").readNullable[Int] and
        (__ \ "value").readNullable[String] and
        (__ \ "status").readNullable[String] and
        (__ \ "bar").readNullable[Bar]
      )(FooBuilder.apply _)
      val writer: Writes[Foo] = (
        (__ \ "id").write[Int] and
        (__ \ "value").write[String] and
        (__ \ "status").write[String] and
        (__ \ "bar").write[Bar]
      )(unlift(Foo.unapply))
    }
    
  6. ==============================

    6.이것은 아마도 "최소한의 소음"요구 사항을 만족하지 않습니다,하지만 이유는 옵션 [문자열]로 새 매개 변수를 도입하지?

    이것은 아마도 "최소한의 소음"요구 사항을 만족하지 않습니다,하지만 이유는 옵션 [문자열]로 새 매개 변수를 도입하지?

    case class Foo(id:String,value:String, status:Option[String] = Some("pending"))
    

    오래된 클라이언트에서 푸를 읽을 때, 당신은 당신의 소비자 코드 (A getOrElse에) 그때 처리 할 거라고 없음을 얻을 수 있습니다.

    당신이 마음에 들지 않으면 또는, BackwardsCompatibleFoo를 소개합니다 :

    case class BackwardsCompatibleFoo(id:String,value:String, status:Option[String] = "pending")
    case class Foo(id:String,value:String, status: String = "pending")
    

    다음 코드에 따라 데이터 체조 모두 이런 종류의 처리해야 할 피, 더에 작업 할 푸에 하나를 켜십시오.

  7. ==============================

    7.당신은 옵션으로 상태를 정의 할 수 있습니다

    당신은 옵션으로 상태를 정의 할 수 있습니다

    case class Foo(id:String, value:String, status: Option[String])
    

    사용 JsPath 너무 좋아 :

    (JsPath \ "gender").readNullable[String]
    
  8. from https://stackoverflow.com/questions/20616677/defaults-for-missing-properties-in-play-2-json-formats by cc-by-sa and MIT license