Repository navigation
Expand file tree
/
Copy pathordered_json.lua
More file actions
125 lines (114 loc) · 3.21 KB
/
Copy pathordered_json.lua
File metadata and controls
125 lines (114 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
--- OrderedJson: JSON with key order preservation
---@class OrderedJson
local OrderedJson = {}
OrderedJson.__index = OrderedJson
--- Structure returned by OrderedJson:read
---@class OrderedJsonReadResult
---@field data table
---@field _ordered_keys string[]
---Create a new OrderedJson instance
---@return OrderedJson
function OrderedJson.new()
return setmetatable({}, OrderedJson)
end
---Check if a table is a contiguous array
---@param tbl table
---@return boolean
function OrderedJson:is_array(tbl)
if type(tbl) ~= 'table' then
return false
end
local n = #tbl
for k, _ in pairs(tbl) do
if type(k) ~= 'number' or k < 1 or k > n or k % 1 ~= 0 then
return false
end
end
return n > 0
end
---Read a JSON file and preserve key order
---@param file_path string
---@return OrderedJsonReadResult|nil
function OrderedJson:read(file_path)
local f = io.open(file_path, 'r')
if not f then
vim.notify('Failed to open file ' .. file_path, vim.log.levels.ERROR)
return nil
end
local content = f:read('*a')
f:close()
local ordered_keys = {}
for key in content:gmatch('"([^"]+)":') do
table.insert(ordered_keys, key)
end
local ok, json = pcall(vim.json.decode, content)
if not ok then
vim.notify('Failed to decode JSON in ' .. file_path, vim.log.levels.ERROR)
return nil
end
return {
data = json,
_ordered_keys = ordered_keys,
}
end
---Encode a table or OrderedJsonReadResult to a JSON string, preserving key order if present
---@param obj table|OrderedJsonReadResult
---@param indent? string
---@param level? integer
---@return string
function OrderedJson:encode(obj, indent, level)
indent = indent or ' '
level = level or 0
local tbl, ordered_keys
if type(obj) == 'table' and obj._ordered_keys then
tbl = obj.data
ordered_keys = obj._ordered_keys
else
tbl = obj
ordered_keys = nil
end
local function encode_value(val, _indent, _level)
if type(val) == 'table' then
return self:encode(val, _indent, _level + 1)
end
return vim.json.encode(val)
end
if self:is_array(tbl) then
local parts = { '[\n' }
for i, v in ipairs(tbl) do
if i > 1 then
table.insert(parts, ',\n')
end
table.insert(parts, string.rep(indent, level + 1))
table.insert(parts, encode_value(v, indent, level + 1))
end
table.insert(parts, '\n' .. string.rep(indent, level) .. ']')
return table.concat(parts)
else
local parts = { '{\n' }
local first = true
local function insert_key_value(key, value)
if not first then
table.insert(parts, ',\n')
end
first = false
table.insert(parts, string.rep(indent, level + 1))
table.insert(parts, string.format('"%s": %s', key, encode_value(value, indent, level + 1)))
end
if ordered_keys then
for _, key in ipairs(ordered_keys) do
if tbl[key] ~= nil then
insert_key_value(key, tbl[key])
end
end
end
for key, value in pairs(tbl) do
if not ordered_keys or not vim.tbl_contains(ordered_keys, key) then
insert_key_value(key, value)
end
end
table.insert(parts, '\n' .. string.rep(indent, level - 1) .. '}')
return table.concat(parts)
end
end
return OrderedJson