Firebase multi-path update invalid data; couldn't parse JSON object, array, or value
After an unsuccessful attempt at using the official support@firebase.com channel, I took to brute force debugging.
I parsed out each key/value pair separately as its own table and attempted a multi-path update, and each time it worked. This is when I knew I was onto something odd. Then I slowly built up the entire table key by key until the multi-path update failed and I saw the issue.
My update contained the following:
{
"foo/bar": {
"data": {
"baz": 1
}
},
"foo/bar/data": {
"quu": 2
}
}
And I was hoping for the resulting data in Firebase:
{
foo: {
bar: {
data: {
baz: 1,
quu: 2
}
}
}
}
So, the simple answer is, a multi-path update cannot contain two key names that write to the same location (or a location deeper in the same path).
Now, my multi-path update contained upwards of 20 key/value pairs, so it wasn't quite as easy to spot as the example I've laid out here, so cut me a little slack. I can understand for a number of reasons why this may not be allowed (the atomicity of the request, which update gets applied first, etc), but my issue is that the error returned from Firebase was not only not helpful, it flat out pointed me in the wrong direction, making debugging even harder.
So, the answer is to combine the two multi-path update keys that write to the same location in Firebase to look like the following:
{
"foo/bar/data" : {
"baz": 1,
"quu": 2
}
}
Does Firebase Realtime Database REST API support multi path updates at different entity locations?
If the updates are going to a single database, there is always a common path.
In your case you'll run the PATCH
command against the root of the database:
curl -X PATCH -d '{
"path1/17": json1,
"path2/1733455": json2
}' 'https://yourdatabase.firebaseio.com/.json'
The key difference with your URL seems to be the /
before .json
. Without that you're trying to connect to a domain on the json
TLD, which doesn't exist (yet) afaik.
Note that the documentation link you provide for Batched Updates is for Cloud Firestore, which is a completely separate database from the Firebase Realtime Database.
Firebase: How to update multiple nodes transactionally? Swift 3
In the Firebase documentation in the section Enable Offline Capabilities it is specified that:
Transactions are not persisted across app restarts
Even with persistence enabled, transactions are not persisted across
app restarts.
So you cannot rely on transactions done offline being
committed to your Firebase Realtime Database.
Therefore:
1. there is no way to use firebase transactions at client side to update a value at two or more paths.
2. using a completion callback to perform the second write is not viable since the client could restart the app before a response is received in the completion handler from the firebase server, thus leaving the database in inconsistent state.
I assume that my only option to update data transactionally at the first path and further update the second path with that data that was already written at the first path in Firebase Database, would be to use Conditional Requests over REST as specified in the Firebase Documentation.
This would achieve the same functionality provided by Firebase framework for IOS clients.
- The client will make a request via Alamofire to my server (I will use Vapor framework so as to leverage the Swift language), once the request is received at the Vapor server, a GET request will be sent to the Firebase Database server
root/users/bookings/4875383
in which I will request an ETAG_VALUE
- The client will make a request via Alamofire to my server (I will use Vapor framework so as to leverage the Swift language), once the request is received at the Vapor server, a GET request will be sent to the Firebase Database server
What is ETAG Value?: ( a unique identifier which will be different every time data changes at the path where GET Request is made. Namely if another user writes to the same path before my write request the resource succeeds, my write operation will be rejected since the ETAG value at the path will have already been modified by the other user's write operation. This would enable users to write data transactionally to a path)
- a response is received from the Firebase Server containing a an ETAG_VALUE.
- make a PUT request to the Firebase Server and in the header specify the ETag: [ETAG_VALUE] received from the previous GET request. If the ETAG value posted to the server matches the value at the Firebase Server, the write will succeed. If the location no longer matches the ETag, which might occur if another user wrote a new value to the database, the request fails without writing to the location. The return response includes the new value and ETag.
- Furthermore, now we can update the value at
root/Cleaners/bookings/4875383
to reflect the job that was claimed by a cleaner.
- Furthermore, now we can update the value at
Use transaction to update value at two different nodes
You can push all your logic for updating the database onto the server side with Cloud Functions for Firebase. Use can use a database trigger to respond to data being written in the database, then execute some JavaScript to make sure the fan-out finishes correctly. It will have the advantage of making sure all the changes happen without depending on the client.
Transactions can't modify data at two different locations at once, but you will still probably want to use them in your client and Cloud Functions to make sure concurrent writes will not have problems.
firebase POST url 400 bad request
There are a number of things wrong here:
- You aren't using the value of
id
ornum
here, you need to wrap them in square brackets - The
body
property of afetch
API call should be passed throughJSON.stringify
. - You shouldn't post a Date object in the body of a request. Either convert it to a string, convert it to a plain number, use a timestamp object or use a server value placeholder.
- Your code always assumes that the post was created successfully. The
fetch
API will not throw an error for bad status codes. You should check the status code to determine what you want to do. As Firebase Database operations usually respond in JSON, I've parsed the body here as JSON before checking the code however you should probably check for empty bodies first. - Using
POST /blogs.json
will overwrite your entire/blogs
tree each time you write data. You should change the path or use a PATCH request with the proper body for it.
const id = localStorage.getItem('uid')
const postData = async () => {
const num = localStorage.getItem('blogNumber')
const reqData = {
[id]: { // <-- use square brackets for value as key
[num]: { // <-- use square brackets for value as key
title: titleRef.current.value,
description: contentRef.current.value,
createdAt: Date.now(), // <-- returns milliseconds since epoch
isPosted: false
}
}
}
const resp = await fetch(
'https://assignment-c3557-default-rtdb.asia-southeast1.firebasedatabase.app.firebaseio.com/blogs.json',
{
method: 'POST',
body: JSON.stringify(reqData), // you need to stringify this yourself
headers: {
'Content-Type': 'application/json'
}
}
);
const data = await resp.json();
if (!resp.ok) { // fetch API does not throw errors by itself
const err = new Error('Unexpected status: ' + resp.status);
err.data = data;
err.resp = resp;
throw err;
}
localStorage.setItem('blogNumber', localStorage.getItem('blogNumber')+1)
}
Updated to use deeper path:
const id = localStorage.getItem('uid')
const postData = async () => {
const num = localStorage.getItem('blogNumber')
const reqData = {
title: titleRef.current.value,
description: contentRef.current.value,
createdAt: Date.now(), // <-- returns milliseconds since epoch
isPosted: false
}
const resp = await fetch(
`https://assignment-c3557-default-rtdb.asia-southeast1.firebasedatabase.app.firebaseio.com/blogs/${uid}/${num}.json`,
{
method: 'POST',
body: JSON.stringify(reqData), // you need to stringify this yourself
headers: {
'Content-Type': 'application/json'
}
}
);
const data = await resp.json();
if (!resp.ok) { // fetch API does not throw errors by itself
const err = new Error('Unexpected status: ' + resp.status);
err.data = data;
err.response = resp;
throw err;
}
localStorage.setItem('blogNumber', localStorage.getItem('blogNumber')+1)
}
How to investigate errors in Firebase Remote Config API?
Google Developer Support pointed me to a known bug in the Firebase Remote Config library which has been fixed recently. Upgrading the library indeed fixed the issue.
Related Topics
Uiscrollview Adjusts Contentoffset When Contentsize Changes
How to Convert .Dae to .Scn Files in Scenekit
How to Detect the Colour of an iPhone 5C
Number of Threads Created by Gcd
Which Tasks Are More Suitable to Nsoperation Than Gcd
Sensitivity/Scroll Speed of Uiscrollview with Paging
How to Convert Text to Image on iOS
How to Custom Modal View Controller Presenting Animation
How to Make Sure API Requests Come from Our Mobile (Ios/Android) App
Custom Cell Reorder Behavior in Collectionview
Does iOS Provide Built in Text to Speech Support or Any Class Like Nsspeechrecognizer
Google Analytics iOS Campaign Tracking and Url Builder
Profile Doesn't Match the Entitlements File's Value for the Application-Identifier Entitlement