问题描述
我有一个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) nothing
至required "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