Elm Json.Decode.Pipeline无法解码现有字段

问题描述

我有一个Elm解码器并对其进行了测试。测试通过了,但是当我在应用程序上使用解码器时,出现了一个奇怪的错误,该错误似乎与传递的数据不一致。我不知道怎么了。为了公开起见,我是Elm的新手,这是我的第一个应用程序,针对生产。


解码器

type alias ProviderData =
    { uid : String,displayName : Maybe String,photoURL : Maybe String,email : String,phoneNumber : Maybe String,providerId : String
    }

type alias TokenManager =
    { apiKey : String,refreshToken : String,accesstoken : String,expirationTime : Time.Posix
    }

type alias User =
    { uid : String,emailVerified : Bool,isAnonymous : Bool,tenantId : Maybe String,providerData : List ProviderData,apiKey : String,appName : String,authDomain : String,stsTokenManager : TokenManager,redirectEventId : Maybe String,lastLoginAt : Time.Posix,createdAt : Time.Posix
    }

type alias Model =
    { isUserSignedIn : Bool,isWaitingForSignInLink : Bool,user : Maybe User
    }

unauthenticatedUser : Model
unauthenticatedUser =
    { isUserSignedIn = False,isWaitingForSignInLink = False,user = nothing
    }

decoder : Json.Value -> Model
decoder json =
    case Json.decodeValue authDecoder (Debug.log ("Decoding " ++ Json.Encode.encode 4 json) json) of
        Ok model ->
            model

        Err err ->
            Debug.log (Debug.toString err) unauthenticatedUser

authDecoder : Json.Decoder Model
authDecoder =
    Json.succeed Model
        |> required "isUserSignedIn" Json.bool
        |> required "isWaitingForSignInLink" Json.bool
        |> optional "user" (Json.map Just decodeUser) nothing

decodeUser : Json.Decoder User
decodeUser =
    Json.succeed User
        |> required "uid" Json.string
        |> optional "displayName" (Json.map Just Json.string) nothing
        |> optional "photoURL" (Json.map Just Json.string) nothing
        |> required "email" Json.string
        |> required "emailVerified" Json.bool
        |> optional "phoneNumber" (Json.map Just Json.string) nothing
        |> required "isAnonymous" Json.bool
        |> optional "tenantId" (Json.map Just Json.string) nothing
        |> required "providerData" (Json.list decodeProviderData)
        |> required "apiKey" Json.string
        |> required "appName" Json.string
        |> required "authDomain" Json.string
        |> required "stsTokenManager" decodetokenManager
        |> optional "redirectEventId" (Json.map Just Json.string) nothing
        |> required "lastLoginAt" timestampFromString
        |> required "createdAt" timestampFromString

decodeProviderData : Json.Decoder ProviderData
decodeProviderData =
    Json.succeed ProviderData
        |> required "uid" Json.string
        |> optional "displayName" (Json.map Just Json.string) nothing
        |> optional "photoURL" (Json.map Just Json.string) nothing
        |> required "email" Json.string
        |> optional "phoneNumber" (Json.map Just Json.string) nothing
        |> required "providerId" Json.string

decodetokenManager : Json.Decoder TokenManager
decodetokenManager =
    Json.succeed TokenManager
        |> required "apiKey" Json.string
        |> required "refreshToken" Json.string
        |> required "accesstoken" Json.string
        |> required "expirationTime" timestampFromInt

timestampFromString : Json.Decoder Time.Posix
timestampFromString =
    Json.andThen
        (\str ->
            case String.toInt str of
                Just ts ->
                    Json.succeed (Time.millisToPosix ts)

                nothing ->
                    Json.fail (str ++ " is not a tiMetamp")
        )
        Json.string

timestampFromInt : Json.Decoder Time.Posix
timestampFromInt =
    Json.andThen
        (\ts ->
            Json.succeed (Time.millisToPosix ts)
        )
        Json.int

unauthenticatedUser : Model
unauthenticatedUser =
    { isUserSignedIn = False,user = nothing
    }

错误

Failure "Json.Decode.oneOf Failed in the following 2 ways:



(1) Problem with the given value:
    
    {
            "uid": "","displayName": null,"photoURL": null,"email": "joe@mail.com","emailVerified": true,"phoneNumber": null,"isAnonymous": false,"tenantId": null,"providerData": [
                {
                    "uid": "joe@mail.com","providerId": "password"
                }
            ],"apiKey": "","appName": "[DEFAULT]","authDomain": "example.firebaseapp.com","stsTokenManager": {
                "apiKey": "","refreshToken": "","accesstoken": "","expirationTime": 1603998440000
            },"redirectEventId": null,"lastLoginAt": "1603576515267","createdAt": "1603573117442","multiFactor": {
                "enrolledFactors": []
            }
        }
    
    Expecting an OBJECT with a field named `createdAt`



(2) Problem with the given value:
    
    {
            "uid": "","multiFactor": {
                "enrolledFactors": []
            }
        }
    
    Expecting null" <internals>: { isUserSignedIn = False,user = nothing }

测试

module Tests exposing (..)

import Expect
import Json.Decode as Json
import Modules.Firebase as Firebase
import Test exposing (..)
import Time


all : Test
all =
    describe "Test Firebase"
        [ test "Test auth JSON" <|
            \_ ->
                let
                    input =
                        """
                          { 
                              "isUserSignedIn": true,"isWaitingForSignInLink": false,"user": {
                                "uid": "xxxxxxxxxxxx","providerData": [
                                  {
                                    "uid": "joe@mail.com","providerId": "password"
                                  }
                                ],"apiKey": "apikey.xxxxxxxxx","authDomain": "example.com","stsTokenManager": {
                                  "apiKey": "apikey.xxxxxxxxx","refreshToken": "refresh.xxxxxxxxxxxx","accesstoken": "access.xxxxxxxxxxxx","expirationTime": 1603825391000
                                },"multiFactor": {
                                  "enrolledFactors": []
                                }
                              }
                          }
                        """

                    decodedOutput =
                        case Json.decodeString Json.value input of
                            Ok value ->
                                Ok (Firebase.decoder value)

                            Err err ->
                                Err err
                in
                Expect.equal decodedOutput
                    (Ok firebaseModel)
        ]

firebaseModel : Firebase.Model
firebaseModel =
    { isUserSignedIn = True,user =
        Just
            { uid = "xxxxxxxxxxxx",displayName = nothing,photoURL = nothing,email = "joe@mail.com",emailVerified = True,phoneNumber = nothing,isAnonymous = False,tenantId = nothing,providerData =
                [ { uid = "joe@mail.com",providerId = "password"
                  }
                ],apiKey = "apikey.xxxxxxxxx",appName = "[DEFAULT]",authDomain = "example.com",stsTokenManager =
                { apiKey = "apikey.xxxxxxxxx",refreshToken = "refresh.xxxxxxxxxxxx",accesstoken = "access.xxxxxxxxxxxx",expirationTime = Time.millisToPosix 1603825391000
                },redirectEventId = nothing,lastLoginAt = Time.millisToPosix 1603576515267,createdAt = Time.millisToPosix 1603573117442
            }
    }

于2020-10-30编辑

调试信息

尝试调试此问题,我开始逐字段逐步构建模型,以了解问题所在。以下模型和解码器可以正常工作:

... 
type alias User =
    { uid : String,providerData : List ProviderData
    }
...
decodeUser : Json.Decoder User
decodeUser =
    Json.succeed User
        |> required "uid" Json.string
        |> optional "displayName" (Json.nullable Json.string) nothing
        |> optional "photoURL" (Json.nullable Json.string) nothing
        |> required "email" Json.string
        |> required "emailVerified" Json.bool
        |> optional "phoneNumber" (Json.nullable Json.string) nothing
        |> required "isAnonymous" Json.bool
        |> optional "tenantId" (Json.nullable Json.string) nothing
        |> required "providerData" (Json.list decodeProviderData)
...

注1-所有其他代码保持不变。

注2-输入数据保持不变。

注释3-我通过@ 5ndG建议将Json.map Just更改为Json.nullable。我也尝试过optional "field" (Json.nullable Json.string) nothingrequired "field" (Json.nullable Json.string),但问题仍然存在。

进行此更改后,模型将达到预期状态,减去我删除的字段。通过添加一个字段|> required "apiKey" Json.string,它会因相同的问题而开始失败。

解决方法

我通过将第一个参数设置为字符串并强制我的服务传递JSON字符串而不是Json.Decode.Value来解决此问题。

我将装饰器更改为:


decoder : String -> Model
decoder json =
    case Json.decodeString Json.value json of
        Ok value ->
            decodeValue value

        Err _ ->
            unauthenticatedUser


decodeValue : Json.Value -> Model
decodeValue json =
    case Json.decodeValue authDecoder json of
        Ok model ->
            model

        Err _ ->
            Debug.log unauthenticatedUser

不确定为什么我不能使用服务中的Json.Decode.Value,但这可以解决问题。

,

编辑:此答案是错误的,因此请忽略它。

我可以使用测试中的此输入来重现您的错误:

{ 
    "isUserSignedIn": true,"isWaitingForSignInLink": false
}

也就是说,完全错过了user字段。

通过阅读docs,我认为这是optional函数中的错误。

here所述,您可以通过将user解码器替换为以下内容来解决此问题:

       |> optional "user" (Json.nullable decodeUser) Nothing