Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1v6Vcx-005gjB-T2 for pgsql-general@arkaria.postgresql.org; Wed, 08 Oct 2025 14:58:52 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1v6Vcv-00Auim-JS for pgsql-general@arkaria.postgresql.org; Wed, 08 Oct 2025 14:58:50 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1v6Vcu-00Auid-SG for pgsql-general@lists.postgresql.org; Wed, 08 Oct 2025 14:58:50 +0000 Received: from fhigh-a3-smtp.messagingengine.com ([103.168.172.154]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1v6Vcs-0013MN-2G for pgsql-general@lists.postgresql.org; Wed, 08 Oct 2025 14:58:49 +0000 Received: from phl-compute-06.internal (phl-compute-06.internal [10.202.2.46]) by mailfhigh.phl.internal (Postfix) with ESMTP id 5DCEF140017B; Wed, 8 Oct 2025 10:58:44 -0400 (EDT) Received: from phl-mailfrontend-01 ([10.202.2.162]) by phl-compute-06.internal (MEProxy); Wed, 08 Oct 2025 10:58:44 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aklaver.com; h= cc:content-transfer-encoding:content-type:content-type:date:date :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1759935524; x=1760021924; bh=M3Kesyum+2cDOmIY3eEjtE6bfobwGjSthZhc/fMyW+E=; b= H3nKA2aJN8vgTh2yYpCsdSjspGzdF8lJk99NRF7A8fnhcdmM1QGK82R/3ie00+uJ Sn4TvrjdB2kLrBWCibmrg8XbPJ8MzW1uDCaDrh3qvUeGs4xD+TBkgM2Nc1UfeT2Y UFv1z9M8RYSE9sZ/vWAAj4MkoNYN4CvWYsl0KVDkEFvBUdGbXl5sfTQEiD9kCvxP EFwTFTBNIdVYYHwgZFaOX0avWZ0yn8hhwsLiR/yfR5I8MwunLtsbdWqy1PO/Hk7T /p8R3e/Y1px4SFBPU0R3kZKDCRLo8p3i7QMVQYi548VM7TazXxsbjlYIpyidTq4g YONxnjb3D80BUECSL4S38Q== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm2; t=1759935524; x=1760021924; bh=M 3Kesyum+2cDOmIY3eEjtE6bfobwGjSthZhc/fMyW+E=; b=Zihqf9W/HjHfzmyhP U33GIS1oUfTGozl77MxTlvovQ2/b5RZSoLpLWXp7X0jyqRfHnWBke6Jy+mqyrOg/ D/5EndbRNLOKXiEiZk53v5xlHpHW/hJJNNTHSc6Jng5q2uGke4YUOHsAxB02IKgp GlxxD1MCSW6R+exyVBycWBejT1atsRy66m6OXPEotmfdVobpAa+Z+DlHiwN8VyEY DtB6FrfUwMgkccgUcAht4j7AcS8YfaRjmUWdgHVDzUA9r8CBD9v84PKGhLuKF8ha sXT/l+Vm111yKQ/m+dCypwXcfA5O9ygAc9yuyh/OzeP1q387uzBOj2ZXtSjnsyz5 Qm20g== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdeggddutdefheelucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucenucfjughrpefkffggfgfuvfhfhfgjtgfgsehtkeertd dtvdejnecuhfhrohhmpeetughrihgrnhcumfhlrghvvghruceorggurhhirghnrdhklhgr vhgvrhesrghklhgrvhgvrhdrtghomheqnecuggftrfgrthhtvghrnhepleegveekkeekue eigfdtveeileeuhfefudefteekjeffkeejueejheegheegkedtnecuffhomhgrihhnpehp ohhsthhgrhgvshhqlhdrohhrghenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmh epmhgrihhlfhhrohhmpegrughrihgrnhdrkhhlrghvvghrsegrkhhlrghvvghrrdgtohhm pdhnsggprhgtphhtthhopedvpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehmrg hlvghkmhdtleduvddvtddtfeesghhmrghilhdrtghomhdprhgtphhtthhopehpghhsqhhl qdhgvghnvghrrghlsehlihhsthhsrdhpohhsthhgrhgvshhqlhdrohhrgh X-ME-Proxy: Feedback-ID: i76984098:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Wed, 8 Oct 2025 10:58:43 -0400 (EDT) Message-ID: <0d47dfca-297f-4add-a395-bc5997ce47a9@aklaver.com> Date: Wed, 8 Oct 2025 07:58:43 -0700 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: JSONB subscripting initializes numeric keys as arrays instead of objects To: Krrish Malhotra , pgsql-general@lists.postgresql.org References: Content-Language: en-US From: Adrian Klaver In-Reply-To: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On 10/8/25 02:11, Krrish Malhotra wrote: > I'm using PostgreSQL 16+ and working extensively with jsonb columns > using JSON subscripting paths (as described here: PostgreSQL docs – > jsonb subscripting json.html#:%7E:text=default%20database%20collation.-,8.14.5. > %C2%A0jsonb%20Subscripting,-The%20jsonb%20data>). I've run into an issue > when updating nested paths where intermediate keys might not exist. For > example: > > |UPDATE test SET data['A']['B']['C'] = '{"a": "b"}' WHERE data->>'ID' = > 'abcde'; | > > If A.B doesn’t exist, PostgreSQL automatically initializes it as an > empty JSON object ({}), and then correctly sets the key C. However, if > the last key is numeric, for example: > > |UPDATE test SET data['A']['B']['3'] = '{"a": "b"}' WHERE data->>'ID' = > 'abcde'; | > > PostgreSQL initializes A.B as an empty array instead of an object, and > sets the value at index 3. This behavior isn’t what I want, I’d like > numeric keys to be treated as JSON object keys (e.g., {"3": {...}}) > rather than as array indices. I know I can pre-initialize A.B like this: > > |data['A']['B'] = coalesce(data->'A'->'B', '{}') | > > But that causes problems when multiple JSON path updates happen in the > same query, since it can overwrite or reset other keys. > > Additionally, in my use case, I don’t always know in advance whether a > given path exists at the time of the update, so I’d like a solution that > won’t break or conflict with existing data. > > Is there any way to force PostgreSQL to treat numeric subscripts as > object keys instead of array indices, or otherwise control this > initialization behavior? > The docs are pretty straight forward on this: " Subscript paths must be traversable for all affected values insofar as they exist. For instance, the path val['a']['b']['c'] can be traversed all the way to c if every val, val['a'], and val['a']['b'] is an object. If any val['a'] or val['a']['b'] is not defined, it will be created as an empty object and filled as necessary. However, if any val itself or one of the intermediary values is defined as a non-object such as a string, number, or jsonb null, traversal cannot proceed so an error is raised and the transaction aborted. " You might want to look at jsonb_set from here: https://www.postgresql.org/docs/current/functions-json.html -- Adrian Klaver adrian.klaver@aklaver.com