Formaty danych które powinien znać każdy programista

Format danych to sposób w jaki zapisujemy dane – mamy tutaj spory wybór i to od niego zależy dość mocno wygoda jaką będą miały osoby korzystające z udostępnionych przez nas danych. W ramach tego artykułu przejdziemy sobie przez kilka najpopularniejszych – czyli takich które każdy programista powinien znać :)

Wpis jest efektem szkolenia które przeprowadziłem 26ego września w Krakowie, gdzie w ramach całego, dużego kursu Pythona, opowiadałem o sposobach korzystania z różnego rodzaju API jak i o dobrych praktykach przy jego tworzeniu. Scenariusz zajęć stworzyłem tak by był pełen zadań praktycznych które mocno dotykały właśnie formatów danych, co podczas zajęć okazało się niemniej ważne niż HTTP czy REST. Udało się oczywiście wszystko fajnie przedstawić ale zrodziło to właśnie pomysł na ten wpis :)

Przykłady zapisu i odczytu różnych formatów będą w Pythonie ale bez problemu znajdziesz w Internecie przykłady jak zrobić to w innych językach.

No ale żeby nie przedłużać, zacznijmy od chyba najpopularniejszego w całym programowaniu formatu danych – od XMLa.

XML

XML to język znaczników przeznaczony do reprezentowania różnych danych w strukturalizowany sposób. Jest niezależny od platformy, co umożliwia łatwą wymianę dokumentów pomiędzy różnymi systemami. Jest używany w bardzo popularnych protokole wymiany informacji – SOAP.

Do parsowania tego formatu danych używa się dwóch podejść:

  • SAX – w tym podejściu patrzymy na plik XML jak na strumień danych i definiujemy odpowiednie metody które parser będzie uruchamiał w momencie wystąpienia jakiegoś zdarzenia np. gdy wejście w jakiś element, to nasza metoda może wydobyć jakiś atrybut z niego.
  • DOM – patrzymy na plik XML jak na model obiektowy. Parsery tego typu dostarczają nam metody w stylu „getElementsByTagName” – bez potrzeby obsługi, jak w SAXie, samego procesu przetwarzania.

Oba podejścia mają swoje wady i zalety i np. model DOM jest oczywiście prostszy w używaniu ale wymaga załadowania całego pliku do pamięci – co może okazać się śliskie. SAX nie trzyma w pamięci zbyt dużo – on uruchamia zdefiniowane metody w momencie czytania samego pliku – więc do pamięci trafiają już tylko wyłuskane przez nas dane. Patrząc z innej strony – SAX jest jednokierunkowy, tzn jeśli odwiedzimy jakiś znacznik to do poprzedniego już nie wrócimy. Przy modelu DOM – możemy chodzić po stworzonym drzewie w każdą stronę.

Więc który lepszy? Ano odpowiedź jest taka jak ulubiona odpowiedź każdego seniora/lidera/experta – i brzmi „to zależy do kontekstu” :) więc wybierając sposób odczytu – ustal pierw jak duże te pliki będą, ile masz pamięci i jak szybko ma to działać.

Przykład

Przykład zamiany słownika na xml i na odwrót:

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
from xml.dom.minidom import parseString
from dicttoxml import dicttoxml

data = {
    'blog_url': 'https://mmazurek.dev',
    'blog_rate': 10,
    'blog_max_rate': 10,
    'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']
}
dict_as_xml = dicttoxml(data, custom_root='blog')

"""
<?xml version="1.0" ?>
<blog>
    <blog_url type="str">https://mmazurek.dev</blog_url>
    <blog_rate type="int">10</blog_rate>
    <blog_max_rate type="int">10</blog_max_rate>
    <blog_keywords type="list">
        <item type="str">programowanie</item>
        <item type="str">python</item>
        <item type="str">proces</item>
        <item type="str">tworzenia</item>
        <item type="str">programowania</item>
    </blog_keywords>
</blog>

"""


xml = parseString(dict_as_xml)

xml_as_dict = {}

blog = xml.firstChild

for item in blog.childNodes[0:-1]:
    xml_as_dict[item.tagName] = item.firstChild.nodeValue

last_key = blog.childNodes[-1].tagName
xml_as_dict[last_key] = []

for item in blog.childNodes[-1].childNodes:
    xml_as_dict[last_key].append(item.firstChild.nodeValue)

print(xml_as_dict)

"""
{'blog_url': 'https://mmazurek.dev', 'blog_rate': '10', 'blog_max_rate': '10', 'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']}
"""

Do zamiany słownika na xml użyłem biblioteki dicttoxml. Należy ją doinstalować pip’em. Xmla zamieniłem na słownik używając prostej implementacji DOM o nazwie minidom.

JSON

Jestem prawie pewien że każdy kto czyta ten artykuł zna JSONa – to obecnie najpopularniejszy format danych. Używany jest w wywołaniach AJAX np. w REST, chociaż żadna specyfikacja tego nie nakazuje.

Podrzucam gramatykę JSONa: https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4 czyli po prostu formalny opis składni.

Przykład

1
2
3
4
5
6
7
8
9
10
11
12
from json import loads, dumps

data = {
    'blog_url': 'https://mmazurek.dev',
    'blog_rate': 10,
    'blog_max_rate': 10,
    'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']
}

dict_as_json = dumps(data)

json_as_dict = loads(dict_as_json)

W Pythonie mamy w standardowej bibliotece moduł json który posiada metody loads/dumps oraz ich odpowiedniki dla pracy na plikach: load/dump.

Wspomnę jeszcze o problemie serializacji własnych obiektów:

1
2
3
4
5
6
7
8
9
10
11
12
13
from json import loads, dumps


class Blog:
    def __init__(self):
        self.property = "value"


obj_as_json = dumps(Blog())

"""
TypeError: Object of type Blog is not JSON serializable
"""

Próba serializacji własnych obiektów skutkuje wyjątkiem TypeError, w prostych przypadkach można użyć do serializacji słownikowej reprezentacji instancji obiektu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from json import loads, dumps


class Blog:
    def __init__(self):
        self.property = "value"


obj_as_json = dumps(Blog().__dict__)

print(obj_as_json)
"""
{"property": "value"}
"""

YAML

Jak pierwszy raz zobaczyłem YAMLa (YAML Ain’t Markup Language) to od razu przypominał mi Pythona – tutaj zakres danych i relacje rodzic-dziecko realizowana jest za pomocą wcięć. Znalazł się w tym artykule ponieważ zyskuje popularność :)

Używany jest np. w Ansiblu czy Kubernetesie.

Przykład

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from yaml import safe_load, safe_dump

data = {
    'blog_details': {
        'blog_url': 'https://mmazurek.dev',
        'blog_rate': 10,
        'blog_max_rate': 10,
        'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania'],
        'additional_info': [None, True, {
            "type": ['intresting', 'type']
        }]
    }
}
dict_as_yml = safe_dump(data)
print(dict_as_yml)

Efekt tego programu to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
blog_details:
  additional_info:
  - null
  - true
  - type:
    - intresting
    - type
  blog_keywords:
  - programowanie
  - python
  - proces
  - tworzenia
  - programowania
  blog_max_rate: 10
  blog_rate: 10
  blog_url: https://mmazurek.dev

co jest dość ciekawe, bo o ile założenie że JSONa zna każdy jest bezpieczne, to za YAML’a nie dałbym niczego sobie uciąć.

Tak jak napisałem wyżej – relacje rodzic-dziecko realizowane są wcięciami a listy znakami „-„. W przeciwieństwie do JSONa, YAML umożliwia odwołanie się do innych elementów samego siebie – co pozwala na to by nie duplikować danych.

Do obsługi tego formatu należy doinstalować moduł pyyaml.

INI

Ten format danych możesz spotkać najczęściej jako sposób trzymania konfiguracji. Mamy tu bardzo proste relacje – tworzymy sekcje a pod sekcją może być para klucz-wartość. INI nie pozwala (na tyle na ile wiem, jeśli się mylę – popraw mnie w komentarzu) na definiowanie list.

Przykład

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from configparser import ConfigParser

data = {
    'blog_url': 'https://mmazurek.dev',
    'blog_rate': 10,
    'blog_max_rate': 10,
    'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']
}

config = ConfigParser()
config.add_section('blog')

for key, val in data.items():
    if not isinstance(val, list):
        config.set('blog', key, str(val))
    else:
        config.add_section(key)
        config.set(key, key, ','.join(val))

with open("inifile.ini", "w") as f:
    config.write(f)

Co wygeneruje taki plik:

[blog]
blog_url = https://mmazurek.dev
blog_rate = 10
blog_max_rate = 10

[blog_keywords]
blog_keywords = programowanie,python,proces,tworzenia,programowania

Zauważ że listę zrealizowaliśmy „na piechotę”. Można ten format połączyć z JSONem i zrobić coś takiego:

[blog]
blog_url = https://mmazurek.dev
blog_rate = 10
blog_max_rate = 10
blog_keywords = ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']

Dane binarne

Te formaty które pokazałem wyżej to formaty tekstowe. Poza formatem tekstowym, czyli takim który gołym okiem może zostać odczytany, jest jeszcze format binarny w którym obiekt zapisujemy binarnie. Ma to swoje wady – jak np. to że dane takie nie są możliwe do przeniesienia pomiędzy językami, ba, czasem nawet pomiędzy programami w tym samym języku jest problem. Ma też zalety – zapisując binarnie nie martwimy się o to ze ktoś nam dane w takim formacie zmieni – nawet jeśli zmieni to po zmianie nie będą nadawać się do deserializacji.

W Pythonie mamy moduł pickle i używa się go tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pickle import load, dump

data = {
    'blog_url': 'https://mmazurek.dev',
    'blog_rate': 10,
    'blog_max_rate': 10,
    'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']
}


with open('data.pickle', 'wb') as f:
    dump(data, f)

with open('data.pickle', 'rb') as f:
    data = load(f)

print(data)

"""
{'blog_url': 'https://mmazurek.dev', 'blog_rate': 10, 'blog_max_rate': 10, 'blog_keywords': ['programowanie', 'python', 'proces', 'tworzenia', 'programowania']}
"""

Zmierzając do brzegu

Mimo że JSON jest najpopularniejszym formatem danych to nigdy nie wiadomo do końca z czym będziemy mieć przyjemność pracować. Może ta integracja z jakimś nowym API co czeka na przyszły sprint, okazać się świetną sposobnością do tego by przekonać się o pomysłowości programistów.

A jeśli nie, to przecież wiedzy nigdy za dużo :)

Dzięki za wizytę,
Mateusz Mazurek
Podziel się na:
    Facebook email PDF Wykop Twitter

2
Dodaj komentarz

avatar
1 Wątki
1 Odpowiedzi
0 Śledzący
 
Komentarz z największą liczbą reakcji
Najczęściej komentowany wątek
2 Komentarze autora
Mateusz M.asd Ostatnie komentarze autora

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subskrybuj  
Powiadom o
asd
Gość
asd

>W przeciwieństwie do JSONa, YAML umożliwia do innych elementów samego siebie – co pozwala na to by nie duplikować danych.

Nie rozumiem